Example Use Cases
When might you want to use a multi-stage build? It allows you to do an entire pipeline within a single build, rather than having to script the pipeline externally. Here’s a few examples…
Java apps using WAR files
- First stage uses a container with Maven to compile, test, and build the war file
- Second stage copies the built war file into an image with the app server (Wildfly, Tomcat, Jetty, etc.)
Golang apps with standalone binaries
- First stage uses a container with golang SDK to build the binary
- Second stage copies the binary into an image
Node.js app needing processed JavaScript for client
- First stage uses a Node container, installs dev dependencies, and performs a build (maybe compiling Typescript, Webpack-ify, etc.)
- Second stage also uses a Node container, installs only prod dependencies (like Express), and copies the distributable from stage one
Here, we will take simple hello-world
golang program to illustrate single-stage build vs multi-stage build:
$ cat main.go
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
Single stage build
Usually for static type langauge as golang, derive Dockerfile from golang SDk, add source, do a build and push it to dockerhub, unfortuantely size was very huge. for example, basic golang:alpine
image is of size 257 MB, it increases further more.
workaround was : Derive from a Golang base image with the whole runtime/SDK (Dockerfile.build) Add source code Produce a statically-linked binary Copy the static binary from the image to the host (docker create, docker cp) Derive from SCRATCH or some other light-weight image such as alpine (Dockerfile) Add the binary back in Push a tiny image to the Docker Hub
that means having two separate Dockerfiles and shell script to control it
Dockerfile.singlestage
FROM golang:alpine
WORKDIR /app
ADD . /app
RUN cd /app && go build -o app
ENTRYPOINT ./app
- now build the dockerfile and run it using following commands,
$ docker build -t surajnarwade/go-singlestage-app .
$ docker run --rm surajnarwade/go-singlestage-app
- You can check docker image size which is only 259MB:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
go-singlestage-app latest bb2594c6d2fd 3 days ago 259MB
Multistage build
It removes hassle of maintaining multiple files, so now we have to maintain only one Dockerfile
syntax is very simple, we will give label to stages as
FROM <image> as <label>
- and then use it in another stage to copy artifacts as,
COPY --from=<label>
- whichever is last FROM statement, that is the final base image
- Dockerfile.multistage
# build stage
FROM golang:alpine AS build-env
ADD . /src
RUN cd /src && go build -o app
# final stage
FROM alpine
WORKDIR /app
COPY --from=build-env /src/app /app/
ENTRYPOINT ./app
now build the dockerfile and run it using following commands,
$ docker build -t surajnarwade/go-multistage-app .
$ docker run --rm surajnarwade/go-multistage-app
- you can see docker image size is reduced which is only approximately 6 MB:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
go-multistage-app latest 975ef40d39ee 3 days ago 5.52MB