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)