docker-composeでApacheコンテナ起動時にcommandを実行する

昨年の2月くらいからリモートで業務するようになってから、1ヶ月くらいの出社期間を挟んでそろそろ1年近くになってきました。リモートでの業務自体は過去にフルリモートで数年勤務してたこともあるので問題はなかったのですが、最近テレカンの際に自分でも引くくらい話すのが下手になっているのを実感します。結構真面目に危機感を覚えました。

それはさておき掲題の件ですが、ぐーぐる先生に聞いても全然解決策が見つけられず、結構久々に苦戦しました。何かこれ、以前にも同じようなことでハマっていたような記憶があるので、備忘録として残しておきます。長くなってしまったので、お急ぎの方は「修正後」をご参照ください。

# 何がしたかったのか

`php:7.4.12-apache`をベースに作成したApacheのコンテナ時に、シェルスクリプトを走らせたかったという所から始まりました。実際には`wp-cli`を使ってWordPressを入れていて、固定ページなどの実装をgit管理出来るように擬似的なマイグレーションの仕組みを作ったのですが、いちいちコンテナに入ってコマンド実行というのがイケてない気がしたのでこれならば`docker-compose up`するだけで勝手に適用されるかなというのが発端です。

# 何をしたのか

一部抜粋の形ですが、`docker-compose.yml`と`dockerfile`はこんな感じです。

```dockerfile:dockerfile

FROM php:7.4.12-apache

# config file
COPY ./php.ini /usr/local/etc/php/

# apt
RUN apt-get update \
  && apt-get install -y libfreetype6-dev libjpeg62-turbo-dev libpng-dev libonig-dev mariadb-client \
  && docker-php-ext-install pdo_mysql mysqli gd iconv

# xdebug
RUN pecl install xdebug && \
    docker-php-ext-enable xdebug
COPY ./xdebug.ini /usr/local/etc/php/conf.d/

RUN a2enmod rewrite

# use bash shell
SHELL ["/bin/bash", "-l", "-c"]

EXPOSE 80

CMD ["apache2-foreground"]
```

多分割とよく見る感じのdockerfileかと思います。

```yaml:docker-compose.yml

version: '3.7'

services:
  app:
    depends_on:
      - db
    build: ./docker/app
    command: bash -c "sh /var/www/bin/migrations.sh"
    volumes:
      - ./bin:/var/www/bin
      - ./.data/app:/var/www/html:delegated
      - ./.data/log/app/:/var/log/apache2/
    restart: always
    ports:
      - "8888:80"
```

`command`にシェルスクリプトの実行コマンドを渡すことで、コンテナ起動時に自動で実行されるようにしました。

これで、コンテナ再起動するだけで都度`/var/www/bin/migrations.sh`が実行されるので、プロジェクトメンバーの負担も軽減出来るかなという発想でした。

# 何が起こったのか

シェルスクリプト自体は正常に動作していましたが、`Docker Desktop`でコンテナのログを見てるとシェルスクリプトの実行ログがループして出力されているような状態に陥りました。最初はシェルスクリプトが終了したら再度実行されている？と思ってシェルスクリプトに`sleep 30d`とか乱暴なことを書いていましたが、よくよく調べてみるとどうも違いそうです。こんなのコミットしたらぶん殴られてる所でした。

半ギレしながら調査していた所、そうではなく、コンテナがひたすら再起動を繰り返しているというのが実際のようです。`Docker Desktop`のコンテナステータスをよくよく見ていれば前述のようなわけのわからないことをしてなかった気もします。

# 何故起こったのか

* シェルスクリプトが終了するとフォアグラウンドで作動しているプロセスが不在になり、コンテナは終了する

というのが原因のようです。この原因に到達した時に、物凄いデジャヴュを感じました。多分以前の案件でもやっぱり同じことをやらかしていると思います。また、この時点で、解決策に至れていなかったのは完全に見落としだったと思います。

# 修正しましょう

先程も書いたように、フォアグラウンドで作動するプロセスがなくなったらコンテナが終了するわけですから、フォアグラウンドで何かを動かし続ければいいのでしょう。ということで、乱暴に修正してみます。出典を記録し損ねてしまったのですが、「`bash`を対話モードで動作させればコンテナは終了しない」というのを見たのでやってみます。

```diff:docker-compose.yml

-   command: bash -c "sh /var/www/bin/migrations.sh"
+   command: bash -c "sh /var/www/bin/migrations.sh && /bin/bash"
```

`Docker Desktop`のコンテナログを見ている限り、先程のようにログが繰り返し出力されなくなりました。コンテナのステータスもRUNNINGのままですので、上手くいったように思えます。試しにこの状態で、コンテナを起動している`localhost:8080`にアクセスしてみます。

すると、`ERR_EMPTY_RESPONSE`と返されました。うんともすんともです。

## 修正がダメだった理由

`ERR_CONNECTION_REFUSED`じゃなくて`ERR_EMPTY_RESPONSE`なのは想定していなかったので、一瞬頭が真っ白になりましたが、冒頭の`dockerfile`を開いてみてようやく気づきました。

* `docker-compose.yml`で`command`を定義すると、`dockerfile`の`CMD`は無視される
    * つまり`dockerfile`の`CMD ["apache2-foreground"]`は実行されていない
* そのため、ApacheのコンテナなのにApacheが起動していない状態になっている

そりゃ見れるはずもありませんね…。

# 修正後

Apacheのコンテナを起動時に`command`でコマンドを渡す際にApacheの起動コマンドも一緒に渡すようにしました。

```diff:docker-compose.yml

-   command: bash -c "sh /var/www/bin/migrations.sh && /bin/bash"
+   command: bash -c "sh /var/www/bin/migrations.sh && apache2-foreground"
```

先程と同様に`localhost:8080`にアクセスしてみると、見慣れた画面が帰ってきました。軽く確認した限りでは、概ね問題はなさそうです。これがベストプラクティスなのかはちょっと懐疑的ではあるんですが、ひとまずは期待していた動作を確認出来るようになったので今日の所はこれで勘弁してやろうと思います。