はじめに
自分のメモ用に殴り書き。この記事では、docker-compose を使って、マルチステージビルドで中間生成される Docker イメージにタグ付けするためのベストプラクティスを紹介します。
TL;DR
- Dockerfile の各ステージには名前を付けましょう。
- 各ステージの中間イメージビルド用の docker-compose.build.ymlを作りましょう。
- 以上
- なお、docker-compose は、バージョン 3.4 以上である必要があります。
- V3.4 でサポートされた buildのtargetという機能を利用します。
 
- V3.4 でサポートされた 
Dockerfile マルチステージビルドのムカつくところ
- Dockerfile のマルチステージビルドは中間イメージにタグを付けてくれません。
- 結果として、<none>:<none>という無名の Docker イメージが残存し続けます。
- たまに掃除して上げないと、docker imagesに大量の無名イメージがリストされます。
- 不要なイメージでサーバーストレージも無駄に消費します。
# image:latest は Dockerfile で生成された最終成果物となる Docker イメージ
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
image                   latest              439521aeed0d        1 hours ago         1.48GB
<none>                  <none>              f683a5456003        1 hours ago         1.33GB
<none>                  <none>              a92a033bf4e7        1 hours ago         1.03GB
<none>                  <none>              7fa2a4097f16        2 hours ago         1.33GB
<none>                  <none>              20acbe3cae6c        2 hours ago         1.03GB
<none>                  <none>              dee5b549543c        3 hours ago         1.33GB
<none>                  <none>              8679ced16d20        3 hours ago         1.03GB
<none>                  <none>              f683a5456003        4 hours ago         1.33GB
<none>                  <none>              a92a033bf4e7        4 hours ago         1.03GB
<none>                  <none>              7fa2a4097f16        5 hours ago         1.33GB
<none>                  <none>              20acbe3cae6c        5 hours ago         1.03GB
<none>                  <none>              dee5b549543c        6 hours ago         1.33GB
<none>                  <none>              8679ced16d20        6 hours ago         1.03GB
...延々と続く
マルチステージビルドって何?
- 公式ドキュメント(英語)
- Docker イメージのビルド工程を複数ステージに分割して最終イメージを生成する、Dockerfile の機能です。
- 複雑になりがちだった Docker イメージ生成プロセスを効率よくするために導入された機能です。
- 例えば、Webアプリケーションサーバーの Docker イメージの場合、フロントエンドのパッケージ生成は、node:latestイメージを使い、バックエンドのパッケージ生成は、python:latestイメージを用いて、最終的にフロントエンドとバックエンドの生成物をalpine:latestイメージにデプロイするといった感じになるでしょう。(図とか欲しいけど、そんな時間ない、殴り書き)
擬似サンプル: シンプルなマルチステージビルド
- この記事では次のような Dockerfileとdocker-compose.ymlを利用して、手順を説明します。
Dockerfile
# 第1ステージビルド: node:latest を用いて、フロントエンドパッケージをビルドする
FROM node:latest
RUN build_frontend_package
# 第2ステージビルド: python:latest を用いて、バックエンドパッケージをビルドする
FROM python:latest
RUN build_backend_package
# 最終ステージビルド: alpine:latest をベースイメージとし、フロントエンドとバックエンドのパッケージを組み込んだ Web アプリケーションサーバを構築する
FROM alpine:latest
COPY --from 0 frontend_package
COPY --from 1 backend_package
RUN build_production frontend_package backend_package
docker-compose.yml
version: "3.4"
services:
    web:
        build: .
        image: image:latest
ポイント1: 各ステージには名前をつけましょう
- 
FROM イメージ名 AS ステージ名を使って、それぞれのステージに名前をつけてください。
- そうすると、Dockerfile 内や、docker-compose から、その名前でステージを参照できます。
- docker-compose でステージ名を指定してビルドするために、ステージに名前を付けるんです。
Dockerfile
# 第1ステージビルド: node:latest を用いて、フロントエンドパッケージをビルドする
FROM node:latest AS frontend_stage
RUN build_frontend_package
# 第2ステージビルド: python:latest を用いて、バックエンドパッケージをビルドする
FROM python:latest AS backend_stage
RUN build_backend_package
# 最終ステージビルド: alpine:latest をベースイメージとし、フロントエンドとバックエンドのパッケージを組み込んだ Web アプリケーションサーバを構築する
FROM alpine:latest AS final_stage
COPY --from frontend_stage frontend_package
COPY --from backend_stage backend_package
RUN build_production frontend_package backend_package
ポイント2: docker-compose.build.yml というファイルでビルドしましょう
- 執筆段階では、docker-compose を使うなら、docker-compose.ymlのみで実現するのは無理だと思います。
- 
docker-compose.build.ymlでは、buildでcontextとtargetという設定を入れます。
- 
contextには、カレントディレクトリとか Docker のビルドコンテキストのパスを入れておきます。
- 
targetには、(先ほど名前をつけた) マルチステージビルドのステージ名を入れて上げます。
- ファイル名は任意です。docker-compose.build.ymlである必要はありません。
docker-compose.build.yml
version: "3.4"
services:
    frontend:
        build:
            context: .
            target: frontend_stage
        image: frontend:latest
    backend:
        build:
            context: .
            target: backend_stage
        image: backend:latest
    final:
        build:
            context: .
            target: final_stage
        image: image:latest
- 
docker-compose.ymlからは、build設定を抜いておきましょう。(残しておいてもOK。使わないだけ。)
docker-compose.yml
version: "3.4"
services:
    web:
        image: image:latest
ビルドしてサービスを起動する方法
- 
docker-compose buildに、-fオプションでdocker-compose.build.ymlを指定してビルドします。
$ docker-compose -f docker-compose.build.yml build
- ビルドが完了したら、docker-compose.ymlを用いて、サービスを起動して上げます。- 注意: docker-compose.build.ymlではありません
 
- 注意: 
$ docker-compose up -d
- Docker イメージの一覧を表示すると、中間イメージがタグ付けされています。
$ docker images
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
image                   latest              439521aeed0d        1 hours ago         1.48GB
frontend                latest              f683a5456003        1 hours ago         1.33GB
backend                 latest              a92a033bf4e7        1 hours ago         1.03GB
まとめ
- Dockerfile の各ステージには名前を付けましょう。
- 各ステージの中間イメージビルド用の docker-compose.build.ymlを作りましょう。
- 細かな修正とかして、何度も docker-compose ビルドし直したりしても、タグ付けされます。(当たり前)
- 知らず知らずのうちに、手元ビルドで大量に残存していた無名イメージがなくなります。
- マルチステージビルドの中間イメージにタグ付けする方法は他にもありますが、私の試行錯誤したかぎりでは、これがベストプラクティスだと思います。
