全力で怠けたい

怠けるために全力を尽くしたいブログ。

Docker のイメージはマルチステージビルドしてイメージサイズを小さくするメモ。

はじめに

Docker のイメージはマルチステージでビルドしてイメージサイズを小さくするメモ。

ソースコードを Docker のなかでビルドしようとするとビルド時だけ必要なライブラリとかも Docker のイメージに含める必要があり、どうしても最終的な Docker イメージのサイズが大きくなってしまう。 あと Docker イメージの中間レイヤーが増えると最終的な Docker イメージのサイズが大きくなってしまうのでできるだけレイヤーの数を増やさない工夫をする必要があるのだけど、Docker Engine 17.05 で導入されたマルチステージビルドはこの2つの課題を一気に解決することができる。

マルチステージビルドのやり方

マルチステージビルドは FROM を Dockerfile の中に複数記述するとマルチステージビルドでき、Dockerfile のなかで最後に記述する FROM のステージが最終的な Docker イメージになる。

# ソースコードをビルドするステージ
FROM golang:1.16 AS BUILD

# ソースコードを実行して生成したバイナリを実行するステージ
FROM busybox

シングルステージビルドとマルチステージビルドでの Docker イメージのサイズを比較

Go のソースコードをシングルステージビルドとマルチステージビルドでそれぞれ Docker のなかでビルドして最終的な Docker イメージのサイズを比較してみる。

Go のソースコードはこんな感じで Hello World! を出力するだけのもの。

// app.go
package main

import "fmt"

func main() {
    fmt.Println("Hello World!")
}
// go.mod
module github.com/ebc-2in2crc/app

go 1.16

まずシングルステージビルドしていく。Dockerfile はこんな感じ。

FROM golang:1.16

WORKDIR /go/src/app
COPY . .

RUN go install -v ./...

CMD ["app"]
$ docker image build -t my-golang-app:single-stage-build .
$ docker container run --rm my-golang-app:single-stage-build
Hello World!

次にマルチステージビルドしていく。Dockerfile はこんな感じ。

FROM golang:1.16 AS BUILD
WORKDIR /go/src/app
COPY app.go .
COPY go.mod .
RUN GOOS=linux go build

FROM busybox
COPY --from=BUILD /go/src/app/app /usr/local/bin/
CMD ["app"]

マルチステージビルドは FROM のところで AS <ステージの名前> みたいにしてステージに名前をつけておくと、後続のステージは COPY --from=<ステージの名前> 〜 みたいにして先行するステージのイメージからファイルとかにアクセスできる。

$ docker image build -t my-golang-app:multi-stage-build .
$ docker container run --rm my-golang-app:multi-stage-build
Hello World!

当たり前だけどシングルステージビルドでもマルチステージビルドでも Docker コンテナを実行すると同じ結果になる。

シングルステージビルドで作った Docker イメージとマルチステージビルドで作った Docker イメージのサイズはこんな感じ。

$ docker image ls | grep my-golang-app
my-golang-app                      single-stage-build   46d618cbdd49   32 minutes ago   864MB
my-golang-app                      multi-stage-build    8dbd2f2d2e87   36 minutes ago   3.17MB

Docker のイメージはシングルステージビルドしたものが 864MB でマルチステージビルドしたものが 3.17MB とマルチステージビルドしたイメージのほうが圧倒的に小さくなってる。 マルチステージビルドのほうは busybox をベースイメージにしてるので非常に小さくなってるけど実際は distroless をベースイメージにすることのほうが多いと思う。 distroless は busybox に比べたらサイズは大きいけど手元でマルチステージビルドしてみたら Docker イメージは 21.1MB で golang:1.16 でシングルステージビルドしたものに比べたら40倍以上は小さくできている。

自分が作ってる Docker イメージはまだシングルステージビルドしているものがあるけどあえてシングルステージビルドする必要はないと思うし、いまは新しく作る Docker イメージは基本的にマルチステージビルドするようにしてる。

参考サイト