In CircleCI, developers can freely combine images like building blocks to create containerized continuous integration (CI) environments. CircleCI supports Docker natively, allowing you to build, test, and deploy your application as a Docker image.

In this post, you will learn how to build a Docker image in CircleCI and push the image to Docker Hub, Docker’s official cloud-based registry for Docker images.

Prerequisites

Setting up the pipeline configuration

In your project’s root directory, create a config.yml in a directory called .circleci:

version: 2.1
jobs:
  build:
    working_directory: ~/app
    docker:
      - image: cimg/base:2022.09
        auth:
          username: $DOCKERHUB_USERNAME
          password: $DOCKERHUB_PASSWORD
    steps:
      - checkout
      - setup_remote_docker:
          docker_layer_caching: true
      - run:
          name: Run tests
          command: |
            docker-compose -f ./docker-compose.test.yml up
      - run:
          name: Build Docker image
          command: |
            TAG=0.1.$CIRCLE_BUILD_NUM
            docker build -t $DOCKERHUB_USERNAME/circleci-new-docker-example:$TAG .
      - run:
          name: Push application Docker image
          command: |
            TAG=0.1.$CIRCLE_BUILD_NUM
            echo $DOCKERHUB_PASSWORD | docker login -u $DOCKERHUB_USERNAME --password-stdin
            docker push $DOCKERHUB_USERNAME/circleci-new-docker-example:$TAG

Depending on the project, some of the details may change. If you want to try these steps for yourself, I prepared a sample project that you can use this config file with.

The project is written in Node.js using Express and simply returns “Hello World”, tested with Jest and supertest.

In the following sections, we will explore how the CircleCI configuration works to build, test, and publish a Docker image of the sample project.

Exploring the configuration structure

version: 2.1
jobs:
  build:
    docker: ...
    steps: ...

In CircleCI, you can define the steps of each job, including repository checkout and cache-related processing. This gives you great freedom, not only for defining the CI container environment but also for the automated steps in your CI/CD pipeline. You can find all the details in the official documentation.

Using the Docker executor

docker:
  - image: cimg/base:2022.09

In this section, you define a cimg/base Docker image as your execution environment. You can use this pre-built convenience image to set up the CI build environment, Ubuntu in our case. This image is also very useful as a base for any custom Docker images.

Your goal is to have an execution environment with Docker and Git installed, both of which are included in cimg/base. As stated in the official documentation, this image contains all you need to run most builds, including Git, Docker, Docker Compose, and much more.

Accessing the Docker image registry

docker:
  - image: ...
    auth:
      username: $DOCKERHUB_USERNAME
      password: $DOCKERHUB_PASSWORD

The auth field is used to specify the credentials needed to access the Docker Hub image registry. These credentials are pulled in as environment variables, which you can set either at the project level or in an organization-wide context. To learn more, read the documentation on setting an environment variable.

Checkout

steps:
  - checkout

The first step, checkout, is a special step to check out the source code of your application. This will be downloaded to the working directory in CircleCI.

Set up remote Docker and Docker layer caching

- setup_remote_docker:
    docker_layer_caching: true

This step helps you avoid the Docker-in-Docker problem. In fact, you’re setting up an environment that is isolated from the CI or primary container environment, then using the remote host’s Docker Engine.

The setup_remote_docker key configures your remote Docker environment. By setting up a remote Docker environment, you can run Docker commands, such as building and pushing Docker images, within your job.

The docker_layer_caching option for your remote environment is set to true, which enables Docker layer caching. With Docker layer caching enabled, CircleCI can reuse layers from previous builds if they haven’t changed. This significantly reduces build times, as unchanged layers do not need to be rebuilt.

Docker layer caching is included in every CircleCI plan, including the free plan. You can refer to the sample project’s build history and compare this workflow where Docker layer caching was enabled with the duration here without Docker layer caching. There we noticed an improvement of more than 30 seconds in the jobs with DLC enabled.

Running tests

- run:
    name: Run tests
    command: |
      docker-compose -f ./docker-compose.test.yml up

For this project I am running the tests with Docker Compose, and the tests are run only in the application container. If you need a database container, it is easy to set one up using Docker Compose.

Pushing the Docker image

- run:
    name: Build and Push application Docker image
    command: |
      TAG=0.1.$CIRCLE_BUILD_NUM
      docker build -t $DOCKERHUB_USERNAME/circleci-docker-example:$TAG .
      echo $DOCKERHUB_PASSWORD | docker login -u $DOCKERHUB_USERNAME --password-stdin
      docker push $DOCKERHUB_USERNAME/circleci-docker-example:$TAG

This is where the application’s Docker image is built and pushed to Docker Hub. In a chronological order, it:

  • Creates a Docker image tag using the built-in environment variable CIRCLE_BUILD_NUM, which provides a unique build number for each run
  • Builds a Docker image from the Dockerfile in the current directory (.)
  • Tags the image with the Docker Hub username and the generated tag
  • Reads a password from the CircleCI environment variable and passes it to the docker login command for authentication purposes.
  • Pushes the image to Docker Hub

Conclusion

CircleCI provides two ways to build a Docker image: using a dedicated virtual machine with the machine executor or using the remote Docker engine with a Docker executor. In this post, you learned how to build your Docker images in the Docker execution environment.

By automating the build, test, and deploy workflow for your Docker images with CircleCI, you can significantly speed up build times and ensure consistent, reliable deployments. To get started, sign up for a free CircleCI account today.

Start Building for Free


Oluyemi is a tech enthusiast with a background in Telecommunication Engineering. With a keen interest in solving day-to-day problems encountered by users, he ventured into programming and has since directed his problem solving skills at building software for both web and mobile. A full stack software engineer with a passion for sharing knowledge, Oluyemi has published a good number of technical articles and blog posts on several blogs around the world. Being tech savvy, his hobbies include trying out new programming languages and frameworks.

Read more posts by Olususi Oluyemi