This latest instalment in the Jupiter Dev Log series covers the newly added “automatic release process” in some detail. The release process was decidedly manual before this work. I ran make docker-push on my local machine, and a new Docker image replaced the one on Docker Hub. There are in fact two key improvements: having versioned releases (and a versioning process to boot), and having an automated release process which acts on some pushes to GitHub and uploads the current version to DockerHub.

The version of the code spoken about in this release can be found under the v0.0.2 tag on GitHub. It’s not radically different than what was before it, however. But I’ll walk through the thought process here.

The first thing that was needed was a way to decouple the push to Docker Hub from my local machine state and my login status to Docker Hub. This is achieved via access tokens which are essentially alternatives to the one password for the account. But they can be managed much better than the password - issues for a single use case, revoked, etc.

The manual release process described in the previous article like so:

$ make docker-build
$ docker login
$ make docker-push

Can now become:

$ make docker-build
$ echo MY_TOKEN | docker login --username horia141 --password-stdin
$ make docker-push

The second thing I did was set up a very simple GitHub Actions workflow to essentially automatically run the above on every push to a branch.

I chose GitHub Actions for convenience’s sake. It looks quite fully featured, but I didn’t really do that much of a comparison between Travis, Circle, any other hosted CI/CD tool, and it. Self-hosted was out of the question of course, but the others could have been better. Or at least Circle, cause it seemed to be innovating a bit with its orbs repositories. I knew Travis wasn’t what it used to be, but I used it a lot in the past and was familiar with it. That said it was a neat tool to learn, and seems to be pretty powerful - especially the ability to have multiple workflows, actions modularity, etc.

I setup a secret named DOCKERHUB_TOKEN to the repo in GitHub. After that I added the following workflow under .github/workflows/release.yml:

name: Release

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Build the docker image
      run: make docker-build
    - name: Publish the docker image
      run: |
        echo $ | docker login --username horia141 --password-stdin
        make docker-push

The bulk of the work is actually done with this setup. However, it’s a bit aggressive when it does a release. More precisely: There’s only one version - latest, with no notion of “release version”. We really don’t want every push to any branch to be a “release”. We want some control of what gets exposed to people.

We can tackle both via process. Namely, we’re gonna implement something like gitflow for this thing, and a small setup for storing the version and other global variables.

I added the following Config file to the root of the repo:

BASENAME=jupiter
VERSION=0.0.1
DOCKERHUB_USER=horia141

The Makefile can be evolved a little bit to reference the Config file via the include & export commands, but mostly stays the same:

include Config
export

docker-build:
    docker build -t jupiter .

docker-push:
    docker login --username=${DOCKERHUB_USER} --password-stdin
    docker tag jupiter ${DOCKERHUB_USER}/${BASENAME}:${VERSION}
    docker push ${DOCKERHUB_USER}/${BASENAME}:${VERSION}

.PHONY: docker-build docker-push

One’ll have to run docker-push via echo $MY_TOKEN | make docker-push which is a little bizzaro. But a human shouldn’t be running this command at all, rather the system should do it.

With this set, I could then operate some process changes, inspired by gitflow:

  • I added a new develop branch and made this the primary branch in GitHub. Every work will start off of it as a feature / bugfix branch, and end up in it via a merge.
  • I relegated the master branch to a “log of releases”. It has about 30 commits of history. But from now on it should just be built out of releases of the form “Releases vX.Y.Z”, each with a tag vx.y.z.
  • Added a docs/releases/version-x.y.z.md series of documents, which are the release notes log too!
  • Other than this, it’s a standard gitflow process. There’s no other scripts, though they will come.

Finally the release.yml file can be morphed by restricting it only to the master branch with certain tags, and using the new docker-push rule:

name: Release

on:
  push:
    branches:
     - master
    tags:
     - v*

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
     - uses: actions/checkout@v2
     - name: Build the docker image
       run: make docker-build
     - name: Publish the docker image
       run: echo $(( secrets.DOCKERHUB_TOKEN )) | make docker-push

OK, this should be about it. Took a bit of time, but hopefully the gradual buildup of something more professional-looking can help you in your projects. There’s still a bunch to go for the future:

  • Having some git flow style scripts in the repo.
  • Learning to squash commits on the master branch.
  • Building a scripts directory both the make and Actions can reference.
  • Factoring out the steps into actions referencing the scripts.
  • Adding some tests (!) and having tests run as a separate workflow for each branch. But those I’ll add in later efforts. Till next time.