【Docker+Laravel6】開発環境構築についてまとめてみました

## 背景
AWSでの本番環境構築にむけてポートフォリオ開発を進めていたところ、以前の開発環境が簡易的で必要な設定がされておらず、環境依存が起こってしまう可能性が高いと考えたので、本番環境構築前に整えておこうという目的で再構築しました。

:::note warn
今回は再構築、初構築両方に対応できるように手順を記載しました。
状況に応じて実行、スキップします。
:::

- GitHubリポジトリ

https://github.com/Yuuichi-M/travel-notes/tree/main/docker/dev

## 開発環境について

### 前提条件
- Dockerインストール済み
- VScodeインストール済み(Vimでも大丈夫です)

### 開発環境
- OS : Mac
- Docker 20.10.12
- Docker Compose v2.2.3
  - Docker image
    - laravel-app(appコンテナ)
      - php7.4-fpm-alpine(nginx,php-fpm,supervisor)
      - Laravel 6.20.34
    - laravel-db(mysqlコンテナ)
      - MySQL 8.0.27

### 開発環境ディレクトリ構成
```:docker/dev配下
.
├── app
│   ├──nginx
│   │   ├──dev.ドメイン名.conf
│   │   └──nginx.conf
│   ├──php
│ 　　　　│   ├──config.inc.php
│ 　　　　│   ├──php-fpm.conf
│ 　　　　│   ├──php.ini
│　　　　　　│   └── www.conf
│   ├──supervisor
│   │   ├──app.conf
│   │   └──supervisord.conf
│   └──Dockerfile
├──mysql
│   ├──Dockerfile
│   └──my.cnf
└──docker-compose.yml

```

### 開発環境構成図
![スクリーンショット 2022-03-07 1.22.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/891391/20540be5-ba22-e73e-b1da-c7066b72624a.png)

## 構築手順(サマリー)

|手順|説明|
|---|---|
|ディレクトリ作成|ホームディレクトリにgit/githubディレクトリを作成|
|git clone|作業ディレクトリに元の開発環境をgit cloneする<br/><font color="Red">※初めて構築する場合はスキップ</font>|
|作業ディレクトリ作成|git/github配下に作業ディレクトリを作成<br/><font color="Red">※初めて構築する場合のみ実行</font>|
|LEMP環境設定|appコンテナ,mysqlコンテナ,docker-compose.ymlの設定|
|docker run|コンテナの構築と起動|
|Laravel install|appコンテナに入り、Laravelのインストールを行います<br/><font color="Red">※初めて構築する場合のみ実行</font>|
|app deploy|appコンテナ内でcomposer install, マイグレーション<font color="Red">(初めて構築する場合はスキップ)</font>など実行|
|Access|設定したドメイン名でアクセス確認|
|DB login|MySQLにログインできることを確認|
|Vue.js install|<font color="Red">※Vue.jsを使用する場合に実行</font>|

## 構築手順

### ディレクトリ作成

- git/github配下に開発環境を構築します。
- git/github配下に移動

```:~/
$ mkdir -p ~/git/github
$ cd ~/git/github
```

### git clone

:::note warn
<font color="Red">初めて構築する場合はスキップ</font>

- GitHubリポジトリをcloneする。
- クローンしたディレクトリに移動

```:~/git/github/
$ git clone リモートリポジトリURL
$ cd クローンしたディレクトリ
```
:::

### 作業ディレクトリ作成

:::note warn
<font color="Red">初めて構築する場合のみ実行</font>
- git/github配下に任意のディレクトリを作成。
このディレクトリで開発をしていきます。
- 作成したディレクトリに移動

```:~/git/github/
$ mkdir 任意のディレクトリ名
$ cd 作成したディレクトリ
```
:::

### LEMP環境設定

#### docker/devディレクトリ作成

- docker/devディレクトリ作成
- docker/dev配下に移動

```:~/git/github/クローンしたリポジトリ
$ mkdir docker
$ mkdir docker/dev
$ cd docker/dev
```

#### 各ディレクトリ, docker-compose.yml作成
- app, mysqlディレクトリ作成
- docker-compose.yml作成

```:docker/dev/
$ mkdir app mysql
$ touch docker-compose.yml
```

#### appコンテナ設定

- appディレクトリに移動
- nginx, php, supervisorディレクトリ作成
- Dockerfile作成

```:docker/dev/
$ cd app
$ mkdir nginx php supervisor
$ touch Dockerfile
```

#### nginx設定

- nginx配下に移動
- dev.ドメイン名.conf, nginx.conf作成

```:app
$ cd nginx
$ touch dev.ドメイン名.conf nginx.conf
```

- dev.ドメイン名.conf設定

```PHP:dev.ドメイン名.conf
server {
    listen       80;
    server_name  dev.ドメイン名;

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

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

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        include fastcgi_params;
    }
}
```
設定ファイル内の説明

|設定|説明|
|---|---|
|server{}|バーチャルホスト定義|
|listen 80;|使用するポート指定|
|server_name|使用するホスト名(ドメイン名を指定)|
|root /var/www/app/public;|公開するディレクトリ指定|
|index index.php index.html index.htm;|index.phpというファイルが存在すれば、"/public/index.php"に内部リダイレクト。<br/>index.phpが在せず、index.htmlが存在すれば、/public/index.phpに内部リダイレクト。|
|access_log  /var/log/nginx/dev.access.log main;|エラーログの出力先を設定|
|error_log  /var/log/nginx/dev.error.log;|アクセスログの出力先を設定|
|location / {}|リクエストされたURIにマッチししたときに使われる設定。<br/>設定は{}内に書く|
|try_files $uri $uri/ /index.php?$query_string;|$urlにファイルがあるか、そしてディレクトリがあるかの確認。なければ/index.php$query_string へ内部リダイレクトする|
|location ~ \.php$ {}|location ~ \.php$の設定に従い、php-fpmを経由してLaravelの開始スクリプトのindex.phpが実行される|

location ~ \.php$ {}内の設定については以下記事の説明が参考になります

https://qiita.com/kotarella1110/items/3b0bd84fdb55276f37d9

- nginx.conf設定

:::note info
<font color="OrangeRed">include /etc/nginx/conf.d/*.conf;</font>で先程設定したdev.ドメイン名.confを読み込んでいます。
通常はnginx.confに直接書き込みますが、複数のバーチャルホストの設定を仮定してファイルを分割しました
:::

```PHP:nginx.conf
user root;
worker_processes auto;

error_log /var/log/nginx/error.log warn;
pid       /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main '[nginx]\t'
                    'time:$time_iso8601\t'
                    'server_addr:$server_addr\t'
                    'host:$remote_addr\t'
                    'method:$request_method\t'
                    'reqsize:$request_length\t'
                    'uri:$request_uri\t'
                    'query:$query_string\t'
                    'status:$status\t'
                    'size:$body_bytes_sent\t'
                    'referer:$http_referer\t'
                    'ua:$http_user_agent\t'
                    'forwardedfor:$http_x_forwarded_for\t'
                    'reqtime:$request_time\t'
                    'apptime:$upstream_response_time\t';

    access_log /var/log/nginx/access.log main;

    sendfile on;
    keepalive_timeout 65;

    gzip  on;
	gzip_static on;
    gzip_http_version 1.0;
    gzip_disable "msie6";
    gzip_proxied any;
    gzip_min_length 1024;
    gzip_comp_level 6;
	gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
    open_file_cache max=100000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    proxy_buffer_size 32k;
    proxy_buffers 50 32k;
    proxy_busy_buffers_size 32k;

    include /etc/nginx/conf.d/*.conf;
}
```

#### PHP設定

- php配下に移動
- config.inc.php, php-fpm.conf, php.ini, www.conf 作成

```
$ cd ../php
$ touch config.inc.php php-fpm.conf php.ini www.conf
```

:::note
以下のファイル設定についてはかなりの文字数となるため掲載は諦めました。
私のポートフォリオの設定ファイルがそれになります。
ご参考までに。
- config.inc.php
- php-fpm.conf
- php.ini
- www.conf

参考:[PHP設定(GitHub)](https://github.com/Yuuichi-M/travel-notes/tree/main/docker/dev/app/php)
:::

https://github.com/Yuuichi-M/travel-notes/tree/main/docker/dev/app/php

#### supervisor設定
- supervisor配下へ移動
- app.conf, supervisord.conf作成
- app.conf, supervisord.conf設定

:::note
Supervisorでnginx(webサーバー)とphp-fpm(アプリの起動)の起動プロセスを一括管理しています。
:::

参考：[Supervisor を Docker で使う](https://docs.docker.jp/engine/admin/using_supervisord.html)

```
$ cd ../supervisor
$ touch app.conf supervisord.conf
```

```PHP:app.conf
[supervisord]
nodaemon=true
[program:app]
command=/usr/local/sbin/php-fpm -F
autorestart=true
stopsignal=TERM
user=root
directory=/var/www/app/
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
stopsignal=TERM
user=root
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
```
```PHP:supervisord.conf
[unix_http_server]
file=/run/supervisord.sock

[supervisord]
logfile=/var/log/supervisord.log
loglevel=info
pidfile=/var/run/supervisord.pid

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///run/supervisord.sock

[include]
files = /etc/supervisor.d/*.ini
files = /etc/supervisor/conf.d/app.conf
```

#### Dockerfile設定

```PHP:Dockerfile
FROM php:7.4-fpm-alpine
ENV APP_ROOT /var/www/app
WORKDIR $APP_ROOT

# Setup UTC+9
RUN apk --update add tzdata && \
  cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
  apk del tzdata && \
  rm -rf /var/cache/apk/*

# install packages
RUN apk update && \
  apk upgrade && \
  apk add --update --no-cache \
  autoconf \
  bash \
  build-base \
  curl-dev \
  freetype-dev \
  g++ \
  gcc \
  git \
  libjpeg-turbo-dev \
  libpng-dev \
  libxml2-dev \
  libxslt-dev \
  make \
  mysql-dev \
  mysql-client \
  nginx \
  openssh \
  openssl \
  py3-pip \
  sudo \
  supervisor \
  tzdata \
  vim \
  redis \
  yaml-dev \
  zlib-dev

RUN docker-php-ext-install pdo_mysql soap

RUN docker-php-ext-configure gd \
  --with-freetype=/usr/include/ \
  --with-jpeg=/usr/include/ && \
  NPROC=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || 1) && \
  docker-php-ext-install -j${NPROC} gd

# Install awscli
RUN pip install awscli

# PHP
RUN rm -f /usr/local/etc/php-fpm.conf.default
RUN rm -f /usr/local/etc/php-fpm.d/zz-docker.conf
COPY php/php-fpm.conf /usr/local/etc/php-fpm.conf
COPY php/php.ini /usr/local/etc/php/php.ini
COPY php/www.conf /usr/local/etc/php-fpm.d/www.conf

# Install xdebug
RUN pecl install xdebug redis
RUN docker-php-ext-enable xdebug

# composer install
RUN curl -sS https://getcomposer.org/installer | php -- --version=1.9.1 --install-dir=/usr/bin
RUN mv /usr/bin/composer.phar /usr/bin/composer

# install Node.js
COPY --from=node:14.2 /usr/local/bin /usr/local/bin
COPY --from=node:14.2 /usr/local/lib /usr/local/lib

## nginx
ADD nginx/nginx.conf /etc/nginx/nginx.conf
ADD nginx/dev.ドメイン名.conf /etc/nginx/conf.d/dev.ドメイン名.conf

# setting superviser
COPY supervisor/supervisord.conf /etc/supervisord.conf
COPY supervisor/app.conf /etc/supervisor/conf.d/app.conf
RUN echo files = /etc/supervisor/conf.d/*.conf >> /etc/supervisord.conf

EXPOSE 80
CMD ["/usr/bin/supervisord"]
```
|設定|説明|
|---|---|
|FROM|ベースイメージ。php:7.4-fpm-alpine|
|ENV|環境変数|
|WORKDIR|指定したディレクトリでWORKDIR以下の命令が実行される。今回はENVで定義されたAPP_ROOT(=/var/www/app)|
|RUN|実行コマンド|
|ADD|ファイルを追加する命令|
|EXPOSE|コンテナ実行時にリッスンするポートやプロトコルをDockerに通知する命令。|
|CMD|コンテナの初期設定の命令。Dockerfileの中で１つだけ指定できる。<br/>複数書いた場合は最後に書いたものが実行される。<br/>supervisordを指定し、管理対象ファイルを実行。|

参考:[Dockerfileリファレンス](https://docs.docker.jp/engine/reference/builder.html#from)

#### MySQLコンテナ設定

- mysql配下に移動
- Dockerfile, my.cnf作成
- Dockerfile, my.cnf設定

```
$ cd ../../mysql
$ touch Dockerfile my.cnf
```

```PHP:Dockerfile
FROM --platform=linux/x86_64 mysql:8.0.27

ENV TZ Asia/Tokyo
ENV LC_ALL ja_JP.UTF-8
ENV MYSQL_ALLOW_EMPTY_PASSWORD yes

RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 467B942D3A79BD29
RUN apt-get update
RUN apt-get install -y --no-install-recommends\
  locales \
  python \
  vim \
  && apt-get clean \
  && rm -rf /var/cache/apt/archives/* \
  /var/lib/apt/lists/*

RUN echo "ja_JP.UTF-8 UTF-8" > /etc/locale.gen && \
  locale-gen ja_JP.UTF-8

# Setup MySQL
RUN touch /var/log/mysqld.log \
  && chown mysql:adm /var/log/mysqld.log

RUN mkdir /var/mysql \
  && chown mysql:adm /var/mysql \
  && rm -rf /etc/mysql/conf.d

COPY ./my.cnf /etc/mysql/
```

```PHP:my.cnf
[mysqld]
pid-file        = /var/run/mysqld/mysqld.pid
socket          = /var/run/mysqld/mysqld.sock
datadir         = /var/lib/mysql
secure-file-priv= NULL
disable_log_bin
log-error=/var/log/mysqld.log

character_set_server=utf8mb4
collation-server=utf8mb4_bin
default-authentication-plugin = mysql_native_password
default-time-zone='+9:00'

innodb_file_per_table=ON
innodb_buffer_pool_size=4G
#innodb_log_file_size=1G
join_buffer_size=256K
read_buffer_size=1M
read_rnd_buffer_size=2M
sort_buffer_size=4M
max_heap_table_size=16M
tmp_table_size=16M
thread_cache_size=100
wait_timeout            = 86400
max_allowed_packet      = 1G
innodb_buffer_pool_size = 1G
max_allowed_packet = 16M
innodb_strict_mode = 0
long_query_time = 5

sql_mode = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

[mysql]
default-character-set=utf8mb4
```

#### docker-compose.yml設定

- dev配下に移動
- docker-compose.yml設定

```
$ cd ../
```

```PHP:docker-compose.yml
version: '2'
services:
    app:
        container_name: アプリ名-app
        build: ./app/
        image: アプリ名-app
        ports:
            - '80:80'
        volumes:
            - ~/git/github/アプリのディレクトリ名:/var/www/app:delegated
        tty: true
        depends_on:
            - db
    db:
        container_name: アプリ名-db
        build: ./mysql/
        image: アプリ名-db
        command: --default-authentication-plugin=mysql_native_password
        ports:
            - '3306:3306'
        environment:
            MYSQL_DATABASE: アプリのディレクトリ名
        volumes:
            - ./mysql/db_data:/var/lib/mysql
```
|設定|説明|
|---|---|
|version|Composeファイルのバージョン。参考：[公式サイト](https://docs.docker.com/compose/compose-file/)|
|services|サービスの指定。サービス名:app, db|
|container_name|コンテナ名:アプリ名-app, アプリ名-db(分かりやすい名前が無難)|
|build|サービスをビルドする。Dockerfileを使ってDockerイメージを構築|
|image|イメージ名:アプリ名-app, アプリ名-db|
|ports|ローカル(ホスト)側とDockerコンテナ側のポート番号の関連付け<br/>ローカルの80番ポートとNginxコンテナの80番ポートをマッピングしている。|
|environment|環境変数|
|volumes|ローカルとDockerコンテナ間のディレクトリのリソースを関連付ける設定|
|tty|コンテナを起動し続ける設定|
|depends_on|コンテナの起動順。DBコンテナから起動するように設定|

#### 環境変数の設定

:::note warn
<font color="Red">初めて構築する場合はスキップ</font>

- アプリのディレクトリへ移動
- .envの作成、環境変数の設定

```
$ cd ~/git/github/アプリのディレクトリ名

$ cp .env.example .env
// 作成した.envの設定をする
```
:::

### docker run

- docker/dev配下へ移動
- コンテナの構築と起動
- コンテナの起動を確認

```
$ cd docker/dev
$ docker-compose up -d
$ docker-compose ps
```

### Laravel install

:::note warn
<font color="Red">初めて構築する場合のみ実行</font>

- appコンテナに入る
- Composerインストール
- Composerバージョン確認
- Laravelインストール
- Laravelのディレクトリやファイルを確認
- .env作成、環境変数の設定
- APP_KEY生成
- .envの更新反映
- app再起動

```
$ docker exec -it appコンテナ名 bash
# composer install
# composer -V
# composer create-project --prefer-dist "laravel/laravel=6.*" .
# ls
# cp .env.example .env
// 作成した.envの設定をする
# php artisan key:generate
# php artisan config:cache
# /usr/bin/supervisorctl restart app
```
:::

### app deploy

:::note warn
<font color="Red">初めて構築する場合はスキップ</font>
- appコンテナに入る
- Composerインストール
- Composerバージョン確認
- APP_KEY生成
- .envの更新反映
- マイグレーション
- app再起動

```
$ docker exec -it appコンテナ名 bash
# composer install
# composer -V
# php artisan key:generate
# php artisan config:cache
# php artisan migrate
# /usr/bin/supervisorctl restart app
```
:::

- gitignore修正
```:gitignore
/node_modules
/public/hot
/public/storage
/storage/*.key
/vendor
.env
.env.backup
.phpunit.result.cache
docker-compose.override.yml
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
/docker/dev/mysql/db_data 
```

### Access

- /etc/hosts編集

```
$ sudo vim /etc/hosts
127.0.0.1 dev.ドメイン名　に変更
```

- `http://dev.ドメイン名/`にアクセスしてアプリ(又はLaravelウェルカムページ)の表示確認できたら完成

**再構築の場合**

![スクリーンショット 2022-04-01 17.27.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/891391/d288b38c-19e8-fff2-1c50-45d4d9d15909.png)

**初構築の場合**
![スクリーンショット 2022-03-18 4.10.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/891391/702eb999-59bd-49cd-b001-0f7e520f8e11.png)

### DB login

-appコンテナに入る
- 最後にMySQLログイン確認

```
$ docker exec -it appコンテナ名 bash
# mysql -u root -h db -p
```

### Vue.js install
:::note warn
<font color="Red">※Vue.jsを使用する場合に実行</font>

- appコンテナに入る
- npmバージョン確認
- npmがinstallされてない場合は以下手順
  - npm インストール
  - npmバージョン確認
- Vue.sインストール

```
$ docker exec -it appコンテナ名 bash
# npm -v

// npmがinstallされてない場合は以下手順
   # npm install
   # npm -v

# npm install -D vue
# npm install -D vue-template-compiler
```
:::

## 振り返り

Dockerひとつとっても様々な構築手順があるんだなぁと感じました。
まだまだnginx, php-fpmなどの設定に関する知識が乏しいので、これから深く学習していきます!!

やはりDockerを使用した環境構築は速くて便利!!　自由自在に構築できるようになりたいですね。

## 参考

https://qiita.com/ucan-lab/items/5fc1281cd8076c8ac9f4

https://qiita.com/k_hoso/items/33ccb5e02e73a244ed31

https://qiita.com/zukka/items/d5e97119fbe69607a5b8

https://qiita.com/zukka/items/8b488fd552a3a7a1ac64#nginx%E8%A8%AD%E5%AE%9A%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB

https://heartbeats.jp/hbblog/2012/04/nginx05.html

https://qiita.com/kotarella1110/items/3b0bd84fdb55276f37d9