A Docker image is built up from a series of layers. Each layer represents an instruction in the image’s Dockerfile. Each layer except the very last one is read-only.
One of the most challenging things about building images is decreasing image size. In this article, we will discuss how we can optimize a docker image size.
Let’s create a custom docker image for a simple golang application.
# app.go
package main
import (
"fmt"
"time"
"os/user"
)
func main () {
user, err := user.Current()
if err != nil {
panic(err)
}
for {
fmt.Println("user: " + user.Username + " id: " + user.Uid)
time.Sleep(1 * time.Second)
}
}
Now, let’s write a **Dockerfile **to package the golang application :
# Dockerfile
FROM ubuntu # Base image
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y golang-go # Install golang
COPY app.go . # Copy source code
RUN CGO_ENABLED=0 go build app.go
CMD ["./app"]
Next, Create a **docker image **and run a container from that image :
# Create image from the Dockerfile
>> docker build -t goapp .
...
Successfully built 0f51e92fe409
Successfully tagged goapp:latest
# Run a container from the image created above
>> docker run -d goapp
04eb7e2f8dd2ade3723af386f80c61bdf6f5d9afe6671011b60f3a61756bdab6
Now, ‘exec’ into the container we created earlier :
# exec into the container
>> docker exec -it 04eb7e2f8dd sh
# list the files
~ ls
app app.go bin boot dev etc home ...
# run the application
~ ./app
user: root id: 0
user: root id: 0
user: root id: 0
user: root id: 0
user: root id: 0
...
We can see that after building the application we have appartifact inside the container. If we check the image size which helped us to build our application artifact :
>> docker images goapp
REPOSITORY TAG IMAGE ID CREATED SIZE
goapp latest 0f51e92fe409 16 hours ago 870MB
The image size is ‘870MB’, but we can slim this down using multi-stage builds. With multi-stage builds, we will use multiple **FROM **statements in our Dockerfile. Each **FROM **instruction can use a different base, and each of them begins a new stage of the build. We can selectively copy artifacts from one stage to another by leaving everything that we don’t want in the final image. To show how this works, let’s adapt the **Dockerfile **from the previous section to use multi-stage build.
We will divide our Dockerfile into two stages. One will be the build stage , which will help us to build our application and generate the artifact. And then we will only copy the artifact from the build stage to another stage and create a tiny production image.
# Dockerfile
# named this stage as builder ----------------------
FROM ubuntu AS builder
ARG DEBIAN_FRONTEND=noninteractive
# Install golang
RUN apt-get update && apt-get install -y golang-go
# Copy source code
COPY app.go .
RUN CGO_ENABLED=0 go build app.go
# new stage -------------------
FROM alpine
# Copy artifact from builder stage
COPY --from=builder /app .
CMD ["./app"]
Now, build the image and check the image size :
>> docker build -t goapp-prod .
Successfully built 61627d74f8b8
Successfully tagged goapp-prod:latest
>> docker images goapp-prod
REPOSITORY TAG IMAGE ID CREATED SIZE
goapp-prod latest 61627d74f8b8 5 minutes ago 8.92MB # <---
As we can see image size has been reduced significantly. It’s time to check if we can run a container from the image we created.
# create docker container
>> docker run goapp-prod
user: root id: 0
user: root id: 0
Great! We were able to use the tiny production image we created and it is working perfectly.
Top comments (0)