docker + docker-compose + docker-syncでSymfonyの環境構築 (超初心者向け)

# はじめに
Docker + Laravelの環境構築はQiitaに溢れているのですが、Docker + Symfonyの記事はあまり見当たらなかったので記事にしました。（と言ってもほぼLaravelと変わらないですが）。
Docker初心者なので、各種設定などを備忘録として細かく解説していこうと思います。

# 構成
## ホスト環境
| ツール | バージョン |
|---|---|
| Mac macOS | Catalina 10.15.5 |
| docker-compose| 1.25.5 |
| docker-sync | 0.5.14 |

## 構築環境 (ゴール)
* PHP 7.4
* mysql 5.7
* Apache
* Symfony 5

## ディレクトリ構成
appディレクトリにsymfonyのソースコードを配置します。注意点として**ルートにある`.env`はSymfonyの`.env`とは異なります**。

```
project/
  ├ app/
  ├ docker/
  │  ├ apache/
  │  │  └ my_app.conf
  │  ├ php/
  │  │  └ php.ini
  │  └ Dockerfile 
  ├  .env
  ├  docker-compose.yaml
  └  docker-sync.yaml
```

# 必要なツール類のインストール
Homebrewを使って、ホスト環境に必要なツール類をインストールします。

## docker

```bash
$ brew install docker
$ brew cask install docker
```

## docker-sync
`>> ~/.zshrc`のところは環境に合わせて変えてください。

```bash
$ brew install ruby
$ echo 'export PATH="/usr/local/opt/ruby/bin:$PATH"' >> ~/.zshrc
$ source ~/.zshrc
$ gem install docker-sync -n /usr/local/bin 
```

# docker-composeの設定

```yaml:docker-compose.yaml
version: '3'

services:
  app:
    container_name: my_app
    build: ./docker
    ports:
      - 8880:80
    volumes:
      - my_app_symfony:/var/www/html
      - ./docker/php/php.ini:/usr/local/etc/php/php.ini
      - ./docker/apache/my_app.conf:/etc/apache2/sites-enabled/my_app.conf
    depends_on:
      - mysql

  mysql:
    image: mysql:5.7
    container_name: my_app_app_mysql
    environment:
      MYSQL_ROOT_PASSWORD: $DB_ROOT_PASSWORD
      MYSQL_DATABASE: $DB_NAME
      TZ: 'Asia/Tokyo'
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    ports:
      - 3306:3306

volumes:
  my_app_symfony:
    external: true
```

## 解説
docker-composeは複数のDockerコンテナをまとめて管理するツールです。`docker-compose.yaml`の`services`の下に各種コンテナの設定を記述します。

`app`には、WEBサーバーとして動作するコンテナの設定を記述してあります。`container_name`のmy_appは好きな値に書き換えて大丈夫です。
`build: ./docker`と記述することでdocker/Dockerfileの内容にしたがってビルドを行います。
`ports`にはコンテナで立ち上げたプロセスのどのポートをローカルのどのポートにバインドするかと言う設定を記述します。ローカル:コンテナの順で記述するので、今回はローカルの8880ポートをコンテナの80ポートへバインドする、と言う意味です。http://localhost:8880 へリクエストを送ると、コンテナ内の80ポートへリクエストが転送されます。
`volumes`にはローカルにあるファイルやディレクトリをコンテナと同期する場合に記述します。`volumes`の2,3行目はローカルのphp.iniとmy_app.confをコンテナと同期する設定が記述してあります。1行目は、appディレクトリにあるローカルのsymfonyのプロジェクトをコンテナの`/var/www/html`と言うように記述してありますが、symfonyのソースコードは量が多く普通に同期するとパフォーマンスが落ちるので後述するdocker-syncを用いて同期を行います。ここでは、一番下に記述してある`volumes`の項目で(docker-syncで作成された)外部のボリュームを定義し、それに`my_app_symfony`と言う名前をつけています。`app`コンテナの`volumes`のローカルの方にはこの`my_app_symfony`を指定します。

`mysql`にはDBとして使用するmysqlコンテナの設定が記述してあります。こちらはDockerfileを用いず、あらかじめ公開されているイメージを使用します。イメージは[Dockerhub](https://hub.docker.com/_/mysql)などで検索することができます。Dockerhubのmysqlのページにドキュメントがあるのですが、コンテナを作成する時に特定の環境変数を渡すことで各種設定を行うことができます。環境変数は`environment`に書くことで渡すことができ、今回は`MYSQL_ROOT_PASSWORD`、`MYSQL_DATABASE`、`TZ`の三つを使用していますです。役割は名前から推測できると思うので割愛します。また、このファイルに変数の値をベタがきしても良いのですが、今回は柔軟性を持たせるためにローカルの環境変数から値を取得してくるようにしています(ややこしい)。ルートディレクトリにある`.env`ファイルに

```.env:.env
DB_ROOT_PASSWORD=root
DB_NAME=app_db
```
と言うように書いておくことで、コンテナ作成時に`docker-compose.yaml`無いの変数を書き換えてくれます。
`command`にはコンテナ作成時にコンテナ内で実行されるコマンドを記述することができます。

# /docker ディレクトリ

```.ini:php.ini
memory_limit = 2048M
date.timezone = "Asia/Tokyo"
mbstring.internal_encoding = "UTF-8"
mbstring.language = "Japanese"
```

```.conf:my_app.conf
<VirtualHost *:80>
        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/html/my_app/public
        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
```

```Dockerfile:Dockerfile
FROM php:7.4-apache

RUN a2dissite 000-default

WORKDIR /var/www/html

RUN apt-get update && apt-get install -y git zip unzip
RUN apt-get install -y wget libjpeg-dev libfreetype6-dev
RUN apt-get install -y  libmagick++-dev \
libmagickwand-dev \
libpq-dev \
libfreetype6-dev \
libjpeg62-turbo-dev \
libpng-dev \
libwebp-dev \
libxpm-dev

RUN docker-php-ext-configure gd --with-freetype=/usr/include/ --with-jpeg=/usr/include/
RUN docker-php-ext-install -j$(nproc) gd

RUN cd /usr/bin && curl -s http://getcomposer.org/installer | php && mv /usr/bin/composer.phar /usr/bin/composer
```

## 解説
`php.ini`と`my_app.conf`には特殊なことは書いていません。必要に応じて編集してください。

`Dockerfile`にはコンテナをどのようにビルドするかと言う手順を記述します。
1行目の`FROM`にはコンテナの元となるイメージを指定します(Dockerhubで検索)。`RUN xxxx`とすると、コンテナ内で`xxxx`を実行します。注意点として、`RUN`コマンドで実行した結果は次の`RUN`コマンドへは引き継がれませんので、`cd`などでディレクトリ移動したのに何故かファイルが無い！！見たいなことが起こります。結果を引継ぎたい時は`&&`でコマンドを続けて指定します。
2行目以降ではApacheのデフォルトサイトを無効化し、`php`のgd拡張のインストール及び、`composer`のインストールを行っています。`WORKDIR /var/www/html`では作業ディレクトリを`/var/www/html`に設定しています。


# docker-syncの設定

```yaml:docker-sync.yaml
version: '2'
syncs:
  my_app_symfony:
    src: './app'
    sync_host_ip: '127.0.0.1'
    sync_host_port: '5000'
    sync_strategy: 'native_osx'
    sync_excludes: ['.git', '.gitignore', 'node_modules']
```

## 解説
`syncs`の中に各種ボリュームの定義を記述します。`my_app_symfony`のところは`docker-compose`の`volumes`で指定したボリューム名と一致させます。
`src`には同期させるディレクトリを指定し、`sync_host_ip`と`sync_host_port`それぞれホストのIPトポートを指定します。`sync_strategy`は、色々あるようですが、とりあえず`native_osx`で問題無いと思います(よくわかってない)。`sync_excludes`には同期しないファイル、ディレクトリを指定します。`node_modules`など量が多くPHPで使用しないソースコードなどは同期しない方が無難だと思います。


# 起動

※ 8880ポート、8888ポート、5000ポート、3306ポートを使用するので、これらを使用しているソフトがある場合は停止しておいてください。

```
$ docker-sync start
$ docker-compose up -d --build
```
この時点で各種コンテナが起動します。

続いて、コンテナへ入り`composer`を用いてsymfonyのプロジェクトを作成します。コンテナからは`exit`で抜けられます。

```bash
# appコンテナでbashを実行
$ docker-compose exec app /bin/bash

# コンテナ内
root@eb419aab1d32:/var/www/html#  composer create-project symfony/skeleton my_app
```
ローカルのブラウザから http://localhost:8880 へアクセスするとsymfonyの画面が表示されていると思います！

## 各種操作
```bash
# コンテナの終了
$ docker-compose stop

# コンテナの再開
$ docker-compose start

# composer install
$ docker-compose exec app composer install
```

以上！