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.
You will need a Dockerfile to build you image. Checkout this tutorial on that https://mkyong.com/docker/docker-spring-boot-examples/
There is a fully working example using springboot and Java 11 at https://github.com/simbo1905/bigquery-graphql/blob/master/pom.xml
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. (Note if you are using SpringBoot you won't use this plugin you
will use the spring-boot-maven-plugin).
<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 a
secret in a github environment called 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>
Note that maven may warn about having two versions of the deploy plugin deployed if you are using the spring boot plugin. This seems to be okay though.
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>
In order to pass the DOCKER_HUB_TOKEN into the action we need to setup an GitHub environment under
your repo Settings. Got to your GitHub repos Settings > Environments and create a new environment.
Note: You will not have that menu option if your repo is Private it is only a free feature on Public repos.
I called mine the same name as my repo docker-maven-ghactions-release
. Add two secrets into this environment:
DOCKER_HUB_TOKEN
which is a docker hub token you create at hub.docker.com under Account Settings > Security > New Access Token.MY_GITHUB_TOKEN
which is a github personal access token you create at github.com Settings > Developer Settings > Personal Access token.
Note that you cannot create any secret starting with GITHUB_
as that is a reserved prefix as environments have some default GITHUB_I* environment variables.
name: Release workflow
on:
push:
branches: master
jobs:
build:
runs-on: ubuntu-latest
environment: docker-maven-ghactions-release
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.MY_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