Optimizing Docker Images with Multi-Stage Builds

By Travis ReederLast updated
Optimizing Docker Images with Multi-Stage Builds

Previously, We’ve discussed creating small Docker images, but now with Docker’s support for multi-stage builds, it’s worth revisiting the topic. Previously, we had to build the binary in one step and then create the Docker image in a separate step, which could be cumbersome and tricky. Let's see if multi-stage builds simplify the process.

Note: This requires Docker 17.05 or newer.

Let's start with a basic Go program:

package main

import "fmt"

func main() {
	fmt.Println("Hello world!")
}

Here's how we can build it using a single-stage Docker build with the golang:alpine image. The Dockerfile looks like this:

FROM golang:alpine
WORKDIR /app
ADD . /app
RUN cd /app && go build -o goapp
ENTRYPOINT ./goapp

Build and run the Docker image:

docker build -t treeder/hello .
docker run --rm treeder/hello

Let's check the image size with:

docker images | grep treeder/hello
Result

The image is 258 MB, which is quite large for just a small Go binary. Now, let’s use a multi-stage build with this updated Dockerfile:

# Build stage
FROM golang:alpine AS build-env
RUN apk --no-cache add build-base git bzr mercurial gcc
ADD . /src
RUN cd /src && go build -o goapp

# Final stage
FROM alpine
WORKDIR /app
COPY --from=build-env /src/goapp /app/
ENTRYPOINT ./goapp

Build and run this new Docker image:

docker build -t treeder/hello .
docker run --rm treeder/hello

Now check the size:

Result

The result is a ~10 MB image instead of 258 MB. At that size the image stops being your bottleneck and the platform starts to matter — we compared six Go hosting platforms on cold starts and pricing.

In summary, multi-stage builds are highly effective and should be used nearly always to keep Docker images slim and optimized.

Keep reading

More posts tagged “Community”.
Microcontainers — Lightweight, Portable Docker Containers

Docker packages your application and its dependencies into a single image for use in a container, but these images often become unnecessarily large. For example, a simple Node.js application using an official image can swell to 644 MB, even though the application itself takes up less than 1 MB. Optimizing the image can significantly reduce this excess size. So what can we do to reduce the image size?

Wed, Aug 28, 2024
Build with Dockerfile: caching layers to speed up CI

This article covers how Docker layer caching actually works, how to structure your Dockerfile to maximize cache hits, and how to wire it all up in CI so the cache persists between builds.

Wed, Apr 15, 2026
The Future of Servers and Infrastructure: Why Docker is Essential for Modern Development and DevOps Practices

Discover how Docker revolutionizes modern development with lightweight containers, streamlined workflows, enhanced DevOps collaboration, and robust security. Dive into the future of scalable, efficient, and portable server infrastructure.

Tue, Aug 6, 2024
Dockerfile basics for deploying your first app in 2026

You've built something. Now you need to ship it — fast, reliably, and without becoming a DevOps engineer overnight. That's exactly what a Dockerfile is for. This guide walks you through the core concepts, real syntax, and practical patterns you need to containerize and deploy your first application. No fluff, no unnecessary theory.

Sun, Apr 5, 2026
How to create Dockerfile for NestJS app

Today we will Dockerize a Node.js application. We will use Nest.js for today. But the commands and processes are exactly the same for any Node.js application built by some other frameworks.

Thu, Jul 25, 2024
How to create Dockerfile for Laravel application

The easiest way to dockerize your Laravel Project

Tue, Jul 16, 2024
Be first in line for updates
and special pricing
Get early access to new features and exclusive discounts delivered straight to your inbox