はじめに
これは、ALH Advent Calendar 2022 17日目の記事です。
きっかけ
半年前、Docker環境の構築を行いました。理屈は理解していたつもりでしたが、初めてだったのでそれなりに苦戦しました。そんな過去の自分に贈る記事を書き上げてみました。
要求
- デプロイ環境:Amazon ECS
- DB:Amazon RDS
- デプロイ方法:AWS CodePipelineを使用(AWS CodeCommit->AWS CodeBuild->Amazon ECS)
(ローカルなどで)docker compose
で動かせると同時に、ECSで動作させることも視野に入れた構築手順としています。
想定読者
- なんとなくDockerのことは理解できたけど環境構築をしたことがない人
- とりあえずLaravelをDockerで動かしたい人
構成
コンテナ構成は以下のようにします。
- アプリケーションコンテナ:PHP
- Webサーバコンテナ:nginx
- DBコンテナ:PostgreSQL
これから記す設定例で指定しているバージョンは、2022年12月時点での最新orLTSですので参考程度に見てください。
なおデプロイ時にはAmazon RDSを使用するため、PostgreSQLはローカルでの開発専用です。
.
├─ php
│ ├─ Dockerfile
│ ├─ zz-docker.conf
│ ├─ php.ini
│ └─ example-app <-Laravelプロジェクト
│ ├─ app
│ ├─ bootstrap
│ :
│ :
│ :
│ └─ vite.config.js
│
├─ nginx
│ ├─ Dockerfile
│ ├─ default.conf
│ └─ example-app
│ └─ .gitkeep
│
└─ docker-compose.yml
こういった記事では、phpディレクトリやnginxディレクトリと同じようにLaravel単体のディレクトリを作成するという構成をよく見ますが、今回はphpディレクトリ配下にLaravelのソースを配置しています。
と言いますのも、後でDockerfileでLaravelのソースをCOPY
する処理をしているのですが、Laravel単体でディレクトリを作った場合、COPY ../example-app /var/www/html/example-app
となり、この場合ビルド時にエラーになります(Dockerfileから親ディレクトリを参照できないため)。なのでphpディレクトリ配下にソースを配置しています。
Dockerfile
アプリケーションコンテナ
FROM php:8.1.10-fpm
RUN apt-get update && apt-get install -y \
unzip \
emacs \
libpq-dev
RUN docker-php-ext-install pdo pdo_pgsql pgsql
# Composerのインストール
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
RUN php composer-setup.php --install-dir=/usr/local/bin --filename=composer
RUN php -r "unlink('composer-setup.php');"
COPY zz-docker.conf /usr/local/etc/php-fpm.d/zz-docker.conf
COPY php.ini /usr/local/etc/php/php.ini
RUN mkdir -p /var/www/html/example-app
COPY ./example-app /var/www/html/example-app
WORKDIR /var/www/html/example-app
FROM php:8.1.10-fpm
まず、今回はWebサーバで動かすので、ベースイメージにはFPM版を使用します。
RUN apt-get update && apt-get install -y \
unzip \
emacs \
libpq-dev
RUN docker-php-ext-install pdo pdo_pgsql pgsql
Composerを利用するためにunzipをインストールしています(Gitでも可)。
コンテナ内のファイルを直接編集したいことがあるので、Emacsを入れています(Emacsを選んだのは個人の趣味ですので、Vimなりnanoなりご自由にどうぞ)。
PostgreSQLを利用するため、docker-php-ext-install
コマンドで拡張モジュールのpdo
pdo_pgsql
pgsql
をインストールしています。またそれらモジュールのインストールのために、先にlibpq-dev
パッケージを入れています。
COPY zz-docker.conf /usr/local/etc/php-fpm.d/zz-docker.conf
FPMの設定ファイルをコンテナ側にコピーしています。こちらにあるように、zz-docker.conf
が最終的にFPMの設定を上書きするため、zz-docker.conf
以外のファイルに設定をしても反映されません。
ちなみにzz-docker.conf
の設定は以下のようにしました。
[global]
daemonize = no
[www]
listen = /var/run/php-fpm/php-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0666
access.log = /proc/self/fd/1
RUN mkdir -p /var/www/html/example-app
COPY ./example-app /var/www/html/example-app
Laravelアプリを置くディレクトリを作成し、ホスト側のファイルをコンテナ側にコピーしています。一般的にはホスト側でソースを編集して、かつその編集を即時に反映させるためバインドマウントしますが、AWS CodeBuildでのビルドのためこの段階でコンテナ側にソースを反映しています。
※この設定のままだと、ソースを変更したらその都度コンテナイメージを再ビルドすることになるので後でバインドマウントします。
WORKDIR /var/www/html/example-app
docker execした際に多少便利なので(すぐアプリケーションのソースを見られる)、Laravelアプリのディレクトリをワーキングディレクトリとしています。
Webサーバコンテナ
FROM nginx:1.20.1
# 設定ファイルをコンテナ側にコピー
COPY default.conf /etc/nginx/conf.d/default.conf
RUN mkdir -p /var/www/html/example-app
COPY ./example-app /var/www/html/example-app
default.confはLaravel公式リファレンスに設定例がありますので、それを参考にしてください。
RUN mkdir -p /var/www/html/example-app
COPY ./example-app /var/www/html/example-app
こちらにあるように、nginxのコンテナにもLaravelのソースをコピーする必要があります。ビルド時に直接php/example-app
は参照できないので(親ディレクトリを経由するため)、以下の対応としました。
- デプロイ環境に反映:AWS CodeBuildの
buildspec
でcp -r php/example-app/. nginx/example-app
をnginxコンテナをビルドする直前に実行 - ローカル:
docker-compose.yml
でphp/example-app
をバインドマウント
docker-compose.yml
version: '3.3'
services:
app:
container_name: example-app
build:
context: ./php
dockerfile: ./Dockerfile
volumes:
- type: volume
source: php-fpm-socket
target: /var/run/php-fpm
volume:
nocopy: true
- type: bind
source: ./php/example-app
target: /var/www/html/example-app
- type: bind
source: ./php/zz-docker.conf
target: /usr/local/etc/php-fpm.d/zz-docker.conf
- type: bind
source: ./php/php.ini
target: /usr/local/etc/php/php.ini
environment:
- DB_HOST=db
- DB_DATABASE= # 例:example-db
- DB_USERNAME= # 例:postgres
- DB_PASSWORD= # 例:postgres
depends_on: ["db"]
web:
container_name: example-web
build:
context: ./nginx
dockerfile: ./Dockerfile
ports:
- target: 8080
published: 8000
protocol: tcp
mode: host
volumes:
- type: volume
source: php-fpm-socket
target: /var/run/php-fpm
volume:
nocopy: true
- type: bind
source: ./php/example-app
target: /var/www/html/example-app
- type: bind
source: ./nginx/default.conf
target: /etc/nginx/conf.d/default.conf
depends_on: ["app"]
db:
container_name: example-db
image: postgres:14.5
ports:
- target: 5432
published: 5432
protocol: tcp
mode: host
volumes:
- type: volume
source: db-store
target: /var/lib/postgresql/data
volume:
nocopy: true
environment:
- POSTGRES_DB= # 例:example-db
- POSTGRES_USER= # 例:postgres
- POSTGRES_PASSWORD= # 例:postgres
volumes:
php-fpm-socket:
db-store:
アプリケーションコンテナ
volumes:
- type: volume
source: php-fpm-socket
target: /var/run/php-fpm
volume:
nocopy: true
今回はUNIXドメインソケットでnginxのコンテナと通信をさせます。
ですのでボリュームphp-fpm-socket
を/var/run/php-fpm
にマウントしています。
volumes:
# (中略)
- type: bind
source: ./php/example-app
target: /var/www/html/example-app
- type: bind
source: ./php/zz-docker.conf
target: /usr/local/etc/php-fpm.d/zz-docker.conf
- type: bind
source: ./php.ini
target: /usr/local/etc/php/php.ini
ソースの変更を即時反映されるよう、Laravelのソースをバインドマウントしています。
また、各種設定のチューニングをする必要があったため、zz-docker.conf
およびphp.ini
もバインドマウントしています。頻繁に変更をしないということであればDockerfileでのCOPY
の方がいいでしょう。
environment:
- DB_HOST=db
- DB_DATABASE= # 例:example-db
- DB_USERNAME= # 例:postgres
- DB_PASSWORD= # 例:postgres
環境変数の設定です。
項目は後述するDBに合わせていますが、具体的なパラメータは値をどこまで.env
から浮かせたいかで変わります。
depends_on: ["db"]
db
コンテナが起動してからコンテナを作成するようにしています。
Webサーバコンテナ
ports:
- target: 8080
published: 8000
protocol: tcp
mode: host
nginxコンテナのポート設定です。
- target :コンテナ内のポート
- published :公開用のポート
となりますので今回の場合、localhost:8000
に来たアクセスを(nginxの)8080
ポートに流すという処理になります。
protocol
はtcp
かudp
が、mode
はhost
かingress
がそれぞれ選択肢になります。(Webアプリケーションなので)HTTPで通信しますのでprotocol: tcp
、今回はSwarm モードを使用しないためmode: host
としています。
volumes:
- type: volume
source: php-fpm-socket
target: /var/run/php-fpm
volume:
nocopy: true
ボリュームのマウントは、アプリケーションコンテナの箇所で説明したPHPコンテナ-nginxコンテナ間の通信を行うためのものです。ですのでPHPコンテナと同様の記述となっています。
volumes:
# (中略)
- type: bind
source: ./php/example-app
target: /var/www/html/example-app
- type: bind
source: ./nginx/default.conf
target: /etc/nginx/conf.d/default.conf
一つ目のマウントはnginx/Dockerfile
のCOPY ./example-app /var/www/html/example-app
をバインドマウントとしています。
二つ目の処理はnginxの設定ファイルをバインドマウントしています。こちらもアプリケーションコンテナと同様に頻繁に変更をしないということであればCOPY
でいいでしょう。
以下のようなエラーが発生した場合、nginxコンテナ内で/var/run/php-fpm
のパーミッションを確認してみてください。所有者がroot
であればwww-data
に変更してください。
FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream (中略), upstream: "fastcgi://unix:/var/run/php-fpm/php-fpm.sock:"
DBコンテナ
image: postgres:14.5
ports:
- target: 5432
published: 5432
protocol: tcp
mode: host
volumes:
- type: volume
source: db-store
target: /var/lib/postgresql/data
volume:
nocopy: true
environment:
- POSTGRES_DB= # 例:example-db
- POSTGRES_USER= # 例:postgres
- POSTGRES_PASSWORD= # 例:postgres
こちらのコンテナは複雑な設定をしなかったので、Dockerfileを用意しておりません。
volumes:
- type: volume
source: db-store
target: /var/lib/postgresql/data
volume:
nocopy: true
DBのデータを保存するために(コンテナを立ち上げ直してもデータを使用できるように)、ボリュームdb-store
にデータベースクラスタをマウントしています。
environment:
- POSTGRES_DB= # 例:example-db
- POSTGRES_USER= # 例:postgres
- POSTGRES_PASSWORD= # 例:postgres
こちらもPHPのコンテナと同様です。しかし同じDBの接続情報ですが、キーが異なるので注意が必要です。
動作確認
docker-compose.yml
がある階層で
$ docker compose build
$ docker compose up
を実行してhttp://localhost:8000/
に接続してLaravelから何かしらのレスポンスが返ってきたら無事構築完了です。
最後に
苦労したことが多かったですが、この構築をきっかけにDockerの理解度がワンランク上がりました。慣れるとコンテナでの開発は利点が多いのでこれからも積極的に使っていきたいところです。