こんにちは
今回は以前から気になっていたDockerのマルチステージビルドを試してみたのでここに書きます
■マルチステージビルドとは
Dockerfile内で複数のビルドステージを定義することにより、最終的に利用するイメージの軽量化を実現できたり、開発環境と検証環境でDockerイメージ内の中身を調整できたりします
また、イメージを軽量化できることで、Dockerイメージの転送量を削減できたり、ECRの課金料金削減にも役立ちます
なお、マルチステージビルドを実現する際は、Dockerfile内で FROM を利用して区切っていきます
■構成
docker-composeを利用して、 Nginx + PHP-FPM + MySQL を構成しているものの中で、PHP-FPMに焦点を当ててみました
.
├── app
│ ├── Dockerfile
│ ├── conf
│ │ ├── disable_display_error.ini
│ │ └── zz-docker.conf
│ └── src
│ ├── common
│ │ └── db_connect.php
│ ├── css
│ │ └── user_list.css
│ ├── get_all_users.php
│ ├── healthchek.html
│ └── index.php
├── db
│ ├── Dockerfile
│ ├── conf
│ │ └── my.cnf
│ ├── data
│ └── sql
│ └── init.sql
├── docker-compose.yml
└── web
├── Dockerfile
└── conf
└── www.example.com.conf
■マルチステージビルド実装前の中身
今回大事なところはappの Dockerfile と docker-compose.yml になります
なので、まずはそれぞれ中身を確認します
・app/Dockerfile
FROM php:8.0.30-fpm
#php.ini用confファイルコピー
COPY ./app/conf/disable_display_error.ini /usr/local/etc/php/conf.d/
#php-fpm用confファイルコピー
COPY ./app/conf/zz-docker.conf /usr/local/etc/php-fpm.d/
RUN groupmod -g 1000 www-data
RUN usermod -u 1000 www-data
#ドキュメントルート作成
RUN apt-get update -y && \
apt-get install -y vim less && \
docker-php-ext-install pdo_mysql && \
mkdir -p /var/www/vhosts/www.example.com/public_html
COPY ./app/src /var/www/vhosts/www.example.com/public_html
# ユーザー切り替え
USER www-data
#作業ディレクトリ設定
WORKDIR /var/www/vhosts/www.example.com/public_html
#ポート開放
EXPOSE 9000
→ FROM が1つのみであり、マルチステージビルドにはなっていません
・docker-compose.yml
version: "3.9"
services:
web01:
build:
context: ./
dockerfile: web/Dockerfile
container_name: web01-container
volumes:
- ./app/src:/var/www/vhosts/www.example.com/public_html
depends_on:
- app01
ports:
- "80:80"
app01:
build:
context: ./
dockerfile: app/Dockerfile
container_name: app01-container
volumes:
- ./app/src:/var/www/vhosts/www.example.com/public_html
restart: always
ports:
- "9000:9000"
db01:
build:
context: ./
dockerfile: db/Dockerfile
container_name: db01-container
env_file: .env/db_env_file
restart: always
volumes:
- ./db/data:/var/lib/mysql
- ./db/conf/my.cnf:/etc/my.cnf
ports:
- "3307:3306"
また、この状態だとDockerイメージ容量は 500MB ほどになっています
nginx_php-fpm_mysql_multibuild_docker-app01 latest 1de25957b816 About an hour ago 500MB
■マルチステージビルドを実装する
それでは、appのDockerファイルでマルチステージビルドを実装します
今回はテストとして、devとproductionのステージを作成し、docker-compose の target でそれぞれ指定してどうなるのか確認してみます
まずはDockerファイルを下記へ修正します
#開発環境ステージのビルド
FROM php:8.0.30-fpm AS dev
#php.ini用confファイルコピー
COPY ./app/conf/disable_display_error.ini /usr/local/etc/php/conf.d/
#php-fpm用confファイルコピー
COPY ./app/conf/zz-docker.conf /usr/local/etc/php-fpm.d/
RUN groupmod -g 1000 www-data
RUN usermod -u 1000 www-data
#ドキュメントルート作成
RUN apt-get update -y && \
apt-get install -y vim less && \
docker-php-ext-install pdo_mysql && \
mkdir -p /var/www/vhosts/www.example.com/public_html
COPY ./app/src /var/www/vhosts/www.example.com/public_html
# ユーザー切り替え
USER www-data
#作業ディレクトリ設定
WORKDIR /var/www/vhosts/www.example.com/public_html
#本番環境ステージのビルド
FROM php:8.0.30-fpm-alpine3.16 AS prod
#ドキュメントルート作成
RUN mkdir -p /var/www/vhosts/www.example.com/public_html && \
docker-php-ext-install pdo_mysql
#php.iniのコピー
COPY --from=dev /usr/local/etc/php/conf.d/disable_display_error.ini /usr/local/etc/php/conf.d/
#php-fpm用confファイルコピー
COPY --from=dev /usr/local/etc/php-fpm.d/zz-docker.conf /usr/local/etc/php-fpm.d/
# ユーザー切り替え
USER www-data
#ポート開放
EXPOSE 9000
→ 新たに FROM php:8.0.30-fpm-alpine3.16 AS prod 配下を作成し、本番環境ではこちらを利用するようにします
なお、今回はベースイメージが dev と異なるので、ドキュメントルートの作成や、pdo_mysql のインストールは prod ステージでも実行する必要があります
※docker-compose.yml のtargetで何も指定しなければ、最後の prod ステージのイメージが利用されます
それでは、一度ビルドしてみます
docker-compose build app01 --no-cache
容量を確認してみます
nginx_php-fpm_mysql_multibuild_docker-app01 latest 7f1cf400b227 24 minutes ago 72.3MB
→ ベースをalpineに変更したので、500MB から 72MB まで容量が小さくなっています
次に docker-compose.yml で target を指定して、実行してみます
version: "3.9"
services:
web01:
build:
context: ./
dockerfile: web/Dockerfile
container_name: web01-container
volumes:
- ./app/src:/var/www/vhosts/www.example.com/public_html
depends_on:
- app01
ports:
- "80:80"
app01:
build:
context: ./
dockerfile: app/Dockerfile
target: prod
container_name: app01-container
volumes:
- ./app/src:/var/www/vhosts/www.example.com/public_html
restart: always
ports:
- "9000:9000"
db01:
build:
context: ./
dockerfile: db/Dockerfile
container_name: db01-container
env_file: .env/db_env_file
restart: always
volumes:
- ./db/data:/var/lib/mysql
- ./db/conf/my.cnf:/etc/my.cnf
ports:
- "3307:3306"
→ app01サービス内で target: prod を指定しています
利用されているイメージを確認してみます
docker-compose images app01
CONTAINER REPOSITORY TAG IMAGE ID SIZE app01-container nginx_php-fpm_mysql_multibuild_docker-app01 latest 7f1cf400b227 72.3MB
→ prod ステージのイメージが利用されています
最後にdocker-compose.yml内の target を dev へ変更し、実行してみます
app01:
build:
context: ./
dockerfile: app/Dockerfile
target: dev
container_name: app01-container
volumes:
- ./app/src:/var/www/vhosts/www.example.com/public_html
restart: always
ports:
- "9000:9000"
なお、変更した後はイメージの再ビルドが必要となり、再ビルドなしではそのまま prod ステージのものが利用され続けます
docker-compose down
docker-compose build app01 --no-cache
docker images | grep app01
nginx_php-fpm_mysql_multibuild_docker-app01 latest 3b17ec085d50 8 seconds ago 500MB
→ イメージの容量が 500MB へと戻りました
それでは、dev を指定した状態で起動します
docker-compose up -d
docker-compose images app01
CONTAINER REPOSITORY TAG IMAGE ID SIZE app01-container nginx_php-fpm_mysql_multibuild_docker-app01 latest 3b17ec085d50 500MB
→ 500MB となっているので、dev ステージのものとなっていますね
■所感
いかがでしたでしょうか
初めてマルチステージビルドをした割にはうまくいき、利便性も感じることができました
また、ビルドステージ毎に環境を分けることで、1枚のDockerfileを環境ごとに使いまわすことができ、docker-compose側でも target を指定するだけで、簡単にマルチ環境の構築ができるとも考えました
次はより実践的に Laravel などを利用して、マルチステージビルドを試していきたいと思います