DEV Community

Cover image for Automate Docker Image Builds and Push to GitHub Registry Using GitHub Actions 🐙
Zoo Codes
Zoo Codes

Posted on • Updated on

Automate Docker Image Builds and Push to GitHub Registry Using GitHub Actions 🐙

This article is a follow to 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


Enter fullscreen mode Exit fullscreen mode

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



cd .github
mkdir workflows


Enter fullscreen mode Exit fullscreen mode

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



cd workflows
touch docker-publish.yml


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

Lets break down the code.



name: Docker Image Publish

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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'


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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

GitHub logo KenMwaura1 / Fast-Api-Vue

Simple asynchronous API implemented with Fast-Api framework utilizing Postgres as a Database and SqlAlchemy as ORM . GiHub Actions as CI/CD Pipeline. Vue + Daisy UI for the frontend

FastAPI Vue Starter App

fastapi-0.92.0-informational CodeQL Docker Compose Actions Workflow Build and Push Docker Image to Docker Hub Docker Image Publish

Twitter

This repository contains code for asynchronous example api using the Fast Api framework ,Uvicorn server and Postgres Database to perform crud operations on notes.

Fast-api

Accompanying Article

Installation method 1 (Run application locally)

  1. Clone this Repo

    git clone (https://github.com/KenMwaura1/Fast-Api-example)

  2. Cd into the Fast-Api folder

    cd Fast-Api-example

  3. Create a virtual environment

    python3 -m venv venv

  4. Activate virtualenv

    source venv/bin/activate

    For zsh users

    source venv/bin/activate.zsh

    For bash users

    source venv/bin/activate.bash

    For fish users

    source venv/bin/activate.fish

  5. Cd into the src folder

    cd src

  6. Install the required packages

    python -m pip install -r requirements.txt

  7. Start the app

    python main.py
    Enter fullscreen mode Exit fullscreen mode

    7b. Start the app using Uvicorn

    uvicorn app.main:app --reload --workers 1 --host 0.0.0.0 --port 8002
    Enter fullscreen mode Exit fullscreen mode
  8. Ensure you have a Postgres Database running locally Additionally create a fast_api_dev database with user **fast_api** having required privileges. OR Change the DATABASE_URL variable in the .env file inside then app folder to reflect database settings (user:password/db)

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

Buy Me a Coffee

Top comments (2)

Collapse
 
hartley94 profile image
Martin Thuo

Great share 🔥

Collapse
 
magnus_gaarder_cebba4424b profile image
Magnus Gaarder

Thank you. How does the workflow know where the Dockerfile is stored, when building?