Push and publish Docker images with GitHub Actions
Tags: build, docker, github, automation
Published: 2021-05-02 18:56:06

---
title: Push and publish Docker images with GitHub Actions
published: true
description: how to push and publish your docker images with Github Actions
tags: build, docker, github, automation
cover_image: https://miro.medium.com/max/4800/1*_4Ex1uUhL93a3bHyC-TgPg.png
canonical_url: https://minompi.medium.com/push-and-publish-docker-images-with-github-actions-1650dd965ba4
---

In many articles, I mentioned many times about using [**GitHub Actions**](https://github.com/features/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 .
```

**Publishing to DockerHub**
```
docker push organization/project:0.1.0
```

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](https://github.com/phparkitect/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](https://miro.medium.com/max/1400/1*_4Ex1uUhL93a3bHyC-TgPg.png)

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 }}
```

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 }}
```

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](https://github.com/crazy-max/ghaction-docker-meta): it extracts metadata (tags, labels) for Docker.

[docker/build-push-action@v2](https://github.com/docker/build-push-action): it builds and pushes Docker images with [Buildx](https://github.com/docker/buildx) with the full support of the features provided by [Moby BuildKit](https://github.com/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'
```

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](https://miro.medium.com/max/1400/1*JzKdcgMYHRJJG97hN-0FnQ.png)

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.