1
4

More than 1 year has passed since last update.

半年前の自分に捧げる、DockerでのLaravel環境構築例

Last updated at Posted at 2022-12-30

はじめに

これは、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

アプリケーションコンテナ

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の設定は以下のようにしました。

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サーバコンテナ

nginx/Dockerfile
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のbuildspeccp -r php/example-app/. nginx/example-appをnginxコンテナをビルドする直前に実行
  • ローカル:docker-compose.ymlphp/example-appをバインドマウント

docker-compose.yml

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ポートに流すという処理になります。
protocoltcpudpが、modehostingressがそれぞれ選択肢になります。(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/DockerfileCOPY ./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の理解度がワンランク上がりました。慣れるとコンテナでの開発は利点が多いのでこれからも積極的に使っていきたいところです。

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4