This page looks best with JavaScript enabled

Create Lightweight Docker Images With Multi Staged Build

 ·   ·  ☕ 4 min read

Me Before

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
FROM golang:alpine

WORKDIR /go/src/github.com/santosh/qagine

COPY . .

RUN go get -d
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o app .

EXPOSE 8080

CMD ["./app"]

Me building the image.

$ docker build -t sntshk/qagine .
Sending build context to Docker daemon  259.6kB
Step 1/7 : FROM golang:alpine
 ---> 760fdda71c8f
Step 2/7 : WORKDIR /go/src/github.com/santosh/qagine
 ---> Running in e2cf75dd1655
Removing intermediate container e2cf75dd1655
 ---> 8b4955b2e885
Step 3/7 : COPY . .
 ---> 43cd9a41efcb
Step 4/7 : RUN go get -d
 ---> Running in 55c01f67fa99
go: downloading github.com/gorilla/mux v1.7.4
go: downloading go.mongodb.org/mongo-driver v1.3.2
go: downloading github.com/dgrijalva/jwt-go v3.2.0+incompatible
go: downloading golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a
go: downloading github.com/pkg/errors v0.8.1
go: downloading github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c
go: downloading github.com/golang/snappy v0.0.1
go: downloading github.com/go-stack/stack v1.8.0
go: downloading github.com/klauspost/compress v1.9.5
go: downloading golang.org/x/sync v0.0.0-20190423024810-112230192c58
go: downloading github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc
go: downloading golang.org/x/text v0.3.2
Removing intermediate container 55c01f67fa99
 ---> 038b29b80653
Step 5/7 : RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o app .
 ---> Running in 122df20d3329
Removing intermediate container 122df20d3329
 ---> f4616dc3fe1c
Step 6/7 : EXPOSE 8080
 ---> Running in 4258f9aac8c6
Removing intermediate container 4258f9aac8c6
 ---> b643c7e16c0b
Step 7/7 : CMD ["./app"]
 ---> Running in 948fa65b093b
Removing intermediate container 948fa65b093b
 ---> 621b8e5396ad
Successfully built 621b8e5396ad
Successfully tagged sntshk/qagine:latest

The sweet size of the image is 525MB.

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
sntshk/qagine       latest              8e966b835d82        7 seconds ago       525MB

That’s because we installed all the dependencies my application needs on top of the golang base image.

Me After

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
FROM golang:alpine

WORKDIR /go/src/github.com/santosh/qagine
COPY . .
RUN go get -d
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o app .


FROM alpine:latest

RUN apk --no-cache add ca-certificates
WORKDIR /go/src/github.com/santosh/qagine
COPY --from=0 /go/src/github.com/santosh/qagine/app .

EXPOSE 8080
CMD ["./app"]

Me running my recipe of our latest Dockerfile. I’m proud that I made that choice. 😋

$ docker build -t sntshk/qagine .
Sending build context to Docker daemon  259.6kB
Step 1/11 : FROM golang:alpine
 ---> 760fdda71c8f
Step 2/11 : WORKDIR /go/src/github.com/santosh/qagine
 ---> Using cache
 ---> 8b4955b2e885
Step 3/11 : COPY . .
 ---> 824d443e02d4
Step 4/11 : RUN go get -d
 ---> Running in d6927d1b21f8
go: downloading github.com/gorilla/mux v1.7.4
go: downloading go.mongodb.org/mongo-driver v1.3.2
go: downloading golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a
go: downloading github.com/dgrijalva/jwt-go v3.2.0+incompatible
go: downloading github.com/go-stack/stack v1.8.0
go: downloading github.com/golang/snappy v0.0.1
go: downloading github.com/klauspost/compress v1.9.5
go: downloading github.com/pkg/errors v0.8.1
go: downloading github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc
go: downloading github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c
go: downloading golang.org/x/sync v0.0.0-20190423024810-112230192c58
go: downloading golang.org/x/text v0.3.2
Removing intermediate container d6927d1b21f8
 ---> 9edacd1c2f9a
Step 5/11 : RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o app .
 ---> Running in b6012db1fde8
Removing intermediate container b6012db1fde8
 ---> 0591bf0229cf
Step 6/11 : FROM alpine:latest
 ---> f70734b6a266
Step 7/11 : RUN apk --no-cache add ca-certificates
 ---> Using cache
 ---> 50dab5d0feaa
Step 8/11 : WORKDIR /go/src/github.com/santosh/qagine
 ---> Running in 8136cd07627f
Removing intermediate container 8136cd07627f
 ---> 900601534a07
Step 9/11 : COPY --from=0 /go/src/github.com/santosh/qagine/app .
 ---> 260353317972
Step 10/11 : EXPOSE 8080
 ---> Running in b445d455c9c1
Removing intermediate container b445d455c9c1
 ---> 284de5350f3e
Step 11/11 : CMD ["./app"]
 ---> Running in 3d26b0b7ad1d
Removing intermediate container 3d26b0b7ad1d
 ---> f63a288ada1e
Successfully built f63a288ada1e
Successfully tagged sntshk/qagine:latest

I’m telling you, you’ll be surprised by the size of the image.

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
sntshk/qagine       latest              a60a5c6a5722        About a minute ago   21.9MB

Explanation

In the first Dockerfile we are taking a base image of golang:alpine which has its own weightage (128MB as listed on https://hub.docker.com/_/golang?tab=tags). On top op that dependency to the application is being installed, which must be quite an amount of space. Finally an executable is generated with its own space needs.

In the second run, we are doing the same. But at the last, we are extracting the executable from the first image and wrapping it inside a brand new alpine image. So this image has only the weight of alpine image (around 5MB?) and the executable.

I am now smarter than yesterday.

Hope this helps you as well.

Share on

Santosh Kumar
WRITTEN BY
Santosh Kumar
Fullstack Developer at Method Studios