Automate Docker Image Builds and Push to GitHub Registry Using GitHub Actions 🐙
Tags: docker, devops, automation, githubactions
Published: 2023-03-11 09:06:21

This article is a follow to previous [article](https://dev.to/ken_mwaura1/automate-docker-image-builds-and-push-to-docker-hub-using-github-actions-32g5) 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](https://github.com/KenMwaura1/Fast-Api-Vue).

## Prerequisites

- [Git](https://git-scm.com/downloads) installed on your machine.
- [GitHub Account](https:www.github.com).
- [Docker](https://www.docker.com/products/docker-desktop) installed on your machine.
- [Docker Compose](https://docs.docker.com/compose/install/) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cfwp85fgsiven6ahwlg8.png)


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

```bash
mkdir .github
```

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

```bash
cd .github
mkdir workflows
```

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

```bash
cd workflows
touch docker-publish.yml
```

### Step 7: Add the workflow code

```yaml

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.

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

```yaml
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.

```yaml

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.

```yaml
# 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.

```yaml
# 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.

```yaml
# 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.

```yaml
# 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.

```yaml
# 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.

```bash
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7j8mx7fxl52gvk2azwlu.png)

### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w2xnl7niltagodw0hyzp.png)

If everything runs successfully, your image should be on [GitHub Container Registry](https://ghcr.io), and you should see a link to the image in the repo as shown below:

![Main Repo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/elo9lr8mx688xwhab3f5.png)

## 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 {% embed https://github.com/KenMwaura1/Fast-Api-Vue/ %}

. 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](https://github.com/KenMwaura1/Fast-Api-Vue/pkgs/container/fast-api-vue/76507611?tag=main).

![Image on main](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mmpmmj3rgr5sqdun2d7f.png)

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

![Next time gif](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h7589gyc9530l1c0y9vj.gif)


## References

- [GitHub Actions](https://docs.github.com/en/actions)
- [GitHub Container Registry](https://docs.github.com/en/packages/guides/about-github-container-registry)
- [GitHub Workflow](https://docs.github.com/en/actions/learn-github-actions/introduction-to-github-actions)
- [GitHub Workflow Syntax](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions)
- [GitHub Workflow Context](https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions)
- [GitHub Workflow Environment](https://docs.github.com/en/actions/reference/environment-variables)
- [Docker Buildx](https://docs.docker.com/buildx/working-with-buildx/)

## 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](https://twitter.com/KenMwaura1) and [LinkedIn](https://www.linkedin.com/in/kennedy-mwaura/).

Buy Me a [Coffee](https://ko-fi.com/kenmwaura1)