If you’re searching for how to write a production-ready Golang Dockerfile, you’ve likely come across the term “multi-stage build.” This technique is one of the most effective ways to keep your Go applications lean, secure, and easy to deploy. In this post, we’ll break down what multi-stage Docker builds are, why they matter, and how you can apply them using a practical example.
What Is a Multi-Stage Docker Build?
A multi-stage build in Docker allows you to use multiple FROM
statements in a single Dockerfile. Each FROM
begins a new stage, and you can selectively copy artifacts from one stage to another. This is especially useful for Golang projects because Go compiles to a static binary, which means you can build in one stage and run in another, much smaller image.
Why Multi-Stage Builds Are a Game Changer for Golang
Traditionally, Docker images built from Golang source code included everything—source files, compilers, modules, and build tools. This leads to:
- 🚨 Large image sizes that are slow to build and push
- 🔓 Excess dependencies that increase attack surface
- 🧹 Cluttered environments that are harder to debug and maintain
Multi-stage builds solve these problems by separating the build and runtime environments.
Example Golang Dockerfile with Multi-Stage Build
Here’s a practical Dockerfile that uses multi-stage builds to compile and run a Go application efficiently:
FROM golang:1.23.4-alpine3.21 AS build
RUN apk add --no-cache git
COPY . /src
WORKDIR /src
RUN go mod tidy
RUN mkdir /app && go build -o /app/app_name
FROM alpine:3.21.0
WORKDIR /app
COPY --from=build /app /app
ENTRYPOINT ["/app/app_name"]
Let’s Break It Down:
- Stage 1: Build
FROM golang:1.23.4-alpine3.21 AS build
creates a build stage with Go and minimal dependenciesapk add git
installs Git to pull Go module dependencies if neededCOPY . /src
brings your app source into the imagego build -o /app/app_name
compiles your application into a binary
- Stage 2: Final Runtime Image
FROM alpine
creates a fresh, minimal imageCOPY --from=build
grabs only the binary output from stage 1ENTRYPOINT
defines how your container runs
Benefits of This Approach
1. Minimal Image Size
The final image is based on Alpine Linux, which is under 5MB. Since it only contains your Go binary and no compilers or source code, it’s incredibly lightweight and fast to ship.
2. Improved Security
Smaller images with fewer dependencies reduce your attack surface. Since the Go toolchain and Git aren’t included in the final container, vulnerabilities in those tools are not a concern for production.
3. Faster Builds and Deployments
Smaller images are faster to build, push, and pull from container registries. CI/CD pipelines run quicker, saving time and cost.
4. Cleaner Separation of Concerns
Your build tools stay in the build stage, while your runtime stage remains minimal and stable. This promotes better DevOps hygiene and debugging practices.
Bonus Tip: Add a .dockerignore File
To make your build context cleaner and faster, include a .dockerignore
file with entries like:
.git node_modules *.log .DS_Store
This prevents unnecessary files from being copied into your Docker image.
Conclusion
If you’re building Go applications for production, using a multi-stage Golang Dockerfile is a no-brainer. It keeps your images small, your deployments fast, and your security risks low. By separating the build and run environments, you follow Docker best practices while leveraging Go’s ability to compile into efficient static binaries.
Want to take it further? Add ENV
variables, health checks, or a non-root user layer to make your image even more production-ready.
Start using multi-stage builds today and give your Go Dockerfiles the upgrade they deserve.
Leave a Reply