Docker - Optimized Dockerfile

发布时间 2023-09-23 19:31:48作者: ZhangZhihuiAAA

We have the incentive to optimize our Dockerfile to build smaller images. Some of the points that we can probably come up with would be the following:
• Not using Golang for our base image. Golang is a statically compiled language—once we have the compiled binary, all we need after that would just be a Linux distribution. With that, we can reduce the container size by just copying the compiled binary that is built to the production base image and then build out a simpler production image for our use.

• Note for the initial version of the Dockerfile suggested here, and we have the ADD . . line. This line indicates to Docker to copy all files from the current working directory (or directory that is passed to the docker build command) to the workspace within the docker image. We would then run the go get command right after. In our simple example, Golang modules download can be handled pretty quickly, but for much larger projects, the downloading of the Golang modules can easily take 5–10 minutes.

We can rely on Docker’s caching mechanism. For Docker if Docker detects that there is potentially little change between the layers, it will rely on the cache layers. In the case where we use the ADD . ., this would mean any code change we added would
invalidate the docker cache. We can alter this by just adding the go.mod and go.sum files and then downloading the Golang modules. After which, as long as we do not change said files, we will not go through the step of needing to download the Golang module dependencies. The optimized Dockerfile based on the previous points would look something like the following:

FROM golang:1.21 as source
WORKDIR /app
ADD go.mod go.sum ./
RUN go env -w GOPROXY=https://goproxy.io,direct
RUN go mod download
ADD . .
RUN go build -o app .

FROM debian:buster
COPY --from=source /app/app /app
CMD ["/app"]

The optimized Dockerfile uses the concept of multi-stage Docker builds. This is an approach where we can define multiple docker image build steps through the Dockerfile. Each docker image to be built defined in the Dockerfile starts with the FROM keyword, so with our example preceding, we are building two docker images here. If we build this Dockerfile by simply running docker build -t tester:v2 ., this would be building all images within the docker image and would output the final image defined within the Dockerfile.

Using the multi-stage Docker build approach, we can set it such that the first Docker image to be built will focus on the building of the Golang binary application binary. To make it easier to reference this built image, we can label it as a source. Once we
have built the first image defined in the Dockerfile, we can simply copy the built binary within said image over to our production container image—we do not need Golang to build tooling/runtime in production. Part of the steps to build the second image in our Dockerfile would be copying the binary from the source image; this is done by adding the step: COPY --from=source /app/app /app.

zzh@ZZHPC:/zdata/MyPrograms/Go/aaa$ docker build -t tester:v2 .
[+] Building 30.8s (15/15) FINISHED
zzh@ZZHPC:~/.docker$ docker images tester:v2
REPOSITORY   TAG       IMAGE ID       CREATED              SIZE
tester       v2        3bd6add9048c   About a minute ago   122MB
zzh@ZZHPC:/zdata/MyPrograms/Go/aaa$ docker run --name=tester -p 8080:8080 -d tester:v2
zzh@ZZHPC:/zdata/MyPrograms/Go/aaa$ docker logs -f tester
/app: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by /app)
/app: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by /app)