CircleCI’s Docker image building and pushing is a surprisingly flexible process, often leading people to believe it’s just a docker build and docker push behind a circleci orb.
Let’s see it in action. Here’s a .circleci/config.yml snippet that builds and pushes an image to Docker Hub:
version: 2.1
orbs:
docker: "circleci/docker@1.0.0"
jobs:
build-and-push-image:
docker:
- image: cimg/base:2023.07
steps:
- checkout
- setup_remote_docker
- run:
name: Build and Push Docker Image
command: |
docker build -t your-dockerhub-username/your-repo-name:$CIRCLE_SHA1 .
docker push your-dockerhub-username/your-repo-name:$CIRCLE_SHA1
- docker/docker-login:
auth-url: https://index.docker.io/v1/
username-env-var: DOCKERHUB_USERNAME
password-env-var: DOCKERHUB_TOKEN
This pipeline checks out your code, sets up a Docker daemon for use within the CircleCI environment, then directly executes docker build and docker push. The docker/docker-login orb step configures authentication for pushing to Docker Hub. The image is tagged with the Git commit SHA ($CIRCLE_SHA1), ensuring a unique identifier for each build.
The core problem CircleCI’s Docker integration solves is providing a reliable, reproducible, and isolated environment for building container images. Instead of relying on a developer’s local machine, which can have varying configurations and installed software, CircleCI uses a clean, ephemeral environment. This guarantees that your Docker builds are consistent, regardless of who or when the build is triggered.
Internally, the setup_remote_docker step is crucial. It spins up a Docker daemon within your CircleCI job’s execution environment. This allows the subsequent docker commands to interact with this daemon, build images, and run containers as if you were on a local Docker host. The docker build command uses your Dockerfile to create the image, and docker push then transmits this image to a registry like Docker Hub.
The key levers you control are:
Dockerfile: This is the blueprint for your image. Its contents dictate the base image, installed dependencies, copied files, and entrypoint/command.- Image Tagging: How you tag your images (
-tflag indocker build) is critical for versioning and traceability. Using environment variables like$CIRCLE_SHA1(the Git commit SHA),$CIRCLE_TAG(if building from a Git tag), or$CIRCLE_BUILD_NUMprovides robust versioning. - Registry Authentication: Securely pushing to private or public registries requires authentication. The
docker/docker-loginorb (or manualdocker logincommands) handles this, typically using environment variables for credentials. - Base Image Selection: The
imagekey in your job’sdockersection specifies the executor image for the job itself, not the image being built. This executor image should contain the Docker client.cimg/baseis a common choice.
Many users overlook the distinction between the executor image (the environment your job runs in) and the image being built. You can use any image that has the Docker client installed as your executor, not just a generic docker image. This allows you to, for example, run your Docker build steps within an environment that already has specific build tools or SDKs pre-installed, streamlining your build process.
The next concept you’ll likely grapple with is efficiently managing image layers and optimizing build times, often through multi-stage builds and caching.