Docker Multi-Stage building

Why do we need smaller container images?

Sometimes a Docker Image is really big and heavy to start. Let’s assume you use a Node.js Environment, Next.js in particular. What you can do is to use an existing Node.js Image like mhart/alpine-node, build your Site and than run it.

It could look like this:

FROM mhart/alpine-node
WORKDIR /app
COPY package.json .
COPY package-lock.json .
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000

CMD ["node_modules/.bin/next", "start"]

What this does is, copy the package.json and package-lock.json into the building Image and run npm install to get all dependencies. After that we copy the rest of our app and run the build. We expose our default port 3000 and run the app.

This works totally fine but we have data that we dont need to run our app. We include all the source code and development dependencies.

So how we can rid of it?

We use a multi-stage Docker build. We split this example in 2 build steps. The first one looks very familiar to the previous example. In the second step we don’t care about our development dependencies or other stuff that we don’t need to provide the app. So we only copy the builded app over from the first build step.

FROM mhart/alpine-node AS builder
WORKDIR /app
COPY package.json .
COPY package-lock.json .
RUN npm install
COPY . .
RUN npm run build

FROM mhart/alpine-node:base
WORKDIR /app
COPY --from=builder /app .
EXPOSE 3000

CMD ["node_modules/.bin/next", "start"]

With this tiny changes on the Dockerfile we gain an extreme defferent image size. To prove that, here are some numbers (i use the Next.js styled-components example):

Default Build: 178 MB

Multi-Stage Build: 91.6 MB

But how works this?

In the first 7 lines we define the first build step but with an addition on the first step. We declare with the AS that this step have the name: builder Thats a handy way to access this content in a later step. So we can use this handle in our second step. With the command COPY and the argument --from= we can get the data out of the first build step. Is the build complete, we have a tiny Docker image and Docker forget about the rest.

In theory you can use as many as steps you like or needed. Try it out and dig deeper into Docker Image building. It’s always worth it.