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
- Create a GitHub repository.
- Connect you local repository to the GitHub repository.
- Push your code including the Dockerfile and Docker Compose file to the GitHub repository.
- Create a
.github
folder in the root of your project. - Inside the
.github
folder, create aworkflows
folder. - Create a
docker-publish.yml
file inside theworkflows
folder. - Add the workflow code.
- Commit and push the changes to the GitHub repository.
- 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.
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 /
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 /
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:
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
.
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:
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
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
This repository contains code for asynchronous example api using the Fast Api framework ,Uvicorn server and Postgres Database to perform crud operations on notes.
Accompanying Article
Installation method 1 (Run application locally)
-
Clone this Repo
git clone (https://github.com/KenMwaura1/Fast-Api-example)
-
Cd into the Fast-Api folder
cd Fast-Api-example
-
Create a virtual environment
python3 -m venv venv
-
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
-
Cd into the src folder
cd src
-
Install the required packages
python -m pip install -r requirements.txt
-
Start the app
python main.py
7b. Start the app using Uvicorn
uvicorn app.main:app --reload --workers 1 --host 0.0.0.0 --port 8002
-
Ensure you have a Postgres Database running locally
Additionally create afast_api_dev
database with user**fast_api**
having required privileges
OR
Change the DATABASE_URL variable in the .env file inside thenapp
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.
Thanks for reading! If you have any questions, feel free to leave a comment below.
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.