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.

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

FROM golang:1.8.3
WORKDIR /go/src/
RUN go get -d -v  
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/ .
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:

      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.