If building secure, small Docker images are important to you then you should be using multistage builds. Depending on the language you’re using you can make images with almost zero dependencies!

The way to think of a multi-stage Dockerfile is as a multi-step build. For most production applications there is a first step where you grab all the dependencies and build the project, and then a second where you actually run it with a much smaller Docker image.

Reasons to Use a Multi-Stage Build

  1. Image Size – Smaller Docker image means less network usage and storage
  2. Vulnerabilities – Less chance of running unpatched software
  3. Security – Add secrets in the first stage without having them on the final image

Basic Example

Below is an example of a basic multi-stage Dockerfile. It simply builds the golang repository and then runs it in an alpine image. Sometimes if you want an extra small image you can use a scratch image which is incredibly minimal. But for most cases, you need things like certificates so using alpine is a safer bet unless you really know what you’re doing.

FROM golang:1.11 AS builder
WORKDIR /go/src/{PROJECT PATH}/
COPY app.go .
RUN go build -o app . FROM alpine:latest WORKDIR /root/ COPY --from=builder /go/src/{PROJECT PATH}/app .
ENTRYPOINT ["./app"]

Real-World Example

The example above is very basic. Below is based off real-world Dockerfiles I actually use, some of the main differences are dependencies are added. The alpine image is only 5 MB so for most projects it is the way to go.

A common thing that often gets forgotten when building a multi-stage build is certificates. Because alpine builds are so small they don’t come with certificates built in. So adding ca-certificates solves that problem.

FROM golang:1.11-alpine AS builder

RUN apk add --no-cache ca-certificates openssl git
RUN wget -O /usr/local/bin/dep https://github.com/golang/dep/releases/download/v0.4.1/dep-linux-amd64 && \
chmod +x /usr/local/bin/dep

RUN mkdir -p /go/src/github.com/{project path}/

ADD . /go/src/github.com/{project path}/

WORKDIR /go/src/github.com/{project path}/

RUN dep ensure

RUN go build -o app .

# final stage
FROM alpine:latest
WORKDIR /app
RUN apk --no-cache add ca-certificates
COPY --from=builder /go/src/github.com/{project path}/app /app/
ENTRYPOINT ["./app"]

I hope this shows you some good ways of using multi-stage Docker builds in your projects to reduce image size and security attack vector. Let me know in the comments how you use multi-stage builds!

Categories: Docker

Leave a Reply

Your email address will not be published. Required fields are marked *