【Dockerfile 無し】docker-compose で簡単にPHP開発環境構築 SSL対応 Laravelインストール

ある程度簡単にdocker-composeで開発環境を構築する手順です

この手の記事はよくあるので、設定自体は全然煮詰まってないかもしれませんが、

このやり方以下のポイントを意識しています

1. dockerイメージのビルドをしない（公式のイメージのまま利用）
1. SSL対応
1. WEBコンテナのユーザIDをホスト側のユーザIDに合わせる

1が個人的1番のポイントで好みですが、取り急ぎテスト的に環境作って始めるときに、**使い捨てになるかもしれないイメージをローカルに作りたくない**という動機です。
（開発環境として安定したらビルドしたほうが環境の立ち上げが早くなるし良いと思います）

2は本番環境はSSLなのでローカルもできるだけSSLにしたい、

3はホスト側とコンテナ側でユーザIDが合わないと開発中にファイルのパーミッションでこけることがちょくちょくあるので合わせるようにしています。

Laravel限定のネタではないですが、例としてLaravelの環境を構築します、

###  初期準備ファイル
```bash
$ tree -L 3
.
├── docker-compose.yml
└── docker_config
    └── web
        ├── logs       ※ディレクトリ
        ├── make_env.sh
        └── web.conf
```

####  docker-compose.yml

php-apache, nodejs, mysql, mailhogのコンテナが含まれています。

見ての通りですが、buildは行わず、commnad でコンテナの立ち上げ時に初期設定を行います。そのため、ビルド済みのイメージに比べてコンテナが利用できる状態になるまでに多少時間がかかります。

冒頭に書いた公式のイメージのままビルドせずに利用するのですが、なんとなくプロジェクトごとにかっちりとイメージをビルドするのが面倒なのでとりあえずこれでスタートしています。

command の中で変数で `LOCALGID` `LOCALGID` を埋め込んでいますが、ホスト側のユーザID,グループIDを受け渡すことで、コンテナ内の www-data ユーザをホスト側のIDで作り直し、パーミッション関連の問題が発生しないように努めています。

.envに変数定義しておけば、docker-compose.yml内で利用できます。

```bash
version: '3'

volumes:
  db_volume:

services:

  web:
    image: php:8.1-apache-buster
    ports:
      - "443:443"
    volumes:
      - ./:/var/www/html/
      - ./docker_config/web/web.conf:/etc/apache2/sites-available/web.conf
      - ./docker_config/web/ssl.crt:/etc/ssl/certs/ssl.crt
      - ./docker_config/web/ssl.key:/etc/ssl/private/ssl.key

    working_dir: /var/www/html/project_root
    tty: true
    env_file:
      - ./.env
    entrypoint: /bin/bash
    command: >
      -c "
      #update
        apt-get update;
        apt-get install -y libzip-dev;
      #php拡張インストール
        docker-php-ext-install zip;
        docker-php-ext-install pdo_mysql mysqli;
      #xdebugインストール
        pecl install xdebug && docker-php-ext-enable xdebug;
      #www-dataユーザの削除、再作成（ローカルユーザIDと合わせる）
        userdel -r www-data;
        groupadd -g ${LOCALGID} www-data;
        useradd -M -u ${LOCALUID} -g ${LOCALGID} -s /sbin/nologin www-data;
      #apacheモジュールの有効化
        a2enmod ssl;
        a2enmod rewrite;
      #apacheサイトの有効化
        a2ensite ssl;
        a2ensite web;
      #apacheデフォルトサイトの無効化      
        a2dissite 000-default;
      #php.iniのコピー
        cp /usr/local/etc/php/php.ini-development /usr/local/etc/php/php.ini;
      #php.iniのメモリリミットの拡張
        sed -i.bk -e 's/memory_limit = 128M/memory_limit = 8192M/gi' /usr/local/etc/php/php.ini ;
      #apacheコンフィグテスト
        apachectl configtest;
      #composerのインストール
        curl -sS https://getcomposer.org/installer | php -- --version=2.1.14 ;
        mv composer.phar /usr/local/bin/composer ;
        composer -v;
      #xdebugの有効化
        echo '#コメントアウト' > /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini ;
        echo '#extension=xdebug' >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini ;
        echo 'zend_extension=xdebug.so' >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini ;
        echo 'xdebug.mode=coverage' >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini;
      #apacheの起動
        apache2-foreground
      "
  nodejs:
    image: node:14.16.1-alpine
    working_dir: /opt/project_root
    volumes:
      - ./project_root:/opt/project_root
    user: ${LOCALUID}
    tty: true

  db:
    image: mysql:8.0.23
    environment:
      - MYSQL_DATABASE=db_name
      - MYSQL_USER=db_user
      - MYSQL_PASSWORD=db_pass
      - MYSQL_ROOT_PASSWORD=root_db_pass
      - TZ=Asia/Tokyo
      - BIND-ADDRESS=0.0.0.0
    volumes:
      - db_volume:/var/lib/mysql
    ports:
      - "33306:3306"
    tty: true
    command: >
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_bin
      --sql-mode=NO_ENGINE_SUBSTITUTION
      --default-authentication-plugin=mysql_native_password
      --local-infile=1

  mailhog:
     image: "mailhog/mailhog"
     ports:
       - "8025:8025"
     tty: true
```

#### .envファイル作成用スクリプト
上述した `LOCALGID` `LOCALGID` を記述した.envファイルを作成するためのスクリプトです。
初回に `docker-compose.yml up` する前に一回だけ実行します。

```bash
$ cat docker_config/web/make_env.sh 
#!/bin/bash
THIS_DIR=$(cd $(dirname $0);pwd)
set -eu
cat <<EOT > ${THIS_DIR}/../../.env
LOCALUID=`id -u`
LOCALGID=`id -g`
EOT
```
実行すると .env が生成されます。

```bash
$ bash docker_config/web/make_env.sh 

$ tree -La 1
├── .env
├── docker-compose.yml
└── docker_config

$ cat .env
LOCALUID=1000
LOCALGID=1000
```


### ssl関連ファイル作成


```bash
$ cd docker_config/web/

$ openssl genrsa -out ssl.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
..............................................+++++
.........+++++
e is 65537 (0x010001)

$ openssl req -new -sha256 -key ssl.key -out ssl.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:localhost
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

$ echo "subjectAltName = DNS:localhost" > san.txt

$ openssl x509 -req -sha256 -days 3650 -signkey ssl.key -in ssl.csr -out ssl.crt -extfile san.txt
Signature ok
subject=C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = localhost
Getting Private key
```

SSl関連ファイル作成後のディレクトリ構成は以下のようになります

```bash
$ tree -L 3
.
├── docker-compose.yml
└── docker_config
    └── web
        ├── logs       ※ディレクトリ
        ├── make_env.sh
        ├── san.txt
        ├── ssl.crt
        ├── ssl.csr
        ├── ssl.key
        └── web.conf
```

apache用コンフィグファイル

```bash
$ cat docker_config/web/web.conf 
<IfModule mod_ssl.c>
        <VirtualHost _default_:443>
                ServerAdmin webmaster@localhost

                DocumentRoot /var/www/html/project_root/public

                ErrorLog /var/www/html/docker_config/web/logs/error.log
                CustomLog /var/www/html/docker_config/web/logs/access.log combined

                SSLEngine on
                SSLCertificateFile      /etc/ssl/certs/ssl.crt
                SSLCertificateKeyFile /etc/ssl/private/ssl.key
 
                <FilesMatch "\.(cgi|shtml|phtml|php)$">
                                SSLOptions +StdEnvVars
                </FilesMatch>
                <Directory /usr/lib/cgi-bin>
                                SSLOptions +StdEnvVars
                </Directory>
        </VirtualHost>
</IfModule>
```

### docker-compose up
コンテナを立ち上げます

```bash
$ docker-compose up -d
Creating network "[作業ディレクトリ]_default" with the default driver
Creating [作業ディレクトリ]_nodejs_1  ... done
Creating [作業ディレクトリ]_mailhog_1 ... done
Creating [作業ディレクトリ]_db_1      ... done
Creating [作業ディレクトリ]_web_1     ... done
```

`docker logs -f コンテナ` でコンテナ内の出力を追います。
`apache2 -D FOREGROUND` が表示されたらapacheの起動完了となります。

```bash
$ docker logs -f webコンテナ
AH00112: Warning: DocumentRoot [/var/www/html/project_root/public] does not exist
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.27.0.5. Set the 'ServerName' directive globally to suppress this message
AH00112: Warning: DocumentRoot [/var/www/html/project_root/public] does not exist
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.27.0.5. Set the 'ServerName' directive globally to suppress this message
[Tue Dec 21 15:59:41.322194 2021] [mpm_prefork:notice] [pid 1] AH00163: Apache/2.4.38 (Debian) PHP/8.1.0 OpenSSL/1.1.1d configured -- resuming normal operations
[Tue Dec 21 15:59:41.322232 2021] [core:notice] [pid 1] AH00094: Command line: 'apache2 -D FOREGROUND'
```

コンテナに入ります。
`-u $(id -u)`をつけることで、そのユーザIDでコンテナにアタッチされます。
つけない場合は、rootでログインされるので、コンテナ内でファイルの作成などすると、ホスト側でファイルの削除ができないなどの事象に当たる場合があったりします。

```bash
$ docker exec -u $(id -u) -it livewire_web_1 /bin/bash
```
ホスト側と同一のユーザIDでWEBユーザ(www-data)が作成されていることを確認します

```bash
www-data@16d3e36d1d88:/var/www/html$ id
uid=1000(www-data) gid=1000(www-data) groups=1000(www-data)
```

composerでlaravelのプロジェクトをインストールします

```bash
www-data@16d3e36d1d88:/var/www/html$ composer create-project laravel/laravel project_root

・・・
Package manifest generated successfully.
77 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
> @php artisan vendor:publish --tag=laravel-assets --ansi --force
No publishable resources for tag [laravel-assets].
Publishing complete.
> @php artisan key:generate --ansi
Application key set successfully.
```
https://localhost にアクセスしてlaravelの初期ページが表示されることを確認します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274034/969d4376-2ab7-75e8-b806-f63c73da30d3.png)



警告が表示されていますが、最初に作った証明書ファイルをブラウザにインポートすれば警告表示も表示されなくなります。