複数の環境でDockerfileを共通化するために使えるtips
前提
コンテナを用いてアプリケーションのワークロードを構築することにはいくつかの利点があります。
なかでも、下記に上げられるポータビリティと環境の再現性は非常に強力です。
ポータビリティ
コンテナは、アプリケーションとその依存関係をコンテナ内にパッケージ化します。
これにより、開発環境で構築したコンテナを本番環境にデプロイする際にも、一貫した動作が期待できます。
異なる環境間でアプリケーションを移行する際に、互換性の問題や依存関係の不一致が生じるリスクが低減され、ポータビリティが高まります。
環境の再現性
コンテナは環境に依存しないため、開発者が特定の環境でアプリケーションを構築した場合でも、他の開発者や運用チームが同じ環境を再現することが容易です。
コンテナイメージにはアプリケーションのコードとその実行環境が含まれており、イメージを共有することで他の人が同じ環境でアプリケーションを実行できます。
これにより、開発から本番までの環境の一貫性と再現性が向上します。
モチベーション
コンテナのポータビリティと環境の再現性を最大限に生かすためには、全環境で共通のコンフィグを利用する必要があります。
本稿ではPHPアプリケーションをdockerで構築する例を題材に、dockerfileを複数の環境で共通化するために使えるtipsを紹介します。
ARGステートメントを使用する
Dockerfile内でARGステートメントを使用して、ビルド時に渡される引数を定義することができます。これにより、環境ごとに異なる値を渡すことができます。
例えば、ベースイメージのタグやポート番号など、環境に依存する値をARGステートメントで宣言し、ビルド時に引数として渡すことができます。
公式ドキュメントに記載はありませんが、巷ではARGステートメントで渡す値のことをビルド引数(build argument)などと呼ぶようです。
余談ですが、ENV
とARG
は全く違うものなので注意が必要です。
実例
ARGステートメントの実例です。
今回は、php.iniを環境毎に別ディレクトリで管理し、ARGステートメントを用いてコンテナのビルド時にコピー元を切り換える例を示します。
.
├── Dockerfile
├── dev
│ └── php.ini
└── prd
└── php.ini
# 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がコピーされました。
サンプルコードはこちらにあります。
マルチステージビルドを使用する
Dockerのマルチステージビルドを使用することで、ビルドプロセスを複数のステージに分割することができます。
各ステージで環境に依存しない共通の設定や処理を行い、最終的なイメージに必要な要素のみを含めることができます。
これにより、ビルドの効率化や共通化が可能となります。
また、docker build
コマンドの--target
オプションを使うことで、ビルドするステージを指定することができます。
実例
マルチステージビルドの実例です。
今回は、開発環境にはxdebugとcomposerコマンドをインストールし、本番環境にはcomposer install
で取得した依存パッケージを展開する例を示します。
tree -I vendor/
.
├── Dockerfile
├── composer.json
└── composer.lock
# 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には色々インストールしているので、容量にかなりの差があることが分かります。
サンプルコードはこちらにあります。
まとめ
- コンテナのメリットを最大限に生かすために、複数環境で共通のDockerfileを使う
- ARGステートメントを用いて、環境毎に読み込む設定ファイルを切り替えることが出来る
- マルチステージビルドを用いて、環境毎にビルドするステージを指定することが出来る
参考文献
Discussion