Automate Docker Image Builds and Push to GitHub Registry Using GitHub Actions ๐Ÿ™

Automate Docker Image Builds and Push to GitHub Registry Using GitHub Actions ๐Ÿ™

We go through how to automate Docker builds and push to GitHub Registry using GitHub Actions.

ยท

8 min read

This write-up is a follow up to the previous article on how to automate Docker image builds and push to Docker Hub using GitHub Actions.

Introduction

This article will be on how to automate Docker builds and push to GitHub Registry. This is a very useful feature for developers who want to build and push Docker images to GitHub Registry. We go through how to automate Docker builds and push to GitHub Registry using GitHub Actions.

Overview

The Problem

We currently have a project that builds and pushes Docker images to Docker Hub. We want to automate the process of building and pushing Docker images to GitHub Registry. This will allow the image to available for use in other CI/CD pipelines or testing.

The Solution

We will use GitHub Actions to automate the process of building and pushing Docker images to GitHub Registry.

We will use the same Dockerfile and Docker Compose file from the previous article. We will also use the same GitHub repository.

The Steps are as follows

  1. Create a GitHub repository.
  2. Connect you local repository to the GitHub repository.
  3. Push your code including the Dockerfile and Docker Compose file to the GitHub repository.
  4. Create a .github folder in the root of your project.
  5. Inside the .github folder, create a workflows folder.
  6. Create a docker-publish.yml file inside the workflows folder.
  7. Add the workflow code.
  8. Commit and push the changes to the GitHub repository.
  9. Watch as the workflow runs and builds and pushes the Docker image to GitHub Registry.

Alternatively,if you already have an existing project with a Dockerfile and/or Docker Compose file, you can skip steps 1-3 and start from step 4. Or fork/clone my repository.

Prerequisites

  • Git installed on your machine.
  • GitHub Account.
  • Docker installed on your machine.
  • Docker Compose installed on your machine. (optional if you intend to build multiple containers)
  • A GitHub repository with a Dockerfile and/or Docker Compose file.

Enough Talk, Let's Get Started

Since we already have a project with a Dockerfile and Docker Compose file, we will skip steps 1-3 and start from step 4.

Step 4: Create a .github folder in the root of your project

Here is the folder structure of the current project.

Folder Structure

Ensure you are in the root of your project and create a .github folder.

mkdir .github

Step 5: Inside the .github folder, create a workflows folder

cd .github
mkdir workflows

Step 6: Create a docker-publish.yml file inside the workflows folder

cd workflows
touch docker-publish.yml

Step 7: Add the workflow code


name: Docker Image Publish

on:
  push:
    branches: [ "main" ]
    # Publish semver tags as releases.
    tags: [ 'v*.*.*' ]
  pull_request:
    branches: [ "main" ]

env:
  # Use docker.io for Docker Hub if empty
  REGISTRY: ghcr.io
  # github.repository as <account>/<repo>
  IMAGE_NAME: ${{ github.repository }}


jobs:
  build:

    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      # This is used to complete the identity challenge
      # with sigstore/fulcio when running outside of PRs.
      id-token: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      # Install the cosign tool except on PR
      # https://github.com/sigstore/cosign-installer
      - name: Install cosign
        if: github.event_name != 'pull_request'
        uses: sigstore/cosign-installer@f3c664df7af409cb4873aa5068053ba9d61a57b6 #v2.6.0
        with:
          cosign-release: 'v1.11.0'


      # Workaround: https://github.com/docker/build-push-action/issues/461
      - name: Setup Docker buildx
        uses: docker/setup-buildx-action@v2

      # Login against a Docker registry except on PR
      # https://github.com/docker/login-action
      - name: Log into registry ${{ env.REGISTRY }}
        if: github.event_name != 'pull_request'
        uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Extract metadata (tags, labels) for Docker
      # https://github.com/docker/metadata-action
      - name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      # Build and push Docker image with Buildx (don't push on PR)
      # https://github.com/docker/build-push-action
      - name: Build and push Docker image
        id: build-and-push
        uses: docker/build-push-action@v4
        with:
          context: "{{defaultContext}}:src"
          push: ${{ github.event_name != 'pull_request' }} # Don't push on PR
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Lets break down the code.

name: Docker Image Publish

on:
  push:
    branches: [ "main" ]
    # Publish semver tags as releases.
    tags: [ 'v*.*.*' ]
  pull_request:
    branches: [ "main" ]

The above code specifies when the workflow should run. In this case, the workflow will run when a push is made to the main branch or when a tag is pushed to the repository. name: Docker Image Publish

env:
  # Use docker.io for Docker Hub if empty
  REGISTRY: ghcr.io
  # github.repository as <account>/<repo>
  IMAGE_NAME: ${{ github.repository }}

The above code specifies the registry to use and the image name. In this case, we are using GitHub Registry and the image name is the name of the repository.


jobs:
  build:

    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      # This is used to complete the identity challenge
      # with sigstore/fulcio when running outside of PRs.
      id-token: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      # Install the cosign tool except on PR
      # https://github.com/sigstore/cosign-installer
      - name: Install cosign
        if: github.event_name != 'pull_request'
        uses: sigstore/cosign-installer@f3c664df7af409cb4873aa5068053ba9d61a57b6 #v2.6.0
        with:
          cosign-release: 'v1.11.0'

The above code specifies the job to run. In this case, we are running a job called build. The job will run on an Ubuntu machine and will have the following permissions:

  • Read access to the repository contents
  • package write access
  • id-token write access

The job will have the following steps:

  • Checkout the repository
  • Install the cosign tool

The cosign tool is used to sign the image before pushing it to the registry.

# Workaround:

- name: Setup Docker buildx
  uses: docker/setup-buildx-action@v2

The above code sets up the Docker buildx action. Buildx is a Docker CLI plugin that extends the docker command with the full support of the features provided by Moby BuildKit builder toolkit.

# Login against a Docker registry except on PR

- name: Log into registry ${{ env.REGISTRY }}
  if: github.event_name != 'pull_request'
  uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
  with:
    registry: ${{ env.REGISTRY }}
    username: ${{ github.actor }}
    password: ${{ secrets.GITHUB_TOKEN }}

The above code logs into the registry. In this case, we are logging into GitHub Registry. The username and password are the GitHub username and the GitHub token respectively. Ensure you have the secrets.GITHUB_TOKEN secret in your repository or configure a personal access token.

The if condition ensures that the step is not run on a pull request. This ensures we only run the step on a push to the main branch.

# Extract metadata (tags, labels) for Docker

- name: Extract Docker metadata
  id: meta
  uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
  with:
    images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

The above code extracts the metadata for the Docker image. The metadata includes the tags and labels for the image.

# Build and push Docker image with Buildx (don't push on PR)

- name: Build and push Docker image
  id: build-and-push
  uses: docker/build-push-action@v4
  with:
  context: "{{defaultContext}}:src"
  push: ${{ github.event_name != 'pull_request' }} # Don't push on PR
  tags: ${{ steps.meta.outputs.tags }}
  labels: ${{ steps.meta.outputs.labels }}
  cache-from: type=gha
  cache-to: type=gha,mode=max

The above code builds and pushes the Docker image to the registry. The push condition ensures that the image is only pushed to the registry on a push to the main branch.

# Sign the image with cosign

- name: Sign the image with cosign
  if: github.event_name != 'pull_request'
  run: |
  cosign sign --key cosign.key ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.tags }}

Step 8: Commit and push the changes

Commit the changes locally and push to your dev branch first. This allows you to test the workflow before merging to the main branch. It also provides a way to revert the changes if something goes wrong.

git branch dev
git checkout dev 
git add .
git commit -m "Add Docker workflow"
git push

Example of successful workflow run:

Successful run on dev

Step 9: Workflow Run and Image on GitHub Registry

Create a pull request to merge the dev branch to the main branch. Once the pull request is merged, the workflow will run and push the image to the registry. Example of successful run on main.

Successful run on main

If everything runs successfully, your image should be on GitHub Container Registry, and you should see a link to the image in the repo as shown below:

Main Repo

Conclusion

In this tutorial, we have learned how to create a Docker workflow to build and push a Docker image to GitHub Container Registry. We have also learned how to use the docker/metadata-action action to extract the metadata for the image.

We have also learned how to use the docker/build-push-action action to build and push the image to the registry. This should serve as a starting point for you to build your own Docker workflow. Curious to see what you Build!

GitHub Repository

The GitHub repository for this tutorial is here. The repository contains the code for the Fast-API application and the Vue application. The repository also contains the Docker workflow. Feel free to fork the repository and play around with the code. Link to the image is here.

Image on main

Thanks for reading! If you have any questions, feel free to leave a comment below.

Next time gif

References

About the Author

Ken Mwaura is a Freelance Back-end Software Engineer He is passionate about building scalable and maintainable software. He is also passionate about learning new technologies and sharing his knowledge with others. You can find him on Twitter and LinkedIn.

ย