LoginSignup
5
6

More than 5 years have passed since last update.

docker 17.05 の multi-stage build で image サイズを30分の1にしてみる

Last updated at Posted at 2018-04-21

きっかけ

Kubernetes best practices: How and why to build small container images に docker image size を小さくする tips について書かれていたので、実際に試してみました。

Kubernetes ではパフォーマンス上 image size は小さい方が良い というのはあると思いますが、他のユースケースでも image size が小さい方が嬉しいことは多いと思います。

すぐに忘れちゃうので、基本的には自分の備忘録のために残しておきます。

その前に multi-stage build ってなに?

Docker Documentation に詳細が記載されています。

Docker 17.05 以降のバージョンから使えるようになった機能です。
1つの Dockerfile 内で複数のベースイメージを使うことができ、最終的に一つの image をつくることができます。
メンテナンス性を保ったまま、Dockerfile の最適化などを行うことができます。

端的にいうと Builder-Pattern を使わずに Docker Image Size を小さくする ことができます。

今回は便宜上、単一のベースイメージから作成する Dockerfile を single-stage と書くことにします。

今回の構成

Docker 17.09.0-ce (Docker for Mac)

.
├── multi-stage
│   ├── Dockerfile
│   └── main.go
└── single-stage
    ├── Dockerfile
    └── main.go

https://github.com/ight-reco/go-docker-multistage に今回の構成を置いています。

実際にやってみる

single-stage

single-stage の docker image から作ってみます。

single-stage/Dockerfile
# golang app build & 実行用
FROM golang:alpine
WORKDIR /app
ADD . /app
RUN cd /app && go build -o goapp

EXPOSE 8080
ENTRYPOINT ./goapp
single-stage/main.go
package main

import (
        "fmt"
        "net/http"
)

func main() {
        http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
                fmt.Fprintln(w, "Hello, World")
        });

        fmt.Println("Running http://localhost:8080");
        http.ListenAndServe(":8080", nil);
}
$ docker build -t single-stage ./single-stage
...
Successfully tagged single-stage:latest

$ docker images --format "table {{.Repository}}\t{{.Size}}"
REPOSITORY          SIZE
single-stage        382MB
...

build できました。golang:alpine を使っていても 382MBぐらいでした。
念のため実行してみます。

$ docker run -it -p 8080:8080 builder-pattern
Running http://localhost:8080
$ curl localhost:8080
Hello, World

無事実行できているようです!

multi-stage

同様に multi-stage image を作っていきます。
multi-stage なので FROM が2回出現します。
main.go は single と同じなので割愛します。

multi-stage/Dockerfile
# golang app build 用
FROM golang:alpine AS build-env
WORKDIR /app
ADD . /app
RUN cd /app && go build -o goapp

# golang app 実行用 (ここのベース image は最小限に)
FROM alpine
WORKDIR /app

# build した goapp を実行用に COPY
COPY --from=build-env /app/goapp /app
EXPOSE 8080
ENTRYPOINT ./goapp
$ docker build -t multi-stage ./multi-stage
...
Successfully tagged multi-stage:latest

$ docker images --format "table {{.Repository}}\t{{.Size}}"
REPOSITORY          SIZE
multi-stage         10.8MB
single-stage        382MB
...

10.8MB!!!! 大幅に小さくなりました!

なんでそうなるの?

golang の場合、build するとワンバイナリになるので、基本的にはビルドされたバイナリさえあれば動作します (HTTPS 通信など一部のケースではその場限りではない)。
そのため、ビルド時だけに必要なファイルは実行用イメージには含めずに作成することで image size を抑えることができるようです。

まとめ

Builder-Pattern と呼ばれるパターンでビルドと実行を分ければ multi-stage build 機能を使わずに実現可能ですが、やはり複雑性が増してしまうので multi-stage build を採用するで良いと思います!

今回はすごい極端な例ですので、実際にはもっと image size が大きくなるとは思います。

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6