Dockerコンテナ初回起動時にLaravelの環境をセットアップする方法を求めて

## 追記(2020/07/24)
コメント欄や社内の先輩から教わったのですが、初回起動判定を仕込んだシェルスクリプトをDockerfileのENTRYPOINTに指定すれば、本記事の目的は達成できます。

今回の目的であれば.envファイルやComposerのインストール先フォルダの存在チェックをしてセットアップ済みかどうかを判定するようにすれば、初回セットアップを自動化できます。


## TL;DR
Docker環境でLaravelプロジェクトをセットアップするときに色々頑張った結果、セットアップ手順の簡略化はMakefileが一番マシという結論に至ったので、努力の軌跡を記します。


## 経緯
とある案件にアサインされたときに、環境構築手順がメンテされておらず煩雑になっていたので、どうにかして改善できないか、色々と調査を始めたのがキッカケでした。

まず、3つのLaravelプロジェクトが1つのWebサーバーコンテナ(`php:7.0-apache`)でまとめられていたので、セットアップの際にこれだけのコマンドを実行する必要がありました。

```shell-session
$ docker-compose up --build -d
$ docker-compose exec web bash -c "cd api && composer install"
$ docker-compose exec web bash -c "cd cms && composer install"
$ docker-compose exec web bash -c "cd stub && composer install"
$ cp api/.env.example api/.env
$ cp cms/.env.example cms/.env
$ cp cms/.htaccess.sample cms/public/.htaccess
$ cp stub/.env.example stub/.env
```

また、複合主キーを使っている関係でLaravelではマイグレーションできないので、ローカルの開発の際にはphpMyAdminからDB定義とシーディングのためのSQLを流し込む必要もありました。


## セットアップ手順改善の流れ
### 1. 各種コマンドの一括実行
始めにセットアップに必要なファイルをdocker-composeが配置されているリポジトリに集約して、ディレクトリ構成の階層も見直し、コマンド群をシェルスクリプトに全部まとめました。

```sh:init.sh
#!/bin/bash

# イメージ・コンテナの作成
echo "$ docker-compose up --build -d"
docker-compose up --build -d

# 各リポジトリのパッケージをインストール
echo '$ docker-compose exec web bash -c "cd api && composer install"'
docker-compose exec web bash -c "cd api && composer install"
echo '$ docker-compose exec web bash -c "cd cms && composer install"'
docker-compose exec web bash -c "cd cms && composer install"
echo '$ docker-compose exec web bash -c "cd stub && composer install"'
docker-compose exec web bash -c "cd stub && composer install"

# 設定ファイル群をコピー
echo "$ cp conf/api.env ../api/.env"
cp conf/api.env ../api/.env
echo "$ cp conf/cms.env ../cms/.env"
cp conf/cms.env ../cms/.env
echo "$ cp conf/cms.htaccess ../cms/public/.htaccess"
cp conf/cms.htaccess ../cms/public/.htaccess
echo "$ cp conf/stub.env ../stub/.env"
cp conf/stub.env ../stub/.env
```

まずはこれを叩き台に、SQLを流し込むコマンドを追加しようとしたりしていました。


### 2. SQLの自動流し込み
その後調査した結果、実行してほしいSQLファイルをディレクトリに入れてボリュームとして`docker-entrypoint-initdb.d`にマウントすればコンテナの初回起動時にSQLを自動で流し込んでくれることを知りました。


### 3. コンテナ初回起動時に各種コマンドの自動実行？
`docker-entrypoint-initdb.d`などを活用すれば、Dockerの設定項目のみでコンテナの作成からLaravelプロジェクトのセットアップまでまとめてできるのではないか？　との仮説を立てました。

.envファイルのコピーとパッケージのインストールは各プロジェクトディレクトリ内で実行しないといけないので、少なくともコンテナの作成時には実行できません（ボリュームがマウントされていないため）。

どうにかしてコンテナの起動時に実行できないか色々と設定を調べたところ、`docker-compose.yml`の設定値にコンテナ起動時のコマンドを指定するものがあったので、下記のように設定してみました。

```yml
command: >
  bash -c "
    cd api &&
    composer install &&
    cp .env.example .env &&
    cd ../cms &&
    composer install &&
    cp .env.example .env &&
    cd ../stub &&
    composer install &&
    cp .env.example .env &&
    source /etc/apache2/envvars &&
    apache2 -D FOREGROUND
  "
```

これでWebサーバーコンテナの初回起動時に上記のコマンドが実行され、Dockerリポジトリをクローンしてきて`docker-compose run --build -d`するだけでローカル開発環境を構築できるようになりました。


## 泡沫の夢
Dockerに詳しい方ならすぐに気が付いたかもしれませんが、上記の設定はあくまでもコンテナの起動時に実行されるコマンドを設定するものです。
当たり前ですがコンテナの**初回起動時にのみ**実行するものではありません。
当時の僕は環境構築手順を簡略化できた喜びに目が眩み、この簡単な事実に全く気付かず、夢から目が醒めたのは随分と時間が経ってのことでした。


## 原点回帰
その後も色々と考えてみましたが、コンテナの初回起動時にのみ特定のコマンドを実行する冴えた方法が見つからないので、今後の拡張性も踏まえてMakefileでタスクを定義することにしました。

```text:Makefile
init:
	docker-compose up -d --build
	cp conf/api.env ../api/.env
	cp conf/cms.env ../cms/.env
	cp conf/cms.htaccess ../cms/.htaccess
	cp conf/stub.env ../stub/.env
	docker-compose exec web bash -c "cd api && composer install"
	docker-compose exec web bash -c "cd cms && composer install"
	docker-compose exec web bash -c "cd stub && composer install"

--以下略--
```


## 結論
Dockerの設定のみでLaravelのセットアップを1コマンドで終わらせるのは(少なくとも僕には)無理なので、手順の簡略化にあまりコストをかけたくないのであれば、おとなしくMakefileでも使いましょう。
<font color="#F0F0F0">ところでどなたかコンテナの初回起動時にのみコマンドを実行する方法を御存知でしたらご教示ください(切実)。</font>