Create Automated CI/CD Builds Using GitHub Actions and DockerHub

10 minute read     Updated:

Rose Chege %
Rose Chege

We’re Earthly. We make building software simpler and therefore faster. This article is about GitHub Actions, if you’d like to see how Earthly can improve your GitHub Actions builds then check us out.

To streamline your development workflow, in this tutorial, we will explore the power of GitHub Actions in automating CI/CD builds. Specifically, we will focus on leveraging GitHub Actions to automate Docker builds and facilitate seamless deployments to DockerHub. Join us as we construct an automated CI/CD pipeline using GitHub Actions and DockerHub.

Prerequisites

To follow along with this article, it is essential to have the following:

  • Node.js installed on your computer.
  • Docker installed on your computer.
  • GitHub and DockerHub accounts.
  • Basic knowledge of working with Docker.
  • Basic knowledge of how to use GitHub.
  • Familiarity editing YAML files.

Creating the Application and a Dockerfile to Build the Application Image

To get started, you need an application to put into a Docker image. In this guide, you will develop a simple Node.js application with a single endpoint that just print Hello World to the screen. Alternatively, if you have an application you have already developed, you can use it along and achieve the same objective this guide aims for.

The simple Node.js application will be created as follows:

From your preferred working directory, initialize the application:

npm init -y

Install express for setting up the web server:

npm install express

Create an app.js file to host the application logic as follows:

  • Import the necessary packages:
const express = require('express');
  • Define the express instance and the port for the application:
const app = express();
const PORT = process.env.PORT || 4000;
  • Define a default testing route:
app.get('/',(req,res) => {
    res.status(200);
    res.send("Hello World!!");
});
  • Start the application:

app.listen(PORT, () => console.log(`App listening on port ${PORT} `));

The whole code in the apps.js looks as shown below:

const express = require('express');

const app = express();
const PORT = process.env.PORT || 4000;

app.get('/',(req,res) => {
    res.status(200);
    res.send("Hello World!!");
});

app.listen(PORT, () => console.log(`App listening on port ${PORT} `));
  • In the package.json add a script for running the application:
"start":"node app.js"

You can test your application and ensure it works as expected by starting the development server using the following command:

npm run start

From your browser, go to http://localhost:4000 to check if the application works as expected.

A simple Node.js app showing Hello World

To run this application with Docker, you need to provide the correct command for packaging it. To do this, you can use a Dockerfile, which specifies the instructions to build a Docker image. Once the image is built, it contains everything required to run the application, including the code, runtime, libraries, and settings. This results in a Docker container image that can be used to run the application on any Docker-supported platform.

In your application directory, create a file named Dockerfile. In this Dockerfile, add the commands for building the image step by step as follows:

Specify the base image to use, in this case, the node version:

FROM node:19

Define the working directory, where the application will reside inside the Docker:

WORKDIR /usr/src/app

Copy package.json to the working directory:

COPY package*.json .

Run the npm install command to install the application dependencies on Docker:

RUN npm install

Copy the rest of the application files to Docker, i.e., app.js:

COPY . .

Expose the port the application will run on:

EXPOSE 4000

Define the command to run the application. This is the same command that Node.js runs on when creating the application locally:

CMD = ["npm","run", "start"]

Your complete code in the Dockerfile should be as shown below:

FROM node:19
WORKDIR /usr/src/app
COPY package*.json .
RUN npm install
COPY . .
EXPOSE 4000
CMD = ["npm","run", "start"]

If you are using a different application, ensure your Dockerfile reflects the instruction needed to dockerize your application.

Build the Docker image to check if these instructions work correctly on Docker:

docker build . --tag node_app 

And run the application on docker:

docker run -it -p 4000:4000 node_app

The results should remain the same as when you tested the application locally. Otherwise, recheck the above steps and ensure that you entered the code correctly.

Creating a Github Repository and Pushing the Application to Github

GitHub actions require you to host your application code on a remote repository, including the Dockerfile containing the Docker commands. A GitHub repository stores your code and related files.

Create a GitHub repository to add all your code and add you project so far to it.

If you’re not familiar with Git then I recommend using a Git client with a graphical user interface (GUI), such as GitHub Desktop to make working with Git repositories on GitHub easier. Here’s a brief overview of how to use GitHub Desktop to push code changes to your remote GitHub repository:

  • Open GitHub Desktop and log in using your GitHub account
  • Select the repository you have created on your GitHub page and open it with GithHub Desktop:
Open repository with GitHub Desktop
  • Clone the repository:
Clone a GitHub repository
  • Add your application code in the local cloned repository
  • Review the changes on GitHub Desktop, add a commit message summarizing your changes and click the Commit button:
Adding files using GitHub Desktop
  • Click the Publish button to push your code to the remote GitHub repository:
Pushing changes to GitHub

Setting Up DockerHub

The GitHub action that you will set up will push the application image to the DockerHub repository. Therefore, you must have the correct access token for GitHub Actions to access your DockerHub account. In the DockerHub account portal, you will create an access key for GitHub to connect to your DockerHub account. You can create a new access key as follows:

Navigate to your account setting, security and click on the New Access Token:

Create a new DockerHub access token

Specify the access token description and permission:

Generate a new DockerHub access token

Copy the access token:

Copy DockerHub access token

Once the key is ready, it should be copied immediately and saved securely because it will be provided only once and cannot be retrieved, and it will not be stored on DockerHub. The key will be required for the upcoming steps.

Creating a GitHub Actions Workflow

With all the application code available remotely, you can create a GitHub Actions workflow that will automate the build process and save the build artifacts to DockerHub.

Connecting GitHub Actions With DockerHub

To set up a workflow, GitHub must communicate with DockerHub using the DockerHub access token (that you just created) and your DockerHub username. Your DockerHub access token is a confidential information, and you need to add it as a secret environment variable on GitHub. The workflows will execute this access token and username as secret environment variables on GitHub.

To add the secret environment variables, navigate to the repository you created on Github

In your repository, navigate to Settings, Secrets and Variables, and Actions sections as follows:

Creating GitHub Actions environment variables

Click on the New repository secret button:

Adding GitHub Actions environment variables

Create a DOCKERHUB_USERNAME variable and add your DockeHub username as the value:

New GitHub Actions environment variables

Create a DOCKERHUB_TOKEN variable and add the access token you created earlier as the value. You should have the following results:

Docker access token and username as GitHub secret environment variables

Implementing the GitHub Actions Workflow

GitHub Actions workflow includes creating the processes that trigger events, jobs, and steps that set up a workflow. The docker image will be built and deployed to the DockerHub when you push the code to the main branch of the GitHub repository. Therefore, the GitHub Actions workflow needs to initiate a push event on the main branch of the Github repository you hosted your application code.

To setup the workflow, you need to create the workflow file. You can create one as shown below:

Click on the Actions` tab in your repository and click the set up a workflow yourself:

Creating a GitHub Action workflow

On the resulting editor, add the following workflow to the main.yml file as follows:

Define the build trigger:

name: node_app

on: # specify the build to trigger the automated ci/cd
    push:
        branches:
            - "main"

GitHub Actions will name this configuration node_app. The code specifies the trigger as a push event (changes) to your application code’s main branch on the GitHub repository you created.

Define the job that indicates the steps to checkout the code and build the Docker images:

jobs:
    build:
        name: Build Docker image
        runs-on: ubuntu-latest # specify the build machine
        steps:

GitHub Actions uses jobs to define one or more tasks that your CI/CD pipeline will run. In the code above, the build specifies the steps and it also specifies the machine type that the steps will run on using the runs-on keyword. The value of ubuntu-latest indicates an ubuntu machine as the pipeline environment. The steps will define a list of stages to be executed in sequence to fulfill the pipeline objectives. This steps are as follows:

Define the steps that will checkout the code:

- # checkout to the repository on the build machine
    name: Checkout
    uses: actions/checkout@v3

The uses: actions/checkout@v3 syntax clones your Github repository to the ubuntu-latest build machine. This will make the code available to the subsequent steps in executing the job.

Define the step to sign in to DockerHub with the credentials in the GitHub Action environment variable secrets:

- # login to Docker Hub using the secrets provided
    name: Login to Docker Hub
    uses: docker/login-action@v2
    with:
        username: $
        password: $

The uses: docker/login-action@v2 syntax allows GitHub Action to log in to your DockerHub registry based on the DOCKERHUB_USERNAME and DOCKERHUB_TOKEN you added as GitHub Actions environment variables.

Define the step that setup the Docker Buildx:

- # create a build kit builder instance
    name: Set up Docker Buildx
    uses: docker/setup-buildx-action@v2

The uses: docker/setup-buildx-action@v2 syntax creates a Docker Buildx builder instance. It uses a docker buildx command that builds Docker images to your desired architectures.

Define the step that build and push the docker image to DockerHub. The workflow will build the image based on the Dockerfile commands and tag the images. It will finally push and deploy the built image to your DockerHub as follows:

- # build the container image and push it to Docker Hub with \
 # the name clockbox.
    name: Build and push
    uses: docker/build-push-action@v4
    with:
        context: .
        file: ./Dockerfile
        push: true
        tags: $/clockbox:latest

The uses: docker/build-push-action@v4 syntax will execute your Dockerfile, push the resulting image to your DockerHub registry, and tag it as $/clockbox:latest.

The whole workflow code is as shown below:

name: node_app

on: # specify the build to trigger the automated ci/cd
    push:
        branches:
            - "main"

jobs:
    build:
        name: Build Docker image
        runs-on: ubuntu-latest # specify the build machine
        steps:
            - # checkout to the repository on the build machine
                name: Checkout
                uses: actions/checkout@v3
            - # login to Docker Hub using the secrets provided
                name: Login to Docker Hub
                uses: docker/login-action@v2
                with:
                  username: $
                  password: $
            - # create a build kit builder instance
                name: Set up Docker Buildx
                uses: docker/setup-buildx-action@v2
            - # build the container image and push it to Docker \
                # Hub with the name clockbox.
                name: Build and push
                uses: docker/build-push-action@v4
                with:
                  context: .
                  file: ./Dockerfile
                  push: true
                  tags: $/clockbox:latest

Testing the Builds with GitHub Actions

To deploy the workflow, click on Start commit, add a commit message, and submit:

The start commit button

Navigate to the Actions tab to view the job and the build status.

Checking the workflow

After committing from the previous step, the build should start automatically:

The GitHub Action build image stage

Once The GitHub workflow executes all the steps, the build will be completed as follows:

The build steps and status

From your DockerHub dashboard, you should be able to view the image that has just been deployed:

The Docker Image on DockerHub

Conclusion

GitHub Actions and DockerHub integration streamline your development process. Automating your builds and deployments pipelines creates team efficiency while reducing errors and increasing productivity.

This article has shown you how to create an automated CI/CD build with GitHub and DockerHub. In this article, you have learned:

  • How to dockerize an application with Docker
  • How to create and deploy an application from GitHub Actions
  • How to automatically build and push a Docker image DockerHub with GitHub action.

The code used in this tutorial can be found in this GitHub repository. And if you’re looking to continue building out your GHA workflows, consider using Earthly. Earthly runs everywhere, including GitHub Actions and can improve the reliability of your CI/CD pipelines. It works great with GitHub Actions and docker.

Earthly + GitHub Actions
GitHub Actions are better with Earthly. Get faster build speeds, improved consistency, and local testing along with an easy-to-use syntax – no YAML – and better monorepo support.

Go to our homepage to learn more

Rose Chege %
Rose Chege

Rose is a lover of technology and an upright individual who is not afraid to get out of her comfort zone and try out new programming paradigms.

Writers at Earthly work closely with our talented editors to help them create high quality tutorials. This article was edited by:
Mustapha Ahmad Ayodeji
Mustapha Ahmad Ayodeji

Ahmad is a Software developer and a Technical writer with so much interest for Django related frameworks.

Updated:

Published:

Get notified about new articles!
We won't send you spam. Unsubscribe at any time.