Docker に Ubuntu + PHP8.1 + Xdebug + SQLite 環境を構築してみる

経緯

拠所無き事情により PHP8.1 + SQLite の検証環境が必要になったため、 Docker で構築することにしました。ついでに、機能は少なくて良いので起動を軽くするために Ubuntu 環境で作ってみることにしました。

成果物

早速ですが、作成したものがこちらになります。

compose.yml

version: '3.9'
services:
  web:
    build:
      context: ./apache/docker
      dockerfile: Dockerfile
      args:
        WEB_ROOT_DIRECTORY: $WEB_ROOT_DIRECTORY
        WEB_CONTAINER_PORTNUM: $WEB_CONTAINER_PORTNUM
        PHP_VERSION: $PHP_VERSION
    volumes:
      # workspace
      - ./workspace:/workspace
      # docker settings template
      - ./template:/template
      # apache log
      - ./apache/log:/var/log/apache2
      # php ini
      - ./php/ini/php.ini:/etc/php/${PHP_VERSION}/fpm/php.ini
      # error log
      - ./php/error_log:/var/log/php
      # SSL
      - ./cert:/etc/ssl/private
    tty: true
    ports:
      - "$WEB_HOST_PORTNUM:$WEB_CONTAINER_PORTNUM"
      - "$WEB_HOST_PORTSSL:$WEB_CONTAINER_PORTSSL"
      - "$WEB_HOST_PORTSSH:$WEB_CONTAINER_PORTSSH"
    entrypoint: bash -c "bash /workspace/entrypoint.sh $WEB_ROOT_DIRECTORY $WEB_DOMAIN $WEB_HOST_PORTNUM $WEB_CONTAINER_PORTNUM $WEB_HOST_PORTSSL $WEB_CONTAINER_PORTSSL $WEB_SSH_USER $WEB_SSH_PASSWORD $PHP_VERSION && /bin/bash"

Ambergrease (Almalinux) をベースにしているので骨格が同じですね。

Dockerfile

FROM ubuntu:latest
# args
ARG WEB_ROOT_DIRECTORY
ARG WEB_CONTAINER_PORTNUM
ARG PHP_VERSION
# install tzdata without interactive
ENV DEBIAN_FRONTEND=noninteractive
# apt update & install
RUN apt update \
 && apt install -y sudo \
                   vim \
                   less \
                   procps \
                   # network ss (instaed of netstat)
                   iproute2 \
                   # apache
                   apache2 \
                   # zip
                   zip \
                   unzip \
                   # SSL
                   openssl \
                   # SSH
                   openssh-server \
                   # git
                   git \
                   # pasword
                   passwd \
                   # Set Timezone
                   tzdata \
                   # Set local to jp.
                   language-pack-ja \
                && update-locale LANG=ja_JP.UTF-8 \
                   # cleaning
                && apt clean \
                && rm -rf /var/lib/apt/lists/*
# set env
ENV TZ="Asia/Tokyo" \
    LANG="ja_JP.UTF-8" \
    LANGUAGE="ja_JP:ja" \
    LC_ALL="ja_JP.UTF-8"
RUN cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
# install php
RUN apt update \
 && apt install -y php \
                libapache2-mod-php \
                php-fpm \
                php-common \
                php-mbstring \
                php-xmlrpc \
                php-gd \
                php-xml \
                php-cli \
                php-zip \
                php-curl \
                php-imagick \
                # SQLite
                php-sqlite3 \
                # MySQL
                #php-pdo \
                #php-mysql \
                # Xdebug
                php-xdebug \
                # cleaning
                && apt clean \
                && rm -rf /var/lib/apt/lists/*
# composer
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
 && php composer-setup.php \
 && php -r "unlink('composer-setup.php');" \
 && mv composer.phar /usr/local/bin/composer
# PHP
RUN mkdir -p /run/php
RUN chown www-data:www-data /run/php
# php log
RUN mkdir /var/log/php
RUN chown www-data /var/log/php
RUN chmod 755 /var/log/php
# SSL
RUN mkdir -p /etc/ssl/private
# volume directory
RUN mkdir -p /template
RUN mkdir -p /var/www/${WEB_ROOT_DIRECTORY}
RUN mkdir -p /var/www/${WEB_ROOT_DIRECTORY}/web
RUN mkdir -p /workspace
# SSH
RUN mkdir -p /var/run/sshd

冒頭の通り、 Ubuntu にOSを差し替えてパッケージも Ubuntu 系のものに差し替え。 Ambergrease では統合的な検証環境を目指したためパッケージもモリモリでしたが今回は逆に削ぎ落とす方向で調整。

それでも日付を日本時間にするために tzdata を入れたり、ロケールを日本語UTF-8にしたりしていますが。

やや嵌まったのがこの tzdata でして、普通にパッケージをインストールすると途中でタイムゾーンを対話式で聞いてきます。手動インストールの場合はそのまま番号を入力してインストールを進めることができますが、 Docker で一括インストールの場合は人間の入力が介在しないためにビルドが途中で止まってしまいます。

そこで、 apt install を走らせる直前に環境変数として

ENV DEBIAN_FRONTEND=noninteractive

この一行を入れることで対話式に入ることを回避しています。ちなみに、これと同種の問題は CI/CD するときにも発生するため、各種 CI/CD 用の YAML ファイルでやはり環境変数をセットして回避するTipsが必要になります。

それから、 Apache の実行ユーザが RedHat 系では httpd でしたが、 Ubuntu では www-data なのでこれも初見では見逃しがちなので注意。

entrypoint.sh

#!/bin/bash

# gen key & certificate
## localhost
openssl req -new -newkey rsa:2048 -nodes \
        -out /etc/pki/tls/certs/localhost.csr \
        -keyout /etc/pki/tls/private/localhost.key \
        -subj "/C=/ST=/L=/O=/OU=/CN=www.example.com"
openssl x509 -days 365 -req \
        -signkey /etc/pki/tls/private/localhost.key \
        -in /etc/pki/tls/certs/localhost.csr \
        -out /etc/pki/tls/certs/localhost.crt
## .env domain
openssl req -new -newkey rsa:2048 -nodes \
        -out /etc/ssl/private/server.csr \
        -keyout /etc/ssl/private/server.key \
        -subj "/C=/ST=/L=/O=/OU=/CN=*.${2}"
openssl x509 -days 365 -req \
        -signkey /etc/ssl/private/server.key \
        -in /etc/ssl/private/server.csr \
        -out /etc/ssl/private/server.crt

# setting file replace and copy
sed -e "s/WEB_ROOT_DIRECTORY/${1}/gi" \
    -e "s/WEB_DOMAIN/${2}/gi" \
    -e "s/WEB_HOST_PORTNUM/${3}/gi" \
    -e "s/WEB_CONTAINER_PORTNUM/${4}/gi" \
    -e "s/WEB_HOST_PORTSSL/${5}/gi" \
    -e "s/WEB_CONTAINER_PORTSSL/${6}/gi" \
        /template/apache/apache_vh.conf > /etc/apache2/sites-available/${1}.conf
sed -e "s/WEB_ROOT_DIRECTORY/${1}/gi" \
    -e "s/WEB_DOMAIN/${2}/gi" \
    -e "s/WEB_HOST_PORTNUM/${3}/gi" \
    -e "s/WEB_CONTAINER_PORTNUM/${4}/gi" \
    -e "s/WEB_HOST_PORTSSL/${5}/gi" \
    -e "s/WEB_CONTAINER_PORTSSL/${6}/gi" \
        /template/apache/apache_vh_ssl.conf > /etc/apache2/sites-available/${1}_ssl.conf

cp /template/apache/ssl.conf /etc/apache2/mods-available/ssl.conf

# apache module enable
a2enmod ssl proxy_fcgi setenvif rewrite
# apache conf enable
echo ServerName www.example.com:${4} >> /etc/apache2/conf-available/example.conf
a2enconf example php${9}-fpm
# apache cirtual site enable
a2ensite ${1} ${1}_ssl
# apache service start
service apache2 start

# PHP
/usr/sbin/php-fpm${11} &

# SSH
sed -ri 's/^#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
echo "${7}:${8}" | /usr/sbin/chpasswd
ssh-keygen -t rsa -N "" -f /etc/ssh/ssh_host_rsa_key <<< y
/usr/sbin/sshd -D &

SSL証明書発行、 Apache の設定作成と起動、 PHP を php-fpm で起動、最後に SSH サーバを起動して SFTP を受け付けられるようにしておきます。これも基本 Ambergrease をベースにしていますね。

Almalinux との差異で言うと、以下の点でしょうか。

  • Apache の設定ファイル
    • Ubuntu では Apache の設定ファイルの構成が異なっており、特に XXXXX-availableXXXXX-enabled があることに戸惑う
    • XXXXX-enabledXXXXX-available の同名設定ファイルへのシンボリックリンクになっているので、基本 XXXXX-available に設定ファイルを持って行けば問題なさそう
    • シンボリックリンクは a2enconf, a2ensite 辺りのコマンドを叩くと conf-enabledsites-enabled に作られる模様
  • SSH
    • root ログインを許可する……としたいのですが、コメントに記載されている初期値が prohibit-password なので Almalinux と同じコマンドだと文字列不一致を起こして上手く置換されないですね。地味に初見で「あれ?」となるポイント。

その他

php.ini

; XDEBUG ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
xdebug.mode=debug,coverage
xdebug.client_host.=host.docker.internal
xdebug.client_port=9000
xdebug.remote_handler="dbgp"

php.ini に Xdebug 関係の設定を追記したのですが、どうも上手くカバレッジが取れないので (でも上記の設定で phpinfo(); すると Xdebug は読み込まれているし Coverage ✔ enabled, Step Debugger ✔ enabled になる)、 PHP プロジェクトの方で XDEBUG_MODE=coverage phpunit --coverage-html coverage と最初に環境変数をセットして力技で回避するようにしました (詳細は別記事: Docker 上の Ubuntu + PHP8.1 環境で PHPUnit のコードカバレッジを得るまで にて)。

余談

今回は PHP に特化した環境、ということで Elephpant こと象に関する事項で……コンテナなので四角い箱のイメージ、も合わせてインドのムンバイ、エレファンタ石窟群のあるエレファンタ島(石窟をコンテナのイメージに、島の名前は象の石像があったことに由来)の元々の名前 Gharapuri(ガーラープリ)島 から。

参考

Ubuntu

検証

これでビルドと破棄を繰り返して試験・検証。

ネットワーク状態確認

apt, apt-get

タイムゾーン・ロケール設定

対話式を回避

apt install tzdata

する前に

ENV DEBIAN_FRONTEND=noninteractive

の環境変数をセットして回避する。

Apache

RedHat系 とディレクトリ構成が異なるので戸惑うなど。まずは sites-availablesites-enabled の違いを認識する洗礼を受ける。

ユーザーグループ

apache ではなく www-data

ServerName ディレクティブ

SSL

最初は躓いたが特に設定変更するでもなくできた。

PHP

PHP

# a2enconf php8.1-fpm

としたところ

NOTICE: Not enabling PHP 8.1 FPM by default.
NOTICE: To enable PHP 8.1 FPM in Apache2 do:
NOTICE: a2enmod proxy_fcgi setenvif
NOTICE: a2enconf php8.1-fpm
NOTICE: You are seeing this message because you have apache2 package installed.

と言われたので。

PHP + SQLite

Composer

failed to solve: executor failed running …

passwd

SSH

(未完成) vsftpd

この記事を書いた人

アルム=バンド

フロントエンド・バックエンド・サーバエンジニア。LAMPやNodeからWP、Gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。