docker-composeを使ってDocker上にLaravelを構築する方法（Nginx & php-fpm & MySQL）

docker-composeを使ってDocker上にLaravelを構築する方法。WebサーバーはNignx + php-fpmを使用。

##目次

1. [Laravelをローカルに構築](#1-laravelをローカルに構築する)
2. [.envファイルの設定](#envファイルの設定)
3. [dockerにcomposerのデータをコピー](#2-dockerにcomposerのデータをコピーする)
4. [docker-compose.ymlを作成](#3-docker-composeymlを作成する)
5. [Dockerfileの作成](#4-dockerfileの作成)
6. [PHPの構成](#5-phpの構成)
7. [NginxとPHP-FPM](#nginxとphp-fpm)
8. [Nginxの設定](#6-nginxの設定)
9. [MySQLの設定](#7-mysqlの設定)
10. [コンテナ起動](#7-コンテナ起動)
    11. [コンテナの起動状況の確認](#コンテナの起動状況の確認)
    12. [ブラウザで確認](#ブラウザで確認)
13. [（補足）ブラウザ表示でエラーが出た場合の対処法](#補足ブラウザ表示でエラーが出た場合の対処法)


<br>
##1. Laravelをローカルに構築する

###1-1. githubからクローン

```
git clone https://github.com/laravel/laravel.git <プロジェクト名>
```

<br>
###1-2. composerを使ってバンドルをインストールする。

```
$ composer update
```

composer.jsonに書いてあるバンドルがズラーっとインストールされる。


###1-3 .envファイルの設定
まずは.envファイルを作成する。（.env.exampleをコピーする）

```
cp .env.example .env
```

次にキーを作成する。

```
$ php artisan key:generate
```

MySQLと接続するためにDBの設定を変更する。

```
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=******
```

DB_DATABASEとDB_PASSWORDは次で作成するdocker-composer.ymlと合わせる。


<br>
###（補足）composerでプロジェクトを作成する方法

以下リンクのNo3まででローカルで動くLaravelの環境を構築できる。

[Laravelプロジェクトの作り方](https://qiita.com/yuta-38/items/072f38500da41b74db64)


<br>
##2. dockerにcomposerのデータをコピー
プロジェクトのルートディレクトリに移動後、以下を実行。

```
$ docker run --rm -v $(pwd):/app composer install
```

- composerイメージからコンテナを作成しinstallを実行。
- 現在のディレクトリをコンテナ内の/appにマウント。
- コンテナ起動後にコンテナを削除する。

composerの必要なデータのみローカルの現在のディレクトリにコピーする。

バンドルが記述された`composer.lock`がローカルのルートディレクトリに追加される。


### docker runコマンド
・`docker run [オプション] イメージ [コマンド] [引数...]`


####--rmオプション
コンテナ終了時、自動的に削除。
composerのイメージとコンテナは不要なため。必要なデータのみマウントしたボリュームにコピーしておく。


####-vオプション
ボリュームのマウント。
ローカルのディレクトリをコンテナ内のディレクトリにマウントする。

`--volume`の略。

**▼ボリュームのマウント方法**

`-v [ホスト側ソース:]コンテナ側送信先[:<オプション>]`

- -v $(pwd):/app
- ホスト側ソースは絶対パスか、ボリューム名で指定。
    - $(pwd)：ホスト側の現在のディレクトリパス
    - /app: コンテナ内のappディレクトリ

<br>
####起動するイメージの指定
imageに`composer`を指定。タグを指定していない場合は`latest`。

dockerはまず、localで`composer:latest`を探す。なければdocker hubから指定のイメージを探す。

・[docker hun公式 composer image](https://hub.docker.com/_/composer/)


<br>
####コマンド実行例

```
$ docker run --rm -v $(pwd):/app composer install
Unable to find image 'composer:latest' locally
latest: Pulling from library/composer
8bb409133b6b: Pull complete 
Digest: sha256:447516936298f9f63d0b718d1a21bd70066b6c206e174cd29938c79b8eefe7b1
Status: Downloaded newer image for composer:latest
No lock file found. Updating dependencies instead of installing from lock file. Use composer update over composer install if you do not have a lock file.
Loading composer repositories with package information
Updating dependencies
Lock file operations: 105 installs, 0 updates, 0 removals
  - Locking asm89/stack-cors (v2.0.2)
  - Locking brick/math (0.9.2)
  （省略）
Writing lock file
Installing dependencies from lock file (including require-dev)
Nothing to install, update or remove
77 package suggestions were added by new dependencies, use `composer suggest` to see details.
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: facade/ignition
Discovered Package: fideloper/proxy
Discovered Package: fruitcake/laravel-cors
Discovered Package: laravel/sail
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Package manifest generated successfully.
74 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
```

ボリュームをマウントしているので、作成されたファイルがローカルに保存される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/538723a7-bd9c-2d53-5cb8-044f451a043a.png)


<br>
##3. docker-compose.ymlを作成する

```
$ vim docker-compose.yml
``` 

vimエディタで`i`をクリックしてinsertモードに入り、以下をコピペ。

コピペ後は、`esc` + `:wq`で保存して終了。

（参考）[vimの使い方](https://qiita.com/yuta-38/items/733a4f43278414bcbd2c)


###docker-compose.yml

```yml:docker-compose.yml
version: '3'
services:

  #PHP Service
  app:
    build:
      context: .
      #dockerfile: Dockerfile
    image: app_php-fpm
    container_name: appdocker
    restart: unless-stopped
    tty: true
    environment:
      SERVICE_NAME: app
      SERVICE_TAGS: dev
    working_dir: /var/www
    volumes:
      - ./:/var/www
      - ./php/local.ini:/usr/local/etc/php/conf.d/local.ini
    networks:
      - app-network
    links:
      - webserver
      - db

  #Nginx Service
  webserver:
    image: nginx:alpine
    container_name: webserver
    restart: unless-stopped
    tty: true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./:/var/www
      - ./nginx/conf.d/:/etc/nginx/conf.d/
    networks:
      - app-network

  #MySQL Service
  db:
    image: mysql:5.7.22
    container_name: db
    restart: unless-stopped
    tty: true
    ports:
      - "3306:3306"
    environment:
      MYSQL_DATABASE: laravel
      MYSQL_ROOT_PASSWORD: password
      SERVICE_TAGS: dev
      SERVICE_NAME: mysql
    volumes:
      - dbdata:/var/lib/mysql/
      - ./mysql/my.cnf:/etc/mysql/my.cnf
    networks:
      - app-network

#Docker Networks
networks:
  app-network:
    driver: bridge
#Volumes
volumes:
  dbdata:
    driver: local
```

（参考）[docker-composeの処理内容詳細](https://qiita.com/yuta-38/items/565236be9b99cfef4815)


<br>
##4. Dockerfileの作成 

```
$ vim Dockerfile
``` 

vimエディタで`i`をクリックしてinsertモードに入り、以下をコピペ。

コピペ後は、`esc` + `:wq`で保存して終了。

```Dockerfile:Dockerfile
FROM php:7.3-fpm

# Copy composer.lock and composer.json
COPY composer.lock composer.json /var/www/

# Set working directory
WORKDIR /var/www

# Install dependencies
RUN apt-get update && apt-get install -y \
    build-essential \
    libpng-dev \
    libjpeg62-turbo-dev \
    libfreetype6-dev \
    locales \
    zip \
    jpegoptim optipng pngquant gifsicle \
    vim \
    unzip \
    git \
    curl \
    libonig-dev \
    libzip-dev

# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*

# Install extensions
RUN docker-php-ext-install pdo_mysql zip exif pcntl
RUN docker-php-ext-configure gd --with-gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ --with-png-dir=/usr/include/
RUN docker-php-ext-install gd

# Install composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Add user for laravel application
RUN groupadd -g 1000 www
RUN useradd -u 1000 -ms /bin/bash -g www www

# Copy and change existing application directory permissions
COPY --chown=www:www . /var/www

# Change current user to www
USER www

# Expose port 9000 and start php-fpm server
EXPOSE 9000
CMD ["php-fpm"]
```

・`FROM php:7.2-fpm`
ベースイメージにphp:7.2-fpmを指定。

**▼php:7.2-fpm**
PHP-FPMがインストールされているDebianベースのイメージ。


（参考）[Dockerfileの処理内容詳細](https://qiita.com/yuta-38/items/565236be9b99cfef4815)

<br>
###5. PHPの構成
ディレクトリPHPの中にlocal.iniを作成する。

```
$ mkdir php
$ vim php/local.ini
```

```ini:local.ini
upload_max_filesize=40M
post_max_size=40M
```

例として、PHPの構成として、アップロードできるファイルサイズ上限を40Mに設定している。

PHPの構成が完了したら、NginxでFastCGIサーバーとしてPHP-FPMを使用するように設定する。


<br>
##NginxとPHP-FPM
NignxはApachと異なりPHPを直接処理できない。このため、**PHP-FPMと接続してPHPを処理できるようにする**。

PHP-FPMはFast-CGI。Fast-CGIとは、WEBサーバー上でプログラムを動かすためのCGI（Common Gateway Interface）の改良版。Fastは速いの意味。

次のNginxの設定では、Fast-CGIとしてPHP-FPMを使うための設定を記述する。


<br>
##6. Nginxの設定
nginx/conf.dディレクトリにapp.confファイルを作成する。

```
$ mkdir -p nginx/conf.d
$ vim nginx/conf.d/app.conf
```

vimエディタで`i`をクリックしてinsertモードに入り、以下をコピペ。

コピペ後は、`esc` + `:wq`で保存して終了。

```conf:app.conf
server {
    listen 80;
    index index.php index.html;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/public;
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
    location / {
        try_files $uri $uri/ /index.php?$query_string;
        gzip_static on;
    }
}
```

・`conf.d`
.dはディレクトリ名であることを明示。
nginxではconf.d配下にあるすべての`.conf`ファイルを読み込む設定がされている。

```:設定ファイルの読み込み指示例
include /etc/nginx/conf.d/*.conf;
```

・`server`
httpのサーバーの設定。

・`index`
indexディレクティブ。URIが`/`で終わる場合（ディレクトリが指定されている）場合にリダイレクトするファイルを指定する。

```:indexディレクティブの例
index index.php index.html;
```

リクエストされたURIが`/hoge/`の場合に、index.phpがあれば、`hoge/index.php`にリダイレクトする。ない場合は`hoge/index.html`にリダイレクトする。

>デフォルトは`index index.html`となっている。

・`listen 80`
listenディレクティブ。nginxサーバーが使用するポート番号に80を指定。

・`error_log`
エラーログを書き込むためのファイルを指定。

・`access_log`
アクセスログを書き込むためのファイルを指定。

・`root`
ドキュメントルートのディレクトリを指定する。
「/var/www/public」を指定している。

・`location`
特定のuriでリクエストがきた場合の処理を設定。prefixを使って指定する（完全一致や正規表現など）。

指定したuriに一致したら{ }内の処理を実行する。

```
location <prefix> <uri> {
    処理
}
```

**▼正規表現の指定**

| prefix | 内容 |
|:-:|:-:|
| なし  | 前方一致 |
| =  | 完全一致  |
| ~  | 正規表現（大文字・小文字を区別する） |
| ~*  | 正規表現（大文字・小文字を区別しない）  |


####実例（PHP-FPMの設定）
以下はNginxをPHP-FPMに接続する設定。

```conf:
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
```

正規表現で、`\.php$`　（末尾が.php）なら処理を実行。

####・try_files
指定したパスの存在確認をする。（if文に似た処理）
上記であれば、.phpにアクセスした場合は`404`を返す。


####・fastcgi_split_path_info
リクエストで渡されたURIを２分割する。

```
fastcgi_split_path_info ^(正規表現1)(正規表現2)$;
```

**▼正規表現１**
`$fastcgi_script_name`の値になる。
ここでは、(.+\.php)なので、「*.php」が該当。

**▼正規表現２**
`$fastcgi_path_info`の値になる。
ここでは、(/.+)なので、*.php/以下が該当。

**▼実例**
例えばリクエストが`/show.php/article/0001`の場合以下のようになる。

```
$fastcgi_script_name=$document_root/show.php
$fastcgi_path_info=article/0001
```


####・fastcgi_pass アドレス
Fast-CGIサーバーのアドレスを指定する。


####・include ファイル
指定したファイルを読み込む。
ここでは設定ファイルとしてfastcgi_paramsを読み込んでいる。

>DebianやUbuntuの場合設定ファイルはデフォルトで/etc/nginx/fastcgi_paramsにある。
・[NGINX PHP FastCGI Example](https://www.nginx.com/resources/wiki/start/topics/examples/phpfcgi/)

####・fastcgi_index ファイル
URIのパスがスラッシュ`/`で終わる（ディレクトリを指定）場合に開くファイルを指定。


####・fastcgi_param SCRIPT_FILENAME ファイル
実行するファイル名を指定する。

`$document_root$fastcgi_script_name`なので、ドキュメントルートの後に、`$fastcgi_script_name`の値が続く。

**▼$fastcgi_script_name**
fastcgi_split_path_infoの一つ目の正規表現が該当する。つまり「*.php」となり、リクエストされたURIと一致するphpファイルが実行される。


####fastcgi_indexとfastcgi_param SCRIPT_FILENAME
渡されたURIによって使用するphpファイルを切り替える処理になる。

```
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /home/www/scripts/php$fastcgi_script_name;
```

上記設定に対して、/page.phpが渡された場合、SCRIPT_FILENAMEが処理され、/home/www/scripts/php/page.phpが実行される。

一方、URIの終わりがスラッシュで、/page.php/の場合、fastcgi_indexが処理され、home/www/scripts/php/index.phpが実行される。


####・fastcgi_param PATH_INFO
パスの情報。
ここではリクエストされたパスの.php以降の内容が入る。


・（参考）[Nginx http_fastgis_modules](http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#variables)


<br>
###２つ目のlocationの処理内容

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

リクエストされたURIが`/*`（前方一致）の場合、処理を実行する。

####・`try_files $uri $uri/ /index.php?`
ファイルの存在確認をしあれば実行。なければ次のファイルの存在確認を行う。
存在確認する優先順位は、`$uri`, `$uri/`, `/index.php?`となる。


####・`gzip_static on;`
gzipで圧縮済みのファイル「.gz」があればそちらを配信する。




<br>
##7. MySQLの設定
mysqlディレクトリの中にmy.cnfファイルを作成する。

```
$ mkdir mysql
$ vim mysql/my.cnf
```

vimエディタで`i`をクリックしてinsertモードに入り、以下をコピペ。

コピペ後は、`esc` + `:wq`で保存して終了。

```conf:app.conf
[mysqld]
general_log = 1
general_log_file = /var/lib/mysql/general.log
```

ここではMySQLのログの設定をしている。

####・general_log
値を１（or ON）にするとログを出力する。
0（or OFF or 記述なし）の場合はログ出力をしない。


####・general_log_file
指定したファイルにログを出力する。


<br>
##7. コンテナ起動
docker-compose.ymlのあるディレクトリで以下を実行する。

```
$ docker-compose up -d app
```
サービスappを起動すれば、linkでwebserver, dbもつながっているため同時に起動する。

`-d`は デタッチド・モード。バックグラウンドでコンテナを実行するため、コンテナ起動後もそのターミナルでコマンドが打てる状態になる。


**▼（補足）サービスを指定しない場合**
サービスの指定がなければ、docker-composeに記述されたすべてのサービスが起動する。



###コンテナの起動状況の確認
`$ docker ps`コマンドで起動中のコンテナ一覧を表示できる。

```
$ docker ps
CONTAINER ID   IMAGE                       COMMAND                  CREATED          STATUS          PORTS                                      NAMES
04012cd50d18   mysql:5.7.22                "docker-entrypoint.s…"   6 minutes ago    Up 6 minutes    3306/tcp, 0.0.0.0:3306->3306/tcp           db
6d0cd9138856   app_php-fpm                 "docker-php-entrypoi…"   6 minutes ago    Up 6 minutes    9000/tcp                                   app
f02cf8788728   nginx:alpine                "/docker-entrypoint.…"   6 minutes ago    Up 6 minutes    0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   webserver
```

docker-composeのサービスで定義した、app, webserver, dbが起動している。


###ブラウザで確認

`localhost:80`を入力するとLaravelのwelcome.blade.phpが表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/bdf79f0a-43e1-3ad7-26d1-20cc3a2cd02a.png)


<br>
##（補足）ブラウザ表示でエラーが出た場合の対処法
以下のようなエラーが出たらPHPのバージョンを上げ、インストールするバンドルやエクステンションを変更することで解決できる。

>Fatal error: Composer detected issues in your platform: Your Composer dependencies require a PHP version ">= x.x.x". You are running y.y.y. in /var/www/vendor/composer/platform_check.php on line 24

・[PHPバージョンエラー対処法](https://qiita.com/yuta-38/items/e5959f75dc015fe5d78b)


<br>
以上。