コンテナーを利用した PHP 開発

前提条件

PHP アプリケーションのコンテナー化 を完了していること。

概要

本節ではコンテナー化したアプリケーションに対する開発環境の構築方法を学びます。 ここでは以下を行います。

  • ローカルデータベースを追加してデータを保存します。
  • データベースとの処理のため phpMyAdmin を追加します。
  • Compose の設定を通じて、コード編集および保存とともに、実行中の Compose サービスが自動的に更新されるようにします。
  • dev 依存パッケージを含んだ開発用コンテナーを生成します。

ローカルデータベースの追加とデータ保存

コンテナーでは、ローカルサービスとしてたとえばデータベースを構築することができます。 サンプルアプリケーションにおいてそれを実現するためには、以下を行っていくことが必要です。

  • Dockerfile を修正して、データベース接続を行う拡張モジュールをインストールします。
  • compose.yaml ファイルを修正して、データベースサービスの追加、およびデータ保存のためのボリューム追加を行います。

拡張モジュールインストールのための Dockerfile 更新

PHP 拡張をインストールするには Dockerfile を修正する必要があります。 IDE またはテキストエディターを使って Dockerfile を開き、内容を修正します。 以下の Dockerfile では新たな行を挿入し、そこで pdo および pdo_mysql 拡張モジュールをインストールします。 コメントはすべて削除しています。

# syntax=docker/dockerfile:1

FROM composer:lts as deps
WORKDIR /app
RUN --mount=type=bind,source=composer.json,target=composer.json \
    --mount=type=bind,source=composer.lock,target=composer.lock \
    --mount=type=cache,target=/tmp/cache \
    composer install --no-dev --no-interaction

FROM php:8.2-apache as final
RUN docker-php-ext-install pdo pdo_mysql
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
COPY --from=deps app/vendor/ /var/www/html/vendor
COPY ./src /var/www/html
USER www-data

PHP 拡張モジュールのインストールについては PHP 向け公式 Docker イメージ を参照してください。

DB およびデータ追加のための compose.yaml ファイル更新

IDE またはテキストエディターを使って compose.yaml ファイルを開きます。 中を見てみれば分かるように、PostgreSQL データベースとボリュームに関する命令が、コメントアウトされて既に記述されています。 このアプリケーションでは MariaDB を用いることにしています。 MariaDB に関する詳細は MariaDB 公式 Docker イメージ を参照してください。

IDE またはテキストエディターを使って src/database.php ファイルを開きます。 これを見れば、データベース接続を行うために環境変数を読み込んでいることが分かります。

compose.yaml ファイルでは以下の修正を行います。

  1. MariaDB 用のデータベース命令のコメントをはずします。
  2. データベースパスワードを受け渡すため、サーバーサービスに secret を加えます。
  3. サーバーサービスにデータベース接続のための環境変数を加えます。
  4. データ保存を行うためにボリューム命令のコメントをはずします。

以下が修正後の compose.yaml ファイルです。 コメントはすべて削除しています。

services:
  server:
    build:
      context: .
    ports:
      - 9000:80
    depends_on:
      db:
        condition: service_healthy
    secrets:
      - db-password
    environment:
      - PASSWORD_FILE_PATH=/run/secrets/db-password
      - DB_HOST=db
      - DB_NAME=example
      - DB_USER=root
  db:
    image: mariadb
    restart: always
    user: root
    secrets:
      - db-password
    volumes:
      - db-data:/var/lib/mysql
    environment:
      - MARIADB_ROOT_PASSWORD_FILE=/run/secrets/db-password
      - MARIADB_DATABASE=example
    expose:
      - 3306
    healthcheck:
      test:
        [
          "CMD",
          "/usr/local/bin/healthcheck.sh",
          "--su-mysql",
          "--connect",
          "--innodb_initialized",
        ]
      interval: 10s
      timeout: 5s
      retries: 5
volumes:
  db-data:
secrets:
  db-password:
    file: db/password.txt
メモ

Compose ファイル内の命令についての詳細は Compose ファイルリファレンス を参照してください。

Compose を使ったこのアプリケーションを実行する前に確認しておきますが、この Compose ファイルは secrets を用いており、データベースパスワードを保持した password.txt ファイルを指定しています。 このファイルはソースリポジトリには存在していないため、各自で生成してください。

docker-php-sample ディレクトリに db という名前の新たなディレクトリを生成します。 そしてそのディレクトリ内に password.txt という名前のファイルを生成します。 IDE またはテキストエディターを使って password.txt を開き、以下のパスワードを入力します。 パスワードは 1 行内に収める必要があり、余計な行を加えてはなりません。

example

password.txt ファイルを保存して閉じます。

ここまでに docker-php-sample ディレクトリは以下のようになります。

├── docker-php-sample/
│ ├── .git/
│ ├── db/
│ │ └── password.txt
│ ├── src/
│ ├── tests/
│ ├── .dockerignore
│ ├── .gitignore
│ ├── compose.yaml
│ ├── composer.json
│ ├── composer.lock
│ ├── Dockerfile
│ ├── README.Docker.md
│ └── README.md

以下のコマンドを実行してアプリケーションを起動します。

$ docker compose up --build

ブラウザーを開いて http://localhost:9000/database.php にアクセスし、アプリケーションを確認します。 シンプルなウェブアプリケーションにテキストが表示され、また表示更新のたびに加算されるカウンターが表示されたはずです。

端末画面から ctrl+c を押下してアプリケーションを停止します。

データベース内のデータ確認

端末画面から docker compose rm コマンドを実行してコンテナーを削除します。 もう一度 docker compose up を実行すればアプリケーションを再起動することができます。

$ docker compose rm
$ docker compose up --build

http://localhost:9000/database.php にアクセスしているブラウザーを更新してみると、以前のカウンターがそのまま表示されることが分かります。 ボリュームが存在してなかったら、コンテナーを削除した後はデータベースデータが残っていないはずでした。

端末画面から ctrl+c を押下してアプリケーションを停止します。

データベース制御のための phpMyAdmin 追加

compose.yaml を書き換えるだけで、サービスをアプリケーションスタックに加えることが簡単にできます。

compose.yaml を修正して phpMyAdmin 用のサービスを新たに追加します。 詳しくは phpMyAdmin 公式 Docker イメージ を参照してください。 修正した compose.yaml ファイルは以下のとおりです。

services:
  server:
    build:
      context: .
    ports:
      - 9000:80
    depends_on:
      db:
        condition: service_healthy
    secrets:
      - db-password
    environment:
      - PASSWORD_FILE_PATH=/run/secrets/db-password
      - DB_HOST=db
      - DB_NAME=example
      - DB_USER=root
  db:
    image: mariadb
    restart: always
    user: root
    secrets:
      - db-password
    volumes:
      - db-data:/var/lib/mysql
    environment:
      - MARIADB_ROOT_PASSWORD_FILE=/run/secrets/db-password
      - MARIADB_DATABASE=example
    expose:
      - 3306
    healthcheck:
      test:
        [
          "CMD",
          "/usr/local/bin/healthcheck.sh",
          "--su-mysql",
          "--connect",
          "--innodb_initialized",
        ]
      interval: 10s
      timeout: 5s
      retries: 5
  phpmyadmin:
    image: phpmyadmin
    ports:
      - 8080:80
    depends_on:
      - db
    environment:
      - PMA_HOST=db
volumes:
  db-data:
secrets:
  db-password:
    file: db/password.txt

端末画面から docker compose up を実行してアプリケーションを起動します。

$ docker compose up --build

ブラウザーから http://localhost:8080 を開いて phpMyAdmin にアクセスします。 ユーザー名に root、パスワードに example を入力してログインします。 phpMyAdmin を通じてデータベースの制御ができるようになりました。

端末画面から ctrl+c を押下してアプリケーションを停止します。

サービスの自動更新

Compose Watch を利用すると、自分が作成して実行している Compose サービスを自動的に更新できるようになります。 Compose Watch に関する詳細は Use Compose Watch を参照してください。

IDE またはテキストエディターを使って compose.yaml ファイルを開きます。 そして Compose Watch 命令を追加します。 以下は修正した compose.yaml ファイルです。

services:
  server:
    build:
      context: .
    ports:
      - 9000:80
    depends_on:
      db:
        condition: service_healthy
    secrets:
      - db-password
    environment:
      - PASSWORD_FILE_PATH=/run/secrets/db-password
      - DB_HOST=db
      - DB_NAME=example
      - DB_USER=root
    develop:
      watch:
        - action: sync
          path: ./src
          target: /var/www/html
  db:
    image: mariadb
    restart: always
    user: root
    secrets:
      - db-password
    volumes:
      - db-data:/var/lib/mysql
    environment:
      - MARIADB_ROOT_PASSWORD_FILE=/run/secrets/db-password
      - MARIADB_DATABASE=example
    expose:
      - 3306
    healthcheck:
      test:
        [
          "CMD",
          "/usr/local/bin/healthcheck.sh",
          "--su-mysql",
          "--connect",
          "--innodb_initialized",
        ]
      interval: 10s
      timeout: 5s
      retries: 5
  phpmyadmin:
    image: phpmyadmin
    ports:
      - 8080:80
    depends_on:
      - db
    environment:
      - PMA_HOST=db
volumes:
  db-data:
secrets:
  db-password:
    file: db/password.txt

以下のコマンドを実行して Compose Watch を利用したアプリケーションを起動します。

$ docker compose watch

ブラウザーを開いて http://localhost:9000/hello.php にアクセスし、アプリケーションが動作していることを確認します。

ローカルマシン上にあるアプリケーションのソースコードを変更したら、その内容は実行中のコンテナーに即座に反映されます。

IDE またはテキストエディターを使って hello.php を開きます。 Hello, world! の文字列を Hello, Docker! に変更します。

hello.php への変更を保存します。 しばらく待つとアプリケーションへの同期が行われます。 http://localhost:9000/hello.php にアクセスしているブラウザーを更新してみると、修正した文字列が確認できます。

端末画面から ctrl+c を押下して Compose Watch を停止します。 そして docker compose down を実行してアプリケーションを停止します。

開発用コンテナーの生成

ここまでは、コンテナー化したアプリケーションの実行時に Compose は開発依存パッケージはインストールしていません。 このイメージは本番環境向けとしては非常に小さくできています。 しかし開発作業を行う際に必要となるツールや依存パッケージは含まれておらず、tests ディレクトリもありません。 そこで同じ Dockerfile 内に開発用と本番用という双方向けのステージをビルドするマルチステージビルドを用いることにします。 詳しくは マルチステージビルド を参照してください。

Dockerfile を以下のように修正します。

  1. deps ステージを 2 つのステージに分けます。 1 つは本番環境用 (prod-deps) であり、もう一つは開発依存パッケージ類をインストールするステージ (dev-deps) です。
  2. 共通の base ステージを作ります。
  3. 開発用として新たに development ステージを作ります。
  4. final ステージを修正して、prod-deps ステージから依存パッケージをコピーします。

以下に Dockerfile の編集前と編集後を示します。

# syntax=docker/dockerfile:1

FROM composer:lts as deps
WORKDIR /app
RUN --mount=type=bind,source=composer.json,target=composer.json \
    --mount=type=bind,source=composer.lock,target=composer.lock \
    --mount=type=cache,target=/tmp/cache \
    composer install --no-dev --no-interaction

FROM php:8.2-apache as final
RUN docker-php-ext-install pdo pdo_mysql
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
COPY --from=deps app/vendor/ /var/www/html/vendor
COPY ./src /var/www/html
USER www-data
# syntax=docker/dockerfile:1

FROM composer:lts as prod-deps
WORKDIR /app
RUN --mount=type=bind,source=./composer.json,target=composer.json \
    --mount=type=bind,source=./composer.lock,target=composer.lock \
    --mount=type=cache,target=/tmp/cache \
    composer install --no-dev --no-interaction

FROM composer:lts as dev-deps
WORKDIR /app
RUN --mount=type=bind,source=./composer.json,target=composer.json \
    --mount=type=bind,source=./composer.lock,target=composer.lock \
    --mount=type=cache,target=/tmp/cache \
    composer install --no-interaction

FROM php:8.2-apache as base
RUN docker-php-ext-install pdo pdo_mysql
COPY ./src /var/www/html

FROM base as development
COPY ./tests /var/www/html/tests
RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
COPY --from=dev-deps app/vendor/ /var/www/html/vendor

FROM base as final
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
COPY --from=prod-deps app/vendor/ /var/www/html/vendor
USER www-data

compose.yaml ファイルに開発ステージをターゲットとする命令を加えます。

以下は compose.yaml ファイルの更新した一部分です。

services:
  server:
    build:
      context: .
      target: development
      # ...

コンテナー化アプリケーションは、このようにして開発依存パッケージをインストールするようになります。

以下のコマンドを実行してアプリケーションを起動します。

$ docker compose up --build

ブラウザーを開いて http://localhost:9000/hello.php にアクセスし、アプリケーションを確認します。 "Hello, Docker!" アプリケーションが今回も表示されます。

端末画面から ctrl+c を押下してアプリケーションを停止します。

アプリケーションの様子は同じであっても、開発依存パッケージを利用する形を作り出しました。 続けて次の節では Docker を使ったテスト実行方法について学びます。

まとめ

この節では Compose ファイルに対してローカルデータベースとそのデータ保存についての設定を見てきました。 またソースコードの更新とともにアプリケーションの自動同期を行う Compose Watch の利用方法についても学びました。 そして開発環境向けに必要となる依存パッケージを含んだ開発コンテナーを生成する方法について学びました。

関連情報

次のステップ

次の節では Docker を使ったユニットテストの実行方法について学びます。