はじめに
業務でDockerファイルを作成する機会があり、マルチステージビルドを使用したイメージの軽量化やビルド時間の短縮周りを色々調べたので備忘録がてらまとめておこうと思います。参考までに、マルチステージビルドによってどれくらいイメージのサイズが軽量化されたかも計測しています。
基本的にDockerfileの内容は以下の公式ドキュメントに沿って作成しています。
英語ですが、わかりやすいドキュメントなのでぜひ参考にしてみてください。
参考)https://docs.docker.com/guides/golang/build-images/
Dockerfileの完成系
先に完成系を載せちゃいます。
# Step 1: Modules caching
# コンパイルに必要なモジュールをインストールする
FROM golang:1.23.4-alpine3.21 AS modules
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod/ \
go mod download -x
COPY . .
# Step 2: Server building
# ソースコードをコンパイルし、/bin/serverにバイナリーを配置する
FROM modules AS build-server
RUN --mount=type=cache,target=/go/pkg/mod/ \
go build -o /bin/server ./cmd/app
FROM alpine:latest
ENV GO111MODULE=on
ENV TZ=Asia/Tokyo
COPY --from=build-server /bin/server /bin/server
EXPOSE 8080
ENTRYPOINT [ "/bin/server" ]
マルチステージビルドとキャッシュの効果
上記Dockerfileでは、マルチステージビルドを使用しコンパイル結果のバイナリのみをコンテナ内に配置しているため、生成されるイメージサイズは軽量化されています。
またキャッシュを利用して、ビルド時間を短縮しています。
マルチステージビルドを使用しない場合のイメージサイズと比較して、どの程度軽量化されているか確認してみます。
マルチステージビルドなしDockerfile
FROM golang:1.23.4-alpine3.21
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download -x
COPY . .
RUN go build -o /bin/server ./cmd/app
ENV GO111MODULE=on
ENV TZ=Asia/Tokyo
EXPOSE 8080
ENTRYPOINT [ "/bin/server" ]
ビルドコマンド
# マルチステージなしビルド
docker build --tag no-multi-stage-build -f ./docker/Dockerfile .
# マルチステージありビルド
docker build --tag multi-stage-build -f ./docker/Dockerfile .
イメージサイズの比較結果
以下のスクリーンショットは、マルチステージビルドを使用した場合としない場合のイメージサイズを比較したものです。
このように、ビルド時にのみ必要なGoツールチェーンをイメージに含めないことで容量を軽量化させることができます。これはビルド時間やデプロイ時間を短縮できるというメリットがあるだけでなく、コンテナデプロイ時のセキュリティの向上にも繋がります。
おわりに
ビルド時間の短縮は一回で見ると大した短縮時間にはならないですが、何百回もビルドしたりデプロイしたりを繰り返していくことを考えると意外と馬鹿にならない時間になると思います。開発フェーズが進むとdockerfileを修正する時間を取るのも難しくなってきたりするので、なるべく最初にいい感じのdockerfileが作りたいですね。