Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save simbo1905/495400fb708fdc91b75ce9b1bc4666cc to your computer and use it in GitHub Desktop.
Save simbo1905/495400fb708fdc91b75ce9b1bc4666cc to your computer and use it in GitHub Desktop.
Automated Docker releases using Maven and GitHub Actions

Automated Docker releases using Maven and GitHub Actions

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

The POM

Let's start with this mighty pom.xml file in the root of our repository. For simplicity and consistency, we assume:

  1. The project's groupId is the same as the GitHub user name.
  2. The GitHub user name is the same as the Docker Hub user name.
  3. The project's artifactId is the same as the GitHub repository name.
  4. The project's artifactId is also the same as the Docker Hub repository name.
  5. The project uses semantic versioning, for example 1.0.
  6. During development the project's version will be the upcoming version number, appended with -SNAPSHOT, for example 1.1-SNAPSHOT.
  7. Releases in the repository will be tagged with the version number prefixed with v, for example v1.0.
  8. Docker image tags will be the same as the project's version, for example 1.0.
  9. 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:

maven-assembly-plugin

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>

dockerfile-maven-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>

maven-deploy-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.

maven-release-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>

Rest of the POM

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>

The GitHub Environment

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.

The GitHub Action workflow

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment