Push and publish Docker images with GitHub Actions

minompi

AlessandroMinoccheri

Posted on May 2, 2021

Push and publish Docker images with GitHub Actions

In many articles, I mentioned many times about using GitHub Actions because they are a good choice for a lot of reasons.

Nowadays I can admit that there is another choice that I have explored and used a lot these days.
What I mean is the functionality of pushing your docker image through your GitHub Actions during your CI process.

Usually, when I want to publish my docker images to DockerHub, I need to do it manually by the command line, like this:

Building the image

docker image build -t organization/project:0.1.0 .
Enter fullscreen mode Exit fullscreen mode

Publishing to DockerHub

docker push organization/project:0.1.0
Enter fullscreen mode Exit fullscreen mode

It’s not a lot of work, but every time you fix or add a new feature you need to remember to build a new image and publish it.
Usually, I try to avoid manual operations because human error is possible, and automating what is repetitive for me is a best practice everywhere.

So for this reason, in one open-source project Arkitect where I’m contributing nowadays, we have a Dockerfile that needs to be published every time there is a push on master, or a new release comes out.

I can build and publish the Docker image manually every time, but I prefer to avoid this and I have tried exploring GitHub Actions to automate this process.

Github Actions

Exploring GitHub’s actions to automate the process of publishing a docker image to DockerHub was interesting because I found a lot of other interesting GitHub actions and many projects that do the automation that I like.

First of all, I have inserted inside my GitHub project into Settings->Secrets, two important repository secrets:

  • DOCKERHUB_USERNAME: this is your username on Dockerhub or the name of your organization
  • DOCKERHUB_TOKEN: this is the token and you can get it going on DockerHub in Account Settings->Security. Here you can generate a new Access Token. You can take the value and put it on GitHub.

Next step, I have created a file for the workflow inside GitHub and named it docker-publish.yml

The file is something like this:

name: Arkitect
on:
  push:
    branches:
      — '*'
    tags:
      — '*'
 pull_request:
jobs:
  publish_docker_images:
    runs-on: ubuntu-latest
    steps:
    — name: Checkout
      uses: actions/checkout@v2
    — name: Docker meta
      id: meta
      uses: crazy-max/ghaction-docker-meta@v2
      with:
        images: phparkitect/phparkitect
        tags: |
          type=raw,value=latest,enable=${{ endsWith(GitHub.ref, 'master') }}
          type=ref,event=tag
        flavor: |
          latest=false
     — name: Login to DockerHub
       if: GitHub.event_name != 'pull_request'
       uses: docker/login-action@v1
       with:
         username: ${{ secrets.DOCKERHUB_USERNAME }}
         password: ${{ secrets.DOCKERHUB_TOKEN }}
     — name: Build and push
       uses: docker/build-push-action@v2
       with:
         context: .
         push: ${{ GitHub.event_name != 'pull_request' }}
         tags: ${{ steps.meta.outputs.tags }}
         labels: ${{ steps.meta.outputs.labels }}
Enter fullscreen mode Exit fullscreen mode

But it’s not enough because this build will only start if the tests pass, so I have moved the content of this file inside my CI process to another workflow called: build.yml. This is the test suite.

So we need to create a new job that depends on the test job, thanks to the keyword “needs”, like this:

name: Arkitect
on:
  push:
    branches:
      — '*'
    tags:
      — '*'
    pull_request:
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    — uses: actions/checkout@v2
    - name: Install PHP
      run : //stuff to install PHP
    - name: Test
      run: ./bin/phpunit
  publish_docker_images:
    needs: build
    runs-on: ubuntu-latest
    if: GitHub.ref == 'refs/heads/master' || GitHub.event_name == 'release'
    steps:
    — name: Checkout
      uses: actions/checkout@v2
    — name: Docker meta
      id: meta
      uses: crazy-max/ghaction-docker-meta@v2
      with:
        images: phparkitect/phparkitect
        tags: |
          type=raw,value=latest,enable=${{ endsWith(GitHub.ref, ‘master’) }}
          type=ref,event=tag
        flavor: |
          latest=false
    — name: Login to DockerHub
      if: GitHub.event_name != 'pull_request'
      uses: docker/login-action@v1
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}
    — name: Build and push
     uses: docker/build-push-action@v2
     with:
       context: .
       push: ${{ GitHub.event_name != 'pull_request' }}
       tags: ${{ steps.meta.outputs.tags }}
       labels: ${{ steps.meta.outputs.labels }}
Enter fullscreen mode Exit fullscreen mode

In this example, as I said before, I have created a new job that depends on the job named “build”.
In this way, if the job named “build” fails, I don’t create a new docker image, because I want to create it only if the tests pass.

I have used these GitHub Actions:
crazy-max/ghaction-docker-meta@v2: it extracts metadata (tags, labels) for Docker.

docker/build-push-action@v2: it builds and pushes Docker images with Buildx with the full support of the features provided by Moby BuildKit builder toolkit.

A condition that I have added to my docker push job is:

if: GitHub.ref == 'refs/heads/master' || GitHub.event_name == 'release'
Enter fullscreen mode Exit fullscreen mode

Because I want to execute this job only:

  • when there is a push on master
  • when a new tag is created

This is necessary for me because I don’t want to create a new docker image every time a pull request is created.

When there is a push on master the workflow, create a new docker image with the tag “latest”.
When a new tag is released, the workflow creates a new docker image, with the tag equal to the tag of the project and the tag “latest” is recreated.
In this way, I am sure to publish the last version of the docker image every time with new features or bugs fixed.

Github Actions build

In conclusion, using GitHub Actions saves me a lot of time and repetitive work every time I need to publish my docker image for my project.
There are a lot of other processes that can be automated that I would like to explore and try in my projects, so as soon as possible I will publish other articles about this argument.

💖 💪 🙅 🚩
minompi
AlessandroMinoccheri

Posted on May 2, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related