Multi-Stage Docker Builds on CircleCI 2.0
Community Engineer, CircleCI
Note from the publisher: You have managed to find some of our old content and it may be outdated and/or incorrect. Try searching in our docs or on the blog for current information.
Docker introduced multi-stage builds in May of 2017. In simplest terms, these are Dockerfiles with more than one FROM
statement. With a small tweak, you can build multi-stage Dockerfiles on CircleCI 2.0.
Check outGuide to using Docker for your CI/CD pipelines
What Are Multi-Stage Docker Builds?
Docker’s new multi-stage builds allow Dockerfiles to become much more powerful, offering complex builds with a single Dockerfile. An example use case might be when you would normally have one Dockerfile for building the source of your application and then another where you would run and test it. Here’s an example Dockerfile
to solve this problem with multi-stage builds, borrowed from docs.docker.com:
FROM golang:1.8.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]
The first “stage” of this Dockerfile builds the Go application using the official Go image from the Docker Library. The second stage then copies the newly built binary from the first stage (with COPY --from=0
) and places it in a custom Alpine image. The result is an Alpine-based Docker image that anyone can use to run this Go app, without any of the extra software that was used to build the app in the first place, such as the Go toolchain.
Naming Stages
To copy the binary we built to the second stage, we used COPY --from=0
since we didn’t name the stage, so instead we used its index number. Instead, we can name a stage within the FROM
statement and then use that name later. So we could start with FROM golang:1.8.3 as compile-stage
and the in the COPY
statement we would use COPY --from=compile-stage
.
Building on CircleCI 2.0
The Dockerfile example above can be built with CircleCI 2.0 however a config change needs to be made depending on which executor
you’re using. This is because the version of Docker being used by default (as of this writing) on CircleCI is Docker 17.03.0 Community Edition (CE). Multi-stage builds is supported by Docker v17.05.0-ce and newer. Both the machine
and docker
executors allow us to request a newer Docker version.
docker
Executor
We can set the Docker version to use for the remote-engine right under setup_remote_docker
:
- setup_remote_docker:
version: 17.05.0-ce
Remember, when using the docker
executor and setup_remote_docker
, this just provides you with a remote Docker engine to connect to. Your base Docker image that your build is using still needs to have a Docker client installed. More on this and which versions of Docker are supported can be found here.
machine
Executor
The machine
executor now supports specifying an image similar to how the docker
executor works, but with our base VM images. The default image isn’t sufficient for our use case but the “edge” image is:
jobs:
build:
machine:
image: circleci/classic:edge
This provides us with a machine VM with Docker v17.06.0-ce. More on the machine
executor here.
With Docker v17.05.0-ce and newer, you can now build multi-stage Dockerfiles on CircleCI 2.0.