Docker v17くらいからMultiStageBuildという機能が実装されています。
今まで、「Hello,Worldを出力するだけのImageなのに、ベースImageサイズが大きくて、結局出来上がるImageも数百MBになるんだけど、、、」と、なっていたものが、BuildはGoのImage,実行はalpineのImageとできるようになりました。(v17以前でもできなくはなかったけど、めんどくさかった)
これが一体どういうことなのかをみていきたいと思います。
今回は、Hello, World!
を出力するコンテナを作成していきます。
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
#MultiStageBuildを使わない場合
Dockerfileは以下のようになります。
FROM golang:1.13.7-alpine3.11
COPY ./main.go ./
RUN go build -o ./hello ./main.go
ENTRYPOINT ["./hello"]
それぞれ同じディレクトリに存在する状態で、以下のコマンドを打ってみましょう。
$ docker build -t hello:0.1 .
ビルド出来たらRunしてみます。
$ docker run hello:0.1
Hello World!
ちゃんとmain.goがbuildされて、実行されました。では、この時のImageサイズをみてみます。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello 0.1 ea6e8238c063 49 seconds ago 361MB
golang 1.13.7-alpine3.11 87eefb76f0a8 9 days ago 359MB
このように、golangのcontainer Imageがベースになっているため、サイズが大きくなっています。
Hello, World!
を出力するだけのコンテナなのに、これじゃあ重すぎですね。。。
#MultiStageBuildを使う場合
Dockerfileは以下のようになります。
#Stage 1
FROM golang:1.13.7-alpine3.11 as builder
COPY ./main.go ./
RUN go build -o /hello ./main.go
#Stage 2
FROM alpine:3.11
COPY --from=builder /hello .
ENTRYPOINT ["./hello"]
Stage1はgolangのImageを使っていて、ここでmain.goをbuildしています。そしてその実行ファイルをStage2に持ってきて、こっちで実行すると言う形です。
$ docker run hello:0.1
Hello World!
こちらもちゃんと実行されました。同様に、Imageのサイズをみてみます。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello 0.1 15f814bf3a87 12 seconds ago 7.6MB
<none> <none> 6f908ead16f8 13 seconds ago 361MB
golang 1.13.7-alpine3.11 87eefb76f0a8 9 days ago 359MB
alpine 3.11 e7d92cdc71fe 2 weeks ago 5.59MB
ここで<none>
はStage1のImageで、<hello>
がStage2のImageになっています。Hello, Worldを出力するImageは<hello>
なので、そのサイズなんと7.6MBです!MultiStageBuildを使わない場合と比べて約350MBもImageサイズを削減することを実現できました!!
#まとめ
ということで、今回はDockerのMultiStageBuildの機能について、触れてみました。サイズの重たいBuild環境を分離することで、Imageサイズの削減を可能にすることを挙げましたが、他にもStageで分けることで、Dockerfileの保守のしやすさが上がる、セキュリティに強くなるなど、良い事が沢山あります!みなさまも使った事がない方は一度チャレンジしてみてください!