Automate Docker Image Builds and Push to Docker Hub Using GitHub Actions 🐳🐙
Tags: docker, githubactions, tutorial, automation
Published: 2023-03-01 16:47:06

New month new blog post.... 🚀🚀
## Introduction

In this tutorial, we will be building a Docker image of an existing project and pushing it to Docker Hub using GitHub Actions. We will be using the following technologies:

- [Docker](https://www.docker.com/)
- Git and GitHub (obviously)
- [GitHub Actions](https://docs.github.com/en/actions)
- [Docker Hub](https://hub.docker.com/)
  
## Overview

### The Problem ☢️

We want to build a Docker image and push it to Docker Hub whenever we push to the `main` branch. We also want to build a Docker image and push it to GitHub Packages whenever we push to the `dev` branch. We want to do this automatically using GitHub Actions. We want to be able to do this without exposing our Docker Hub credentials.

### The Solution ⚡

We will use GitHub Actions to build and push Docker images to Docker Hub. We will use the following steps:

1. Create a workflow file.
2. Add a job to build and push the Docker image to Docker Hub.
3. Commit and push the changes to the `dev` branch.
4. Specify our docker crenentials as secrets in the repository settings.
5. Open a pull request to merge the `dev` branch into the `main` branch.
6. Watch the workflow run and verify that the Docker image is pushed to Docker Hub.

## Prerequisites 📃

- A GitHub account.
- A Docker Hub account.
- Docker installed on your machine. You can follow the [official documentation](https://docs.docker.com/get-docker/) to install Docker on your machine.
- A GitHub repository with a Dockerfile and a project that you want to build and push to Docker Hub.

## Step 1: Create a workflow file 🏊

All GitHub Actions workflows are defined in YAML files in the `.github/workflows` directory of your repository.

We'll stary by creating a workflow file for building and pushing the Docker image to Docker Hub.

Create a file named `.github/workflows/docker-hub.yml` in your repository. This file will contain the workflow that we will use to build and push the Docker image. The workflow will be triggered whenever we push to the `main` or `dev` branch. We will also specify the Docker image name and tag. We will use the following workflow:

```yaml
name: Build and Push Docker Image to Docker Hub

on:
  push:
    branches: [ "dev", "main" ]
  pull_request:
    branches: [ "dev", "main" ]

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

jobs:

  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - name: Build the Docker image
      run: docker-compose build --no-cache --force-rm 
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Test the Docker image
        run: docker-compose up -d 
  push_to_registry:
    name: Push Docker image to Docker Hub
    runs-on: ubuntu-latest
    steps:
      - name: Check out the repo
        uses: actions/checkout@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      
      - name: Log in to Docker Hub
        uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      
      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
      
      - name: Build and push Docker image
        uses: docker/build-push-action@v2
        with:
          context: "{{defaultContext}}"
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

```

Lets break down the workflow file:

- `name`: The name of the workflow.
- `on`: The events that trigger the workflow. We will trigger the workflow whenever we push to the `main` or `dev` branch.
- `env`: The environment variables that we will use in the workflow. We will use the following environment variables:
  - `REGISTRY`: The registry that we will use to push the Docker image. We will use `docker.io` for Docker Hub.
  - `IMAGE_NAME`: The name of the Docker image. We will use the name of the repository as the name of the Docker image.

- `jobs`: The jobs that will run in the workflow. We will use the following jobs:
  - `build`: The job that will build the Docker image. We will use the following steps:
    - `Checkout`: We will use the [actions/checkout](https://github.com/actions/checkout) action to checkout the repository.
  - `Build the Docker image`: We will use the `docker-compose build` command to build the Docker image. We will use the `--no-cache` and `--force-rm` flags to ensure that we are building the Docker image from scratch. This will ensure that we are building the Docker image with the latest changes before pushing it to Docker Hub.

- `test`: The job that will test the Docker image. We will use the following steps:
  - `Checkout`: We will use the [actions/checkout](https://github.com/actions/checkout) action to checkout the repository.
  - `Test the Docker image`: We will use the `docker-compose up -d` command to test the Docker image. We will use the `-d` flag to run the Docker image in the background.
  
- `push_to_registry`: The job that will push the Docker image to Docker Hub. We will use the following steps:
  - `Check out the repo`: We will use the [actions/checkout](https://github.com/actions/checkout) action to checkout the repository.
  - `Set up Docker Buildx`: We will use the [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) action to set up Docker Buildx.
  - `Log in to Docker Hub`: We will use the [docker/login-action](https://github.com/docker/login-action) action to log in to Docker Hub. We will use the following inputs:
    - `username`: The username of the Docker Hub account. We will use the `DOCKER_USERNAME` secret.
    - `password`: The password of the Docker Hub account. We will use the `DOCKER_PASSWORD` secret.
  - `Extract metadata (tags, labels) for Docker`: We will use the [docker/metadata-action](https://github.com/docker/metadata-action) action to extract the metadata for the Docker image. We will use the following inputs:
  - `images`: The name of the Docker image. We will use the `REGISTRY` and `IMAGE_NAME` environment variables.
- `Build and push Docker image`: We will use the [docker/build-push-action](https://github.com/docker/build-push-action) action to build and push the Docker image to Docker Hub. We will use the following inputs:
  - `context`: The context of the Docker image. We will use the `defaultContext` variable.
  - `push`: A boolean value that specifies whether to push the Docker image to Docker Hub. We will set this to `true`.
  - `tags`: The tags of the Docker image. We will use the `tags` output of the `docker/metadata-action` action.
  - `labels`: The labels of the Docker image. We will use the `labels` output of the `docker/metadata-action` action.

## Step 2: Create a `dev` branch and commit the changes to the `dev` branch 🌿

Create a `dev` branch and commit the changes to the `dev` branch. This will allow us to test the workflow before merging the changes to the `main` branch. We will use the following commands:

```bash
git checkout -b dev
git add .
git commit -m "Add workflow to build and push Docker image to Docker Hub"
```

## Step 3: Push the changes to the `dev` branch 🧑‍💻

Create a Github Repository if you dont already have one and push the changes to the `dev` branch. We will use the following commands:

```bash
git add .
git commit -m "Add workflow to build and push Docker image to Docker Hub"
git push -u origin dev
```

## Step 4: Specify our docker credentials as secrets in the repository settings 🔐

We will specify our Docker Hub credentials as secrets in the repository settings. We will use the following secrets:

- `DOCKER_USERNAME`: The username of our Docker Hub account.
- `DOCKER_PASSWORD`: The password of our Docker Hub account.
  
This will allow us to use the credentials in the workflow file without exposing them. Thus keeping sensitive information secure. This gives the workflow access to the Docker Hub account. As well ability to use the workflow across multiple repositories.

## Step 5: Open a pull request to merge the `dev` branch into the `main` branch 🛣️

Once the workflow successfully runs on the `dev` branch, we can merge the changes to the `main` branch. Open a pull request to merge the `dev` branch into the `main` branch. This will trigger the workflow. The workflow will run on the `main` branch and push the Docker image to Docker Hub. Example of successful workflow run on open pull request:

![Successful workflow run on open pull request](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yqlfzkejl5egjkxsbnyq.png)

## Step 6: Watch the workflow run 📺

Watch the workflow run and verify that the Docker image is pushed to Docker Hub. Login to Docker Hub and check if the image is successfully pushed to Docker Hub. The image should be pushed to Docker Hub with the appropriate tags. for example `kenmwaura1/fastapi-vue:main` and `kenmwaura1/fastapi-vue:sha-<sha>`. Example of successful workflow run on merge pull request:

![Successful workflow run on merge pull request](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5phf58qglar4pfzg7z65.png)

Screenshot of workflow running on merge pull request:

![Workflow running on merge pull request](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y6q5bz5omip10rjpn21j.png)

## Step 7: Verify that the Docker image is pushed to Docker Hub 🐋

Login to Docker Hub and check if the image is successfully pushed to Docker Hub. The image should be pushed to Docker Hub with the appropriate tags. for example `kenmwaura1/fastapi-vue:main` and `kenmwaura1/fastapi-vue:sha-<sha>`. Example of image pushed to Docker Hub:

![Image pushed to Docker Hub](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jc54bbxep5o184yhjn93.png)

## GitHub Repo for this tutorial 🐙

Find the sample code for this workflow in the following GitHub repository: {% embed https://github.com/KenMwaura1/Fast-Api-Vue %}

Find the docker image on Docker Hub: [Fastapi-Vue](https://hub.docker.com/repository/docker/kenmwaura1/fast-api-vue/general)

## Conclusion 🫔

In this tutorial, we learned how to build and push Docker images to Docker Hub and GitHub Packages using GitHub Actions. We also learned how to specify our Docker Hub credentials as secrets in the repository settings.

### Next Steps

On the next tutorial, we will learn how to build and push Docker images to GitHub Packages using GitHub Actions. Stay tuned!

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

## References 📖

- [GitHub Actions](https://docs.github.com/en/actions)
- [GitHub Actions: Docker](https://docs.github.com/en/actions/guides/building-and-testing-docker-images)
- [GitHub Actions: Docker metadata](https://docs.github.com/en/actions/guides/building-and-testing-docker-images#creating-and-publishing-docker-images)
- [GitHub Actions: Docker login](https://docs.github.com/en/actions/guides/publishing-docker-images#publishing-images-to-docker-hub)
- [GitHub Actions: Docker build and push](https://docs.github.com/en/actions/guides/publishing-docker-images#publishing-images-to-docker-hub)
- [GitHub Actions: Docker buildx](https://docs.github.com/en/actions/guides/publishing-docker-images#publishing-images-to-docker-hub)
- [Docker Hub](https://hub.docker.com/)