🧰

VS Code Remote - Containers用PHP開発環境を構築する

14 min read

みなさん、VS Code Remote - Containersは使っていますか?
プロジェクト毎に開発環境を構築・共有できて、とても便利ですよね。
また、コンテナの起動・停止がVS Codeと連動するのでとても良い開発体験かと思います。

今回、PHPの開発環境を構築してみたので共有したいと思います。

設定

ディレクトリ構成

.devcontainer 配下に設定をまとめてあります。

.
└── .devcontainer
    ├── .env
    ├── devcontainer.json
    ├── docker
    │   ├── nginx
    │   │   ├── Dockerfile
    │   │   └── config
    │   │       └── default.conf
    │   ├── php
    │   │   ├── Dockerfile
    │   │   └── config
    │   │       ├── php.ini
    │   │       └── xdebug.ini
    │   └── workspace
    │       ├── Dockerfile
    │       └── config
    │           └── php.ini
    └── docker-compose.yml

devcontainer.json

VS Codeのコンテナ開発の設定を行います。

.devcontainer/devcontainer.json
{
    "name": "PHP Development",
    "dockerComposeFile": [
        "docker-compose.yml"
    ],
    "service": "workspace",
    "workspaceFolder": "/var/www",
    "remoteUser": "vscode",
    "settings": {
        "[php]": {
            "editor.formatOnSave": true
        },
        "search.exclude": {
            "**/node_modules": true,
            "**/bower_components": true,
            "**/*.code-search": true,
            "**/vendor/*/**": true
        }
    },
    "extensions": [
        "mikestead.dotenv",
        "EditorConfig.EditorConfig",
        "mhutchie.git-graph",
        "eamodio.gitlens",
        "onecentlin.laravel-blade",
        "onecentlin.laravel5-snippets",
        "emilast.LogFileHighlighter",
        "neilbrayfield.php-docblocker",
        "bmewburn.vscode-intelephense-client",
        "felixfbecker.php-debug",
    ]
}

serviceに、接続するサービス名を指定します。
settingsに、接続コンテナの VS Code の設定を行います。
extensionsに、接続コンテナにインストールする拡張機能を指定します。

docker-compose.yml

.devcontainer/docker-compose.yml
version: "3.9"

services:
  workspace:
    build:
      context: ./docker/workspace
      args:
        USERNAME: ${USERNAME-vscode}
        USER_UID: ${USER_UID-1000}
        USER_GID: ${USER_GID-1000}
        TIME_ZONE: ${TIME_ZONE-UTC}
        LOCALE: ${LOCALE-C}
    tty: true
    volumes:
      - ../:/var/www
      - ./docker/workspace/config/php.ini:/usr/local/etc/php/conf.d/99-php.ini
    working_dir: /var/www

  nginx:
    build: ./docker/nginx
    ports:
      - "${IP_ADDRESS_SETTING}80:80"
      - "${IP_ADDRESS_SETTING}443:443"
    volumes:
      - ./docker/nginx/config:/etc/nginx/conf.d
      - ../.docker/nginx/log:/var/log/nginx
      - ../:/var/www
    environment:
      TZ: ${TIME_ZONE-UTC}

  php:
    build:
      context: ./docker/php
      args:
        USER_UID: ${USER_UID-1000}
        USER_GID: ${USER_GID-1000}
    volumes:
      - ./docker/php/config/php.ini:/usr/local/etc/php/php.ini
      - ./docker/php/config/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini
      - ../:/var/www
    working_dir: /var/www

  mysql:
    image: mysql/mysql-server:8.0
    ports:
      - "${IP_ADDRESS_SETTING}3306:3306"
    volumes:
      - mysql:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD-docker}
      MYSQL_USER: ${DB_USERNAME-docker}
      MYSQL_PASSWORD: ${DB_PASSWORD-docker}
      MYSQL_DATABASE: ${DB_DATABASE-docker}
      TZ: ${TIME_ZONE-UTC}
    command:
      - mysqld
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_ja_0900_as_cs_ks

  postgres:
    image: postgres:14.0-alpine
    ports:
      - "${IP_ADDRESS_SETTING}5432:5432"
    volumes:
      - postgres:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: ${DB_USERNAME-docker}
      POSTGRES_PASSWORD: ${DB_PASSWORD-docker}
      POSTGRES_DB: ${DB_DATABASE-docker}
      TZ: ${TIME_ZONE-UTC}

  redis:
    image: redis:6.2
    ports:
      - "${IP_ADDRESS_SETTING}6379:6379"
    volumes:
      - redis:/data

  redisinsight:
    image: redislabs/redisinsight:latest
    ports:
      - "${IP_ADDRESS_SETTING}8001:8001"

  mailhog:
    image: mailhog/mailhog
    ports:
      - ${IP_ADDRESS_SETTING}1025:1025
      - ${IP_ADDRESS_SETTING}8025:8025

  selenium:
    image: selenium/hub

  selenium-chrome:
    image: selenium/node-chrome-debug
    environment:
      - HUB_PORT_4444_TCP_ADDR=selenium
      - HUB_PORT_4444_TCP_PORT=4444
    depends_on:
      - selenium

volumes:
  mysql:
    driver: local
  postgres:
    driver: local
  redis:
    driver: local

設定しているコンテナの種類です。

  • PHP(CLI)
  • nginx
  • PHP(FPM)
  • MySQL
  • PostgreSQL
  • Redis
  • Redisinsight
  • Mailhog
  • Selenium

workspaceがPHP(CLI)になり、このコンテナ内で作業を行います。
純粋にPHPだけの開発であれば、workspaceのみあれば大丈夫です。

.env

.envにはdocker-compose.ymlの環境変数を設定します。

.devcontainer/.env
TIME_ZONE=Asia/Tokyo
LOCALE=ja_JP.UTF-8

DB_DATABASE=docker
DB_USERNAME=docker
DB_PASSWORD=docker

# Local Loopback Address(127.0.0.0/8):
IP_ADDRESS_SETTING=127.127.127.127:

docker-composeのバージョンが古いと.envを読み込む位置がプロジェクトルートになります。
バージョン1.28以上を使用するようにします。

IP_ADDRESS_SETTING

Dockerではポート毎に公開するIPアドレスを設定できます。
IPアドレスを設定することにより、複数のプロジェクトでのポートの重複を気にしなくて済むようになります。
また、設定したIPアドレスでhostsを設定すると、より便利に開発できます。

127.127.127.127 php-develop.test

IP_ADDRESS_SETTINGに値を設定しない場合は、localhostでの接続になります。
IPアドレスはローカルループバックアドレスを使います。
他のIPアドレスはホストOSから到達できないと思いますが、ローカルループバックアドレスであれば自分自身(Dockerホスト)をさすので接続可能です。

docker

dockerディレクトリにDockerfileや各種コンテナアプリケーションの設定ファイルを設置しています。

workspace

.devcontainer/docker/workspace/Dockerfile
FROM php:8.0-cli-bullseye

ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=${USER_UID}

ARG LOCALE=en_US.UTF-8
ARG TIME_ZONE=UTC

ENV PKG="bash-completion curl dnsutils git imagemagick jq locales mariadb-client postgresql-client rsync sqlite3 tree unzip vim wget zip"
ENV PKG_LIB="libc-client-dev libfreetype6-dev libjpeg62-turbo-dev libkrb5-dev libmagickwand-dev libonig-dev libpng-dev libpq-dev libsqlite3-dev libxslt-dev libzip-dev"
ENV COMPOSER_ALLOW_SUPERUSER 1
ENV DEBIAN_FRONTEND noninteractive
ENV LANG=${LOCALE}
ENV TZ=${TIME_ZONE}

COPY --from=composer:2.1 /usr/bin/composer /usr/bin/composer
COPY --from=node:16.12 /usr/local/bin /usr/local/bin

RUN apt-get update \
    && apt-get install -y $PKG $PKG_LIB \
    && pecl install imagick redis-5.3.4 xdebug-3.1.1 \
    && docker-php-ext-configure imap --with-kerberos --with-imap-ssl \
    && docker-php-ext-enable imagick redis xdebug \
    && docker-php-ext-install bcmath gd imap intl pdo_mysql pdo_pgsql pdo_sqlite xml zip \
    #
    # locale
    && sed -i -E "s/# (${LOCALE})/\1/" /etc/locale.gen \
    && locale-gen ${LOCALE} \
    && dpkg-reconfigure locales \
    && update-locale LANG=${LOCALE} \
    #
    # timezone
    && ln -snf /usr/share/zoneinfo/${TIME_ZONE} /etc/localtime && echo ${TIME_ZONE} > /etc/timezone \
    #
    # user
    && groupadd --gid ${USER_GID} ${USERNAME} \
    && useradd -s /bin/bash --uid ${USER_UID} --gid ${USER_GID} -m ${USERNAME} \
    && apt-get install -y sudo \
    && echo ${USERNAME} ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/${USERNAME} \
    && chmod 0440 /etc/sudoers.d/${USERNAME}


作業を行うコンテナです。
PHP(CLI)のphp:8.0-cli-bullseyeをベースに設定しています。

COPY --fromで別のイメージからコピーしています。

作業はrootで行わず、別ユーザで動かすのでユーザの設定を行っています。

.devcontainer/docker/workspace/config/php.ini
[xdebug]
xdebug.mode=debug,develop
xdebug.start_with_request=yes
xdebug.client_host=localhost
xdebug.log_level=0

PHP(CLI)の設定を行います。

php

.devcontainer/docker/php/Dockerfile
FROM php:8.0-fpm-bullseye

ARG USER_UID=1000
ARG USER_GID=${USER_UID}

RUN apt-get update \
    && apt-get install -y libc-client-dev libfreetype6-dev libjpeg62-turbo-dev libkrb5-dev libmagickwand-dev libonig-dev libpng-dev libpq-dev libsqlite3-dev libxslt-dev libzip-dev sqlite3 zip \
    && pecl install imagick redis-5.3.4 xdebug-3.1.1 \
    && docker-php-ext-configure imap --with-kerberos --with-imap-ssl \
    && docker-php-ext-enable imagick redis xdebug \
    && docker-php-ext-install bcmath gd imap intl pdo_mysql pdo_pgsql pdo_sqlite xml zip \
    #
    # user
    && groupmod -o -g ${USER_GID} www-data \
    && usermod -o -u ${USER_UID} -g www-data www-data

PHP(FPM)のphp:8.0-fpm-bullseyeをベースに設定しています。

PHP(CLI)とPHP(FPM)のユーザが異なるとパーミッションの設定が面倒なので、UID/GIDを合わせます。

.devcontainer/docker/php/config/php.ini
post_max_size = 520M
upload_max_filesize = 512M
.devcontainer/docker/php/config/xdebug.ini
[xdebug]
xdebug.mode=debug,develop
xdebug.start_with_request=yes
xdebug.client_host=workspace
xdebug.log_level=0

PHP(FPM)の設定を行います。

nginx

.devcontainer/docker/nginx/Dockerfile
FROM nginx:1.21

RUN apt-get update && apt-get -y install openssl \
    && openssl req -newkey rsa:2048 -x509 -nodes -set_serial 1 -days 3650 \
       -subj "/C=JP/ST=Tokyo/L=Chiyoda-ku" \
       -keyout "/etc/ssl/private/server.key" -out "/etc/ssl/private/server.crt" \
    && chmod 400 /etc/ssl/private/server.*

httpsで接続できるようにしたいので、自己SSL証明書を作成します。

.devcontainer/docker/nginx/config/default.conf
server {
    listen 80;
    listen 443 ssl;

    client_max_body_size 520M;

    root /var/www/public;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;

        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        include fastcgi_params;

        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
        fastcgi_read_timeout 600;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt { access_log off; log_not_found off; }

    location ~ \.(css|gif|jpeg|jpg|js|png|svg) {
        access_log off;
        log_not_found off;
    }

    ssl_certificate /etc/ssl/private/server.crt;
    ssl_certificate_key /etc/ssl/private/server.key;
}

使用例

プロジェクトのディレクトリを作成し、VS Codeを起動します。

mkdir php-develop
cd php-develop
code .

プロジェクトのディレクトリに.devcontainerを配置します。

VS Codeのコマンドパレット(F1)から [Remote-Containers: Reopen in Container] を実行します。

コンテナの開発環境の構築が始まります。

構築完了後、コンテナ内での開発が可能になります。

Xdebug

VS CodeにXdebugを受け入れる設定を行います。

.vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for Xdebug",
            "type": "php",
            "request": "launch",
            "port": 9003,
        }
   ]
}

Xdebugはバージョン3系をインストールしています。
デフォルトのport9003になります。

Xdebugは、Xdebug側からIDE(VS Code)に接続するのでホスト名を正しく設定する必要があります。

  • PHP(CLI)
    xdebug.client_host=localhost
    PHP(CLI)とVS Codeは同じコンテナになるのでlocalhostになります。

  • PHP(FPM)
    xdebug.client_host=workspace
    VS Codeはworkspaceで動いているので、workspaceを設定します。

Laravel

インストール

composer でインストールします。
composer create-project は空のディレクトリでないとインストールできないので、いったんtmpに退避させてから移動します。

composer create-project --prefer-dist "laravel/laravel:8.*" /tmp/laravel
mv -n /tmp/laravel/* /tmp/laravel/.[^\.]* .

正しくインストールされていればページが表示されると思います。

設定

config/app.php
  'timezone' => env('TZ', 'UTC'),

コンテナの環境変数が設定されている場合、このような設定も可能です。

MySQL

.env
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=docker
DB_USERNAME=docker
DB_PASSWORD=docker

PostgreSQL

.env
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=docker
DB_USERNAME=docker
DB_PASSWORD=docker

Redis

.env
REDIS_HOST=redis
REDIS_PORT=6379
Redisinsight

Mailhog

.env
MAIL_DRIVER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null

Laravel Dusk

Laravelを使ったブラウザテストです。
Seleniumを使った設定を行います。

パッケージをインストール

composer require --dev laravel/dusk

Laraveのインストール設定

php artisan dusk:install

ChromeDriverを使わないのでコメントアウト

sed -i -E 's#[^/](static::startChromeDriver.*)#//\1#' tests/DuskTestCase.php

WEBサーバ(nginx)のURL

.env
APP_URL=http://nginx

WebDriverのURL

.env
DUSK_DRIVER_URL=http://selenium:4444/wd/hub

その他

Macの場合、Dockerの動作が遅いという記事をよく見ます。
ホストのディレクトリをマウントさせると遅くなるようです。

色々対策があるようなので参考にしてみてください。

https://www.keisuke69.net/entry/2021/09/15/104532
https://qiita.com/ysKey2/items/346c429ac8dfa0aed892

また、WindowsのWSL2も同様の問題があるのですが、こちらはWSL2側をマウントさせれば問題ありません。
/mnt/c などWindows側をマウントさせると、すごく遅いです。

Mac/Windowsともに、Data Volumeにマウントさせる分には遅くはならないと思います。
ホストのディレクトリをマウントさせなくても開発できるのもメリットの1つだとおもいます。

正式なリリースはまだですが、GitHub CodespacesというWEBブラウザでVS Code Remote - Containersの環境が、使えるサービスがあります。
こちらの環境の導入もぜひ検討してみてください。


リポジトリ

https://github.com/horatjp/devcontainer-php

参考

https://code.visualstudio.com/docs/remote/containers
https://wand-ta.hatenablog.com/entry/2020/05/23/011001
GitHubで編集を提案

Discussion

ログインするとコメントできます