🐳

複数の環境でDockerfileを共通化するために使えるtips

2023/06/12に公開

前提

コンテナを用いてアプリケーションのワークロードを構築することにはいくつかの利点があります。
なかでも、下記に上げられるポータビリティと環境の再現性は非常に強力です。

ポータビリティ

コンテナは、アプリケーションとその依存関係をコンテナ内にパッケージ化します。
これにより、開発環境で構築したコンテナを本番環境にデプロイする際にも、一貫した動作が期待できます。
異なる環境間でアプリケーションを移行する際に、互換性の問題や依存関係の不一致が生じるリスクが低減され、ポータビリティが高まります。

環境の再現性

コンテナは環境に依存しないため、開発者が特定の環境でアプリケーションを構築した場合でも、他の開発者や運用チームが同じ環境を再現することが容易です。
コンテナイメージにはアプリケーションのコードとその実行環境が含まれており、イメージを共有することで他の人が同じ環境でアプリケーションを実行できます。
これにより、開発から本番までの環境の一貫性と再現性が向上します。

モチベーション

コンテナのポータビリティと環境の再現性を最大限に生かすためには、全環境で共通のコンフィグを利用する必要があります。
本稿ではPHPアプリケーションをdockerで構築する例を題材に、dockerfileを複数の環境で共通化するために使えるtipsを紹介します。

ARGステートメントを使用する

Dockerfile内でARGステートメントを使用して、ビルド時に渡される引数を定義することができます。これにより、環境ごとに異なる値を渡すことができます。
例えば、ベースイメージのタグやポート番号など、環境に依存する値をARGステートメントで宣言し、ビルド時に引数として渡すことができます。

https://docs.docker.jp/engine/reference/builder.html#arg

公式ドキュメントに記載はありませんが、巷ではARGステートメントで渡す値のことをビルド引数(build argument)などと呼ぶようです。

余談ですが、ENVARGは全く違うものなので注意が必要です。

https://techblog.recochoku.jp/1979

実例

ARGステートメントの実例です。
今回は、php.iniを環境毎に別ディレクトリで管理し、ARGステートメントを用いてコンテナのビルド時にコピー元を切り換える例を示します。

フォルダ構成
.
├── Dockerfile
├── dev
│   └── php.ini
└── prd
    └── php.ini
Dockerfile
# syntax=docker/dockerfile:1.4
FROM php:8.2-fpm-alpine

ARG ENVIRONMENT

# PHP設定ファイル
COPY --link ${ENVIRONMENT}/php.ini /usr/local/etc/php/

開発用にビルドしてみます。

docker build --build-arg ENVIRONMENT=dev .

[+] Building 8.2s (11/11) FINISHED
<省略>
 => [2/2] COPY --link dev/php.ini /etc/  0.1s
 => exporting to image                   0.0s
 => => exporting layers                  0.0s
 => => writing image sha256:97b2e5a886e1ecc0ecee4be6440098cca833b55a20c7bc0664aa25bda8e223e4

devディレクトリのphp.iniがコピーされました。

本番用にビルドしてみます。

docker build --build-arg ENVIRONMENT=prd .

[+] Building 3.3s (11/11) FINISHED
 => [2/2] COPY --link prd/php.ini /etc/  0.0s
 => exporting to image                   0.0s
 => => exporting layers                  0.0s
 => => writing image sha256:3ecf947f6f438b40e6ca05af5f87e1f8e6f8a282d15a3a5173f9e697d4dc3f99

prdディレクトリのphp.iniがコピーされました。

サンプルコードはこちらにあります。

https://github.com/isanasan/example-for-dockerfile-standardization-across-environments/tree/main/example-arg-statement

マルチステージビルドを使用する

Dockerのマルチステージビルドを使用することで、ビルドプロセスを複数のステージに分割することができます。
各ステージで環境に依存しない共通の設定や処理を行い、最終的なイメージに必要な要素のみを含めることができます。
これにより、ビルドの効率化や共通化が可能となります。

https://docs.docker.jp/develop/develop-images/multistage-build.html

また、docker buildコマンドの--targetオプションを使うことで、ビルドするステージを指定することができます。

実例

マルチステージビルドの実例です。
今回は、開発環境にはxdebugとcomposerコマンドをインストールし、本番環境にはcomposer installで取得した依存パッケージを展開する例を示します。

フォルダ構成
tree -I vendor/
.
├── Dockerfile
├── composer.json
└── composer.lock
Dockerfile
# syntax=docker/dockerfile:1.4
FROM php:8.2-fpm-alpine as build

# ここで色々インストールする

FROM build as dev

# composer本体を持ってくる
COPY --link --from=composer:latest /usr/bin/composer /usr/bin/

# 開発環境だけxdebugを入れる
RUN <<EOF
    apk add gcc g++ make autoconf linux-headers
    pecl install xdebug-3.2.1
    docker-php-ext-enable xdebug
EOF

# 本番環境用に依存パッケージをインストールする
FROM composer:latest as dependency

COPY composer.* /app/

RUN composer install \
    --no-dev \
    --ignore-platform-reqs \
    --no-interaction \
    --prefer-dist \
    --no-plugins \
    --no-scripts \
    --no-autoloader

COPY --link ./ /app/

RUN composer dump-autoload \
    --no-dev \
    --optimize

FROM build as prd

# 依存バッケージを含むアプリケーションに必要なファイル郡を持ってくる
COPY --link --from=dependency /app/ /var/www/

開発用にビルドしてみます。

docker build . --target dev -t example-dev

[+] Building 2.1s (13/13) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                                                   0.0s
 => => transferring dockerfile: 74B                                                                                                                                                    0.0s
 => [internal] load .dockerignore                                                                                                                                                      0.0s
 => => transferring context: 2B                                                                                                                                                        0.0s
 => resolve image config for docker.io/docker/dockerfile:1.4                                                                                                                           0.9s
 => CACHED docker-image://docker.io/docker/dockerfile:1.4@sha256:9ba7531bd80fb0a858632727cf7a112fbfd19b17e94c4e84ced81e24ef1a0dbc                                                      0.0s
 => [internal] load .dockerignore                                                                                                                                                      0.0s
 => [internal] load build definition from Dockerfile                                                                                                                                   0.0s
 => [internal] load metadata for docker.io/library/composer:latest                                                                                                                     0.8s
 => [internal] load metadata for docker.io/library/php:8.2-fpm-alpine                                                                                                                  0.9s
 => [build 1/1] FROM docker.io/library/php:8.2-fpm-alpine@sha256:f5512b29e2af3ca3d0f9cf9f8b2f7c04020bfc6178559699c58a15563ee3701f                                                      0.0s
 => FROM docker.io/library/composer:latest@sha256:a43a90c9e746f6c3f967cd3489ed0d0ddc6b09f666f3f93ebc8aeb3aa94a562a                                                                     0.0s
 => CACHED [dev 1/2] COPY --link --from=composer:latest /usr/bin/composer /usr/bin/                                                                                                    0.0s
 => CACHED [dev 2/2] RUN <<EOF (apk add gcc g++ make autoconf linux-headers...)                                                                                                        0.0s
 => exporting to image                                                                                                                                                                 0.0s
 => => exporting layers                                                                                                                                                                0.0s
 => => writing image sha256:26a4631bcab9b486bd1ab794d7f25c02490f646704004222411b6c941a9b6ade                                                                                           0.0s
 => => naming to docker.io/library/example-dev                                                                                                                                         0.0s

本番用にビルドしてみます。

docker build . --target prd -t example-prd

[+] Building 2.1s (17/17) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                                                   0.0s
 => => transferring dockerfile: 74B                                                                                                                                                    0.0s
 => [internal] load .dockerignore                                                                                                                                                      0.0s
 => => transferring context: 2B                                                                                                                                                        0.0s
 => resolve image config for docker.io/docker/dockerfile:1.4                                                                                                                           0.8s
 => CACHED docker-image://docker.io/docker/dockerfile:1.4@sha256:9ba7531bd80fb0a858632727cf7a112fbfd19b17e94c4e84ced81e24ef1a0dbc                                                      0.0s
 => [internal] load build definition from Dockerfile                                                                                                                                   0.0s
 => [internal] load .dockerignore                                                                                                                                                      0.0s
 => [internal] load metadata for docker.io/library/php:8.2-fpm-alpine                                                                                                                  0.7s
 => [internal] load metadata for docker.io/library/composer:latest                                                                                                                     0.9s
 => [build 1/1] FROM docker.io/library/php:8.2-fpm-alpine@sha256:f5512b29e2af3ca3d0f9cf9f8b2f7c04020bfc6178559699c58a15563ee3701f                                                      0.0s
 => [dependency 1/5] FROM docker.io/library/composer:latest@sha256:a43a90c9e746f6c3f967cd3489ed0d0ddc6b09f666f3f93ebc8aeb3aa94a562a                                                    0.0s
 => [internal] load build context                                                                                                                                                      0.1s
 => => transferring context: 174.86kB                                                                                                                                                  0.1s
 => CACHED [dependency 2/5] COPY composer.* /app/                                                                                                                                      0.0s
 => CACHED [dependency 3/5] RUN composer install     --no-dev     --ignore-platform-reqs     --no-interaction     --prefer-dist     --no-plugins     --no-scripts     --no-autoloader  0.0s
 => CACHED [dependency 4/5] COPY --link ./ /app/                                                                                                                                       0.0s
 => CACHED [dependency 5/5] RUN composer dump-autoload     --no-dev     --optimize                                                                                                     0.0s
 => CACHED [prd 1/1] COPY --link --from=dependency /app/ /var/www/                                                                                                                     0.0s
 => exporting to image                                                                                                                                                                 0.0s
 => => exporting layers                                                                                                                                                                0.0s
 => => writing image sha256:6ec271c2c64dde8cd8ef115f5ced5e627d272f85af0e632afbff7cf74aed6580                                                                                           0.0s
 => => naming to docker.io/library/example-prd                                                                                                                                         0.0s

開発用コンテナの中身を確認してみます。

開発用コンテナの中身を確認
docker run -it example-dev sh
/var/www/html # php -v
PHP 8.2.7 (cli) (built: Jun  9 2023 01:08:41) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.7, Copyright (c) Zend Technologies
    with Xdebug v3.2.1, Copyright (c) 2002-2023, by Derick Rethans
/var/www/html # composer
   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 2.5.7 2023-05-24 15:00:39

xdebugとcomposerコマンドがインストールされていることが確認できました。

今度は本番用コンテナの中身を確認してみます。

本番用コンテナの中身を確認
docker run -it example-prd sh
/var/www/html # php -v
PHP 8.2.7 (cli) (built: Jun  9 2023 01:08:41) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.7, Copyright (c) Zend Technologies
/var/www/html # composer
sh: composer: not found

xdebugもcomposerコマンドもインストールされていないことが確認できました。

イメージサイズも比較してみましょう。

イメージサイズの比較
docker images
REPOSITORY      TAG              IMAGE ID       CREATED              SIZE
example-prd     latest           6ec271c2c64d   About a minute ago   81.1MB
example-dev     latest           26a4631bcab9   About a minute ago   359MB

devには色々インストールしているので、容量にかなりの差があることが分かります。

サンプルコードはこちらにあります。

https://github.com/isanasan/example-for-dockerfile-standardization-across-environments/tree/main/example-multistage-build

まとめ

  • コンテナのメリットを最大限に生かすために、複数環境で共通のDockerfileを使う
  • ARGステートメントを用いて、環境毎に読み込む設定ファイルを切り替えることが出来る
  • マルチステージビルドを用いて、環境毎にビルドするステージを指定することが出来る

参考文献

https://speakerdeck.com/na_it_o/phperkaigi-2022

Discussion