Life is too short for tedious, manual release processes.
Here, we will use Maven's Release plugin to execute releases.
Instead of triggering the release process manually, we will use a
GitHub Actions workflow on the master
branch. All development
work is done on the dev
branch (or feature branches) and a release is done
when (and only when) the dev
branch is merged with the master
branch.
The release process includes compiling a Java app, building a Docker image and publishing the image on Docker Hub.
Let's start with this mighty pom.xml
file in the root of our repository. For
simplicity and consistency, we assume:
- The project's
groupId
is the same as the GitHub user name. - The GitHub user name is the same as the Docker Hub user name.
- The project's
artifactId
is the same as the GitHub repository name. - The project's
artifactId
is also the same as the Docker Hub repository name. - The project uses semantic versioning, for example
1.0
. - During development the project's version will be the upcoming version number,
appended with
-SNAPSHOT
, for example1.1-SNAPSHOT
. - Releases in the repository will be tagged with the version number prefixed
with
v
, for examplev1.0
. - Docker image tags will be the same as the project's version, for example
1.0
. - The most recently built Docker image will also be available using the
latest
tag.
To build the POM, we will need to configure the following plugins:
This plugin is not strictly necessary, but for now we build the app using a
single assembled jar. To make the app jar executable, we add the manifest
tag
as shown below.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.3</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>my.main.Class</mainClass>
<addClasspath>true</addClasspath>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
We will use Spotify's plugin to build and publish the Docker image. We add two
executions, one for building a Docker image to using the project's version as a tag,
and one using the latest
tag [bang-fist-on-table, surely there must be a better
way of doing this!?].
To upload the image to Docker Hub, we create a
personal access token on Docker Hub. We will set this later as the
system environment variable DOCKER_HUB_TOKEN
.
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.12</version>
<executions>
<execution>
<id>default</id>
<goals>
<goal>build</goal>
<goal>push</goal>
</goals>
</execution>
<execution>
<id>tag-latest</id>
<goals>
<goal>build</goal>
<goal>push</goal>
</goals>
<configuration>
<tag>latest</tag>
</configuration>
</execution>
</executions>
<configuration>
<username>${project.groupId}</username>
<password>${env.DOCKER_HUB_TOKEN}</password>
<repository>registry.hub.docker.com/${project.groupId}/${project.artifactId}</repository>
<tag>${project.version}</tag>
</configuration>
</plugin>
We need to disable the default deploy plugin as we're use the Spotify Docker plugin.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
Apache's Maven release plugin does all the heavy lifting of the release process.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.5.3</version>
<configuration>
<tagNameFormat>v@{project.version}</tagNameFormat>
</configuration>
</plugin>
We'll add the above plugins along with any other dependencies to the skeleton
POM.xml
shown below. We also need to add an scm
tag containing the git
repository URL. This is where the release plugin will push the version changes to.
Similar to the Docker Hub password, we need to create a personal access token on GitHub. We will pass this in later using a system environment variable.
In the example below, we start the project's version with 1.1-SNAPSHOT
. This
means that once we run the release plugin, a version 1.1
will be released.
Once the release has been deployed, the release plugin will update the version
to 1.2-SNAPHOT
.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>my-github-username</groupId>
<artifactId>my-app-name</artifactId>
<version>1.1-SNAPSHOT</version>
<packaging>jar</packaging>
<scm>
<developerConnection>scm:git:https://github.com/${project.groupId}/${project.artifactId}</developerConnection>
<tag>HEAD</tag>
</scm>
<dependencies>
...
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<build>
<plugins>
...
</plugins>
</build>
</project>
name: Release workflow
on:
push:
branches: master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
with:
ref: master
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Configure git
run: |
git config --global committer.email "[email protected]"
git config --global committer.name "GitHub"
git config --global author.email "${GITHUB_ACTOR}@users.noreply.github.com"
git config --global author.name "${GITHUB_ACTOR}"
- name: Checkout master branch
run: git checkout master
- name: Prepare release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: mvn --batch-mode release:prepare -Dusername=$GITHUB_ACTOR -Dpassword=$GITHUB_TOKEN
- name: Perform release
env:
DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }}
run: mvn --batch-mode release:perform
Thanks!