目的
- DockerベースのWeb開発を行う際に開発者のPCがWindows, Mac(Linux)で混在している場合に生じる問題を回避する
動機
- 以前、自分がMac、他のエンジニアたちがWindowsという構成の新規開発のチームを率いたときにこの問題を解決する必要が生じた
問題点の確認
- Dockerベースで開発するシステムなのにDockerの外で実行されるビルド用などのスクリプトが混在している
- Mac PCに慣れた開発者ほど無自覚にそうしてしまいがち
- つまり、Dockerの中で動かすプログラムが仮にWindows機上でうまく動くように配慮されていたとしても、ビルド用スクリプトが直接ホスト上で実行される構成となっていればにそこでコケる
運用方法
[1] Windowsを捨てる
チームの開発PCの構成がMac主体でWindows機の影響を無視できる場合、Windows対応を捨ててしまうというのが何も考えなくて良くなるためある意味最小の解決策となります。
当然ながらWindows機では開発環境の構築や運用ができないソースツリーとなるため、Windows機上で先にUbuntuのWSL2ホストをインストールし、その中でソースツリーを展開してDockerを起動します。
つまり、Mac, Linux PCの場合にシステムはこのような構成ですが、
PC -> Dockerコンテナ
Windows PCではこのような構成になります。
PC -> Ubuntu(WSL2ホスト) -> Dockerコンテナ
つまりWindows PCにおいてもソースツリーを扱う主体がLinux(WSL2)となるため、MacやLinux PCとも理論上差のない形で開発環境を運用できることになります。
こういった運用については既に記事を執筆済みです。
- Windows環境(WSL2)のDockerを爆速化する方法
こちらの記事では軽量LinuxディストロのAlpine Linuxを使って構築していますが、GitHubをプロジェクト管理ツールとしている場合、GitHub cli
がAlpineでは動作しないためUbuntuの方を推奨します。
[2] ビルド用スクリプト等も含め、全てのスクリプト、プログラムの実行をDocker内で統一する(本記事)
システムの開発環境の構築やシステムのビルド時に主に必要になってくる環境依存の作業とは、以下が挙げられます。
- pypi(Python), composer(PHP), npm(Node.js)などのシステムのプログラムが必要としている外部パッケージの導入
- JavaScript, CSS等、システムのプログラムが採用しているプログラミング言語(以下メイン言語)の外でHTML5を構成する諸要素を個別にビルドする
これらの作業をチーム各人のPC環境で直接実行するような実装を行うと環境依存の問題が発生します。
したがってこの問題を解決するにはこういった作業もDocker内での実行に統一すべきです。
Dockerでのビルド等のコマンド実行には主に2つの方法があります。
Dockerfileに記述した初期構築スクリプトの問題
一般的にDockerによる開発ではDockerfile、もしくはdocker-compose.ymlに記述された設定に従って各コンテナを定義し、そのコンテナは起動が完了すると特定のポートを開いて待機状態になるはずです。そういったDockerfileは最後のコマンドに EXPOSE
命令が使われています。
以下は実例として docker-laravel-standard
を例に取ります。
- docker-laravel-standard
例: https://github.com/kanryu/docker-laravel-standard/blob/main/docker/php/Dockerfile
FROM php:8.1-fpm
COPY php.ini /usr/local/etc/php/
RUN apt-get -y update \
&& apt-get install -y zlib1g-dev zip mariadb-client vim curl gnupg \
libzip-dev libfreetype6-dev libjpeg62-turbo-dev libpng-dev libmcrypt-dev \
&& docker-php-ext-install zip pdo_mysql gd iconv exif
RUN apt-get install libyaml-dev -y
RUN pecl install yaml && echo "extension=yaml.so" > /usr/local/etc/php/conf.d/ext-yaml.ini && docker-php-ext-enable yaml
RUN pecl install xdebug
#node.js install
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash -
RUN apt-get install -y nodejs
RUN npm install npm@latest -g
#Composer install
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
RUN php composer-setup.php
RUN php -r "unlink('composer-setup.php');"
RUN mv composer.phar /usr/local/bin/composer
ENV COMPOSER_ALLOW_SUPERUSER 1
ENV COMPOSER_HOME /composer
ENV PATH $PATH:/var/www/laravel-app/vendor/bin
WORKDIR /var/www
RUN composer global require "laravel/installer"
EXPOSE 5173
この Dockerfile
では PHP、node.js、composerの初期設定が行われていますが、composer準拠のPHPパッケージのインストール composer install
は実行されません。
Dockerfile内に以下のように書けば実際に実行してくれるのですが、これでは実運用に問題があります。
RUN composer install
なぜかというとDockerfileの各RUNコマンドは docker build
実行時にただ1度だけ実行され、2回目以降Dockerコンテナを起動した際などには実行されないためです。
システム開発の成長過程で package.json(node.js) composer.json(PHP)などのパッケージ設定ファイルを更新し、パッケージの構成を変更することはままあることですが、システムのDockerコンテナを破棄して再構築しなければ composer install
を実行してくれないということになります。それは不便ですね。
ビルドスクリプトのDocker化
[1] CMDの活用
Dockerコンテナにはもう一つの実行モードがあり、それは CMD
コマンドを書いた場合です。
この場合、CMDに書かれたコマンドの実行が終了すると、Dockerコンテナごとクローズされます。
このコマンド化された Dockerコンテナは、他の通常のDockerコンテナと同じように扱われ、コンテナ群全体の起動時に毎回実行されることになります。
例えば、 composer install
を実行する CMD型 Dockerコンテナは docker-compose.yml 上でこのように書けます。
例: https://github.com/kanryu/docker-laravel-standard/blob/main/docker-compose.yml
build-app:
container_name: docker_laravel_build_app
build: ./docker/php
command:
- /bin/sh
- -c
- |
composer install
npm install
npm run build
volumes:
- ./laravel-app:/var/www
- ./docker/php/dev/php-xdebug.ini:/usr/local/etc/php/conf.d/php-xdebug.ini:ro
- ./docker/php/php.ini:/usr/local/etc/php/php.ini:ro
depends_on:
- app
注意点として、普通にビルドスクリプトを記述していれば同じビルドを2回実行したとしても問題がないはずですが、2回実行したときに問題が出るようなスクリプトを書かないようにしてください。
CMD型Dockerコンテナは当然ながらDockerコンテナそのものですので、 depends_on
指定を行うことでDockerコンテナ同士の起動時の順番を制御することができます。この場合、システム本体のコンテナである app
の起動が完了してから build-app
コンテナは実行されるわけですね。
volumes
を適切に設定することで、2つ以上のDockerコンテナで同じファイルを共有することが可能です。
[2] docker-compose exec越しに実行する
Docker内でコマンドを実行するにはもう一つ方法があり、こちらのほうがむしろ一般的でしょう。
dockerコマンド、もしくは docker-compose コマンドでホストからDockerコンテナ内の任意のコマンドを実行できます。
例えば app
コンテナ内の composer install
を実行したい場合、このように記述できるでしょう。
docker-compose exec app composer install
ソースツリー上の構築作業について、詳細な作業は全てDocker内で実行し、その開始点のみ docker-compose等の代理人に実行させるルールを徹底すればWindows環境でもMac(Linux)環境でも同じように運用できることになります。
まとめ
ここまで3通りの解決手段をご提示しました。
みなさんのシステム開発に役立てば嬉しいです。
なにか疑問、質問、感想等あれば気軽にコメントしてください。