multi stageなDockerfileで中間イメージにタグをつける

Google Cloud Container Builder でbuildする時に高速化する為にいろいろ奮闘中
multi stageなDockerfileの中間イメージにタグをつけたい場合があったのでメモ

# 背景

- Google Cloud Container Builderでのビルドを高速化する為に、キャッシュに使うイメージを事前にpullしておく必要がある
    - https://cloud.google.com/container-builder/docs/speeding-up-builds
- マルチステージなDockerfileのビルド時に最終的なビルド結果のイメージをキャッシュしても、中間イメージのキャッシュが無いので高速化されない
   - 中間イメージのbuildが一番重いので、中間イメージにキャッシュを使って欲しい
   - その為には中間イメージもpushしておく必要がある

# multi-stage とは

https://docs.docker.com/develop/develop-images/multistage-build/

簡単に言えば `ビルド用のイメージと実行用のイメージを分けたい時に1つのDockerfileで実現する方法` ですかね

- `イメージA` でビルド
- `イメージB` に `イメージA` でビルドした結果をコピー

ということが出来る為、実行用イメージにはコンパイラ等を入れておく必要が無い分、イメージが軽くなる
→これによってpullが軽くなるので、デプロイも速くなる

# multi-stageなDockerfile

https://github.com/yagince/docker-multi-stage-sample

```dockerfile
# build stage
FROM rust:1.26.1-slim-stretch AS build-stage

RUN mkdir -p /app
WORKDIR /app

COPY . /app
RUN cargo build --release

# runtime stage
FROM debian:stretch-slim

COPY --from=build-stage /app/target/release/multi-stage /usr/bin/multi-stage

ENTRYPOINT ["/usr/bin/multi-stage"]
```

やってることは↓こんな感じ

- 前半
    - Rustのコンパイラ入りイメージをベースイメージにする
    - Rustのアプリケーションをコンパイル
- 後半
    - 前半のイメージでビルドした結果を `COPY --from` で新しいイメージにコピーする

こんな感じのDockerfileをビルドしてみます

```sh
> docker build -t multi-stage .
Sending build context to Docker daemon  10.75kB
Step 1/8 : FROM rust:1.26.1-slim-stretch AS build-stage
1.26.1-slim-stretch: Pulling from library/rust
f2aa67a397c4: Pull complete
fe1b9f79bfd7: Pull complete
Digest: sha256:b0f6f5d661a8d23f0e7c7cb074bde9d278ca0f83d317f7ac2788720943c463f3
Status: Downloaded newer image for rust:1.26.1-slim-stretch
 ---> b918abbd27e1
Step 2/8 : RUN mkdir -p /app
 ---> Running in 4bc7eeb1dcd6
Removing intermediate container 4bc7eeb1dcd6
 ---> 57ebb94df725
Step 3/8 : WORKDIR /app
Removing intermediate container e9026893de00
 ---> 676390dab381
Step 4/8 : COPY . /app
 ---> b7dd4f593319
Step 5/8 : RUN cargo build --release
 ---> Running in b2cea00fa646
    Updating registry `https://github.com/rust-lang/crates.io-index`
 Downloading regex v1.0.0
 Downloading regex-syntax v0.6.0
 Downloading utf8-ranges v1.0.0
 Downloading thread_local v0.3.5
 Downloading aho-corasick v0.6.4
 Downloading memchr v2.0.1
 Downloading ucd-util v0.1.1
 Downloading lazy_static v1.0.1
 Downloading unreachable v1.0.0
 Downloading void v1.0.2
 Downloading libc v0.2.42
   Compiling void v1.0.2
   Compiling libc v0.2.42
   Compiling ucd-util v0.1.1
   Compiling lazy_static v1.0.1
   Compiling regex v1.0.0
   Compiling utf8-ranges v1.0.0
   Compiling unreachable v1.0.0
   Compiling thread_local v0.3.5
   Compiling regex-syntax v0.6.0
   Compiling memchr v2.0.1
   Compiling aho-corasick v0.6.4
   Compiling multi-stage v0.1.0 (file:///app)
    Finished release [optimized] target(s) in 22.23 secs
Removing intermediate container b2cea00fa646
 ---> 792416763821
Step 6/8 : FROM debian:stretch-slim
stretch-slim: Pulling from library/debian
f2aa67a397c4: Already exists
Digest: sha256:aa33563fd6dc2b14e8cae20cc74f4581d4cb89a3952d87b4cab323707040f035
Status: Downloaded newer image for debian:stretch-slim
 ---> 26f0bb790e25
Step 7/8 : COPY --from=build-stage /app/target/release/multi-stage /usr/bin/multi-stage
 ---> 0d1cfeee975b
Step 8/8 : ENTRYPOINT ["/usr/bin/multi-stage"]
 ---> Running in b4cbffcbfda3
Removing intermediate container b4cbffcbfda3
 ---> 9ea83a0f3562
Successfully built 9ea83a0f3562
Successfully tagged multi-stage:latest
```

```sh
❯ docker images
REPOSITORY                                      TAG                                IMAGE ID            CREATED             SIZE
multi-stage                                     latest                             9ea83a0f3562        2 minutes ago       61.8MB
<none>                                          <none>                             792416763821        2 minutes ago       1.08GB
```

こんな感じでイメージは2つできている。
`<none>` の方が前半のイメージですね。

一応、ビルドしたイメージをrunしてみます

```shell
❯ docker run --rm multi-stage
Captures({0: Some("2010-03-14"), "year": Some("2010"), "month": Some("03"), "day": Some("14")})
```

中身はregexのサンプルコードなので、適当に正規表現でキャプチャした結果が出力されます。
無事実行できてますね。

# 本題: 中間イメージにタグをつける

やっと本題です。
今、 `<none>` になっているビルド用の中間イメージにtagをつけたいです。
答えは簡単で
https://docs.docker.com/engine/reference/commandline/build/
`docker build` の `--target` オプションを使います

```sh
> docker build --target build-stage -t multi-stage:build-stage .
```

```
Sending build context to Docker daemon  10.75kB
Step 1/5 : FROM rust:1.26.1-slim-stretch AS build-stage
 ---> b918abbd27e1
Step 2/5 : RUN mkdir -p /app
 ---> Using cache
 ---> 57ebb94df725
Step 3/5 : WORKDIR /app
 ---> Using cache
 ---> 676390dab381
Step 4/5 : COPY . /app
 ---> Using cache
 ---> b7dd4f593319
Step 5/5 : RUN cargo build --release
 ---> Using cache
 ---> 792416763821
Successfully built 792416763821
Successfully tagged multi-stage:build-stage
```

(cacheが効いてれば速攻終わる)

```sh
❯ docker images
REPOSITORY                                      TAG                                IMAGE ID            CREATED             SIZE
multi-stage                                     latest                             9ea83a0f3562        14 minutes ago      61.8MB
multi-stage                                     build-stage                        792416763821        14 minutes ago      1.08GB
```

先程 `<none>` だった IMAGE ID と 同じimageにtagがついてますね。

あとは、これをpushしておいて、
使う時には`pull`して`--cache-from`に指定すればcacheとして使うことが出来ます。

# まとめ

- 中間イメージにtagをつけたい時は、 `docker build --target` オプションでstageを指定する