Dockerを用いてNginxをWebサーバーとし、PHP(Laravel)、DBはPostgresSQLの開発環境を作成する。

#目的#
ローカルでのLaravelの開発環境を作ってみたいので、Dockerを用いてNginxをWebサーバーとし、PHP(Laravel)、DBはPostgresSQLでローカルの開発環境を作成したいと思います。

選定理由はそれぞれ以下の通りです。
Docker　→　ローカルの環境への影響をなるべく少なく開発環境を作りたい、および新しい技術の習得
Nginx　→　使用したことがないので、使用してみたい。
PHP　→　一般的なWeb用のプログラミング言語だと思うので、使用してみたい。
Laravel　→　MVCモデルのフレームワークを使用してみたい。
PostgresSQL →　最終的にHerokuへのデプロイを考えており、HerokuにおいてはデフォルトのDBであることと、使用したことがないので使用してみたい。

#事前準備#
https://qiita.com/roughstorm/items/6a4fec7c99f7c8dec7a4


#参考#

https://www.membersedge.co.jp/blog/laravel-development-environment-with-docker-compose/

こちらの記事が大変参考になりました。

#開発環境用ディレクトリ、ファイルの作成#
```powershell:powershell
mkdir docker
cd docker
mkdir docker
mkdir docker/web
mkdir docker/php
New-Item docker/php/dockerfile    #Dockerイメージを定義するファイル
New-Item docker/web/default.conf　#nginx用の設定ファイル
New-Item docker-compose.yml　     #Docker上で使用するサービスを定義するファイル
New-Item index.html               #動作確認用
New-Item index.php                #動作確認用
cd docker
```
任意のディレクトリにてPowershellから上記を実行し、開発環境用のフォルダ,ファイルを作成します。

#Webサーバー(nginx)の設定、動作確認#

参考記事に倣い、Webサーバー(nginx)の設定、動作確認（コンテナの定義、起動)から行います。

```yaml:docker-compose.yml
version: '3'
services:
  web:
    image: nginx:1.15.6
    ports:
      - '8000:80'
    volumes:
      - ./docker/web/default.conf:/etc/nginx/conf.d/default.conf
      - .:/var/www/html
```
docker-compose.ymlを上記の様に編集します。

`version`でdocker-compose.yml自体のバージョン（ファイルの書き方）を指定します。

`services`内に実行するサービス（今回だったらnginx)に必要な定義を書いていきます。

`web`はDocker内でのコンテナの名前の定義なので、自分で自由に決めてください。Dockerコマンドでコンテナを指定するときなどにも使います。

`image`は用途にあったものを指定します。今回はnginxを使いたいので、`nginx:1.15.6`と指定しています。
下記URLのDocker Hubからimageを指定するTagを見つけられると思います。

https://hub.docker.com/

`ports`は、ホスト側のポートとコンテナ側のポートをどの様に転送するかを指定します。
今回のように`8000:80`と記述すると、ホスト側から`localhost:8000`にアクセスすることでDocker側の`localhost:80`(80はhttpのデフォルトポート）に転送されます。

`volumes`はホスト側とコンテナ側のファイルの共有の設定です。
`ホスト側のパス`：`コンテナ側のパス`というように記述します。
今回のように、
`./docker/web/default.conf`:`/etc/nginx/conf.d/default.conf`と記述した場合、
`./docker/web/default.conf`がホスト側のパス、
`/etc/nginx/conf.d/default.conf`がコンテナ側のパスとなりますので、
`./docker/web/default.conf`を編集すると、
コンテナ側の`/etc/nginx/conf.d/default.conf`へ編集の結果が反映されます。
（いじったファイルがそのままDocker上に反映される。）

上記以外の項目を設定することもあります。下記の記事が参考になります。

https://qiita.com/yuta-ushijima/items/d3d98177e1b28f736f04


続いて、Webサーバー(nginx)の設定ファイルを記述します。

```conf:default.conf
server {
    listen 80;
    root /var/www/html;
    index index.html;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
}
```

`listen`でWebサーバー（nginx)がリクエストを受け付けるポート番号、IPアドレスなどを設定します。今回は`80`とポート番号のみを記述し、IPアドレスの記述をしていないので、すべてのIPアドレスの80番ポートでリクエストを受け付けます。

`root`でドキュメントルートのディレクトリ、`index`でインデックスとして使われるファイル名を指定します。
リクエストのURIが/で終わっている場合にレスポンスとして返すディレクトリとファイル名を指定しています。
今回は`root`に`/var/www/html`、`index`に`index.html`を記述しているので、
例えば、`http://localhost:8000/`にアクセスした場合、`/var/www/html/index.html`をレスポンスとして返します。

`access_log`, `error_log`はそれぞれのログの出力パスを記述しています。

上記以外の項目を設定することもあります。下記の記事が参考になります。

https://qiita.com/townsoft/items/3e2d410bbe65a364b603


最後に動作確認用にindex.htmlの中身を記述します。

```html:index.html
<h1>docker-compose-laravel</h1>
<p>Served by Nginx</p>
```
以上でファイルの準備が終了したので、Powershellからdocker-compose up(サービス用のコンテナの構築、作成、起動、アタッチ)をします。

```Powershell
docker-compose up -d
```
`docker-compose up -d`はdocker-compose.ymlがあるディレクトリで実行をしてください。

コマンド実行後、localhost:8000 にブラウザでアクセスすると、index.htmlに記述した内容がブラウザから確認出来ると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286510/ca70fe3e-7b24-3160-8ad9-b5a9cadbb934.png)


#PHPの設定、動作確認及びPHPとnginxの連携の設定、動作確認#

続いて、PHPの設定（コンテナの定義、起動）及びPHPとnginxの連携の設定、動作確認を行います。

```yaml:docker-compose.yml
version: '3'
services:
  web:
    image: nginx:1.15.6
    ports:
      - '8000:80'
    depends_on:
      - app
    volumes:
      - ./docker/web/default.conf:/etc/nginx/conf.d/default.conf
      - .:/var/www/html
  app:
    image: php:7.2-fpm
    volumes:
      - .:/var/www/html
```
`docker-compose.yml`にPHP用の記述を追加します。
`web`内に`depends_on`要素を追加し、新たに`app`コンテナの定義を追加します。
`depends_on`を指定すると、サービス間の依存関係を定義することができます。

今回のように`web`内の`depends_on`に`app`を指定すると、
`web`(nginx)は`app`(PHP)に依存しているということになり、
`app`(PHP)のコンテナが先に起動し、`web`(nginx)のコンテナが後に起動することになります。


```conf:default.conf
server {
    listen 80;
    root /var/www/html;
    index index.php index.html index.htm;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        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;
    }

}
```
`index`の記述を`index.html;`から`index.php index.html index.htm;`へ変更しています。
この様に、複数のindexファイルを指定すると、左から順にindexのファイルを探していき、一致したものをレスポンスとして返します。
この記述の場合、例えば、ディレクトリ内にindex.phpとindex.htmlがあった場合は、index.phpを返し、index.htmlとindex.htmがあった場合は、index.htmlを返します。

`location / {~ ~}`については下記のページがとてもわかりやすかったです。

https://www.nyamucoro.com/entry/2018/03/02/232940

`location ~ \.php$ {~ ~}`はWebサーバー(nginx)からPHP(PHP-FPM)へリクエストを渡す設定です。
`fastcgi_pass app:9000;`という書き方は、Dockerのコンテナで動かす場合のみ有効です。
本来は、`127.0.0.1:9000`のように設定するようです。


```php:index.php
<h1>docker-compose-laravel</h1>
<p>Served by Nginx</p>
<?php phpinfo(); ?>
```
動作確認用にindex.phpを上記の様に編集・保存してください。

```powershell
docker-compose up -d
```
`docker-compose up`を実行します。

コマンド実行後、localhost:8000 にブラウザでアクセスすると、index.phpに記述した内容がブラウザから確認出来ると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286510/158fa4bf-852e-dc6b-f939-0d49b02af9cf.png)


#データベース(PostgressSQL)の設定、動作確認#

続いて、PostgresSQLおよびPG Admin(PostgreSQL用のWebのGUI）の設定（コンテナの定義、起動）、動作確認を行います。

下記記事を参考にいたしました。

https://mebee.info/2020/12/04/post-24686/



```yaml
version: '3'

services:
  web:
    image: nginx:1.15.6
    ports:
      - '8000:80'
    depends_on:
      - app
    volumes:
      - ./docker/web/default.conf:/etc/nginx/conf.d/default.conf
      - .:/var/www/html
  app:
    image: php:7.2-fpm
    volumes:
      - .:/var/www/html
  postgres:
    image: postgres:latest
    restart: always
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      PGPASSWORD: password123
      POSTGRES_DB: sample
      TZ: "Asia/Tokyo"
    ports:
      - 5432:5432
    volumes:
      - postgres:/var/lib/postgresql/data
   
  pgadmin:
    image: dpage/pgadmin4
    restart: always
    ports:
      - 81:80
    environment:
      PGADMIN_DEFAULT_EMAIL: ***
      PGADMIN_DEFAULT_PASSWORD: password
    volumes:
      - pgadmin:/var/lib/pgadmin
    depends_on:
      - postgres
    
volumes:
  postgres:
  pgadmin:
```
`docker-compose.yml`にPostgresSQL,PG Admin用の記述を追加します。
`postgres`, `pgadmin`コンテナ用の記述と、`volumes`の記述を追加します。

`restart`要素では、コンテナの自動再起動の設定ができます。今回のように`always`を指定すると、コンテナが停止した場合基本的に勝手に再起動します。（手動除く）

`environment`要素ではそれぞれのコンテナ内での環境変数を指定します。`postgres`内では、PostgresSQL内で使用するDB名やユーザー名、パスワードなどを設定しています。

トップレベルにある`volumes`に`postgres`, `pgadmin`を指定することによって、
他のコンテナ(`app`(PHP))から`postgres`などのコンテナを参照できるようになります。

`PGADMIN_DEFAULT_EMAIL: ***`は、適宜メールアドレスを入れて下さい。

```powershell
docker-compose up -d
```
`docker-compose up`を実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286510/38d86bd9-8699-cb46-4c1e-42355ff5a17c.png)

コマンド実行後、localhost:81 にブラウザでアクセスすると、
上画像のようにPG Adminが表示されることが確認できると思います。
*起動に時間がかかる時があるっぽいです。
**ログインは`environment`で指定したメールアドレスとパスワードでできます。

Powershellから下記のコマンドを実行し、PostgresSQLのコンテナが起動したことを確認できると思います。

```powershell
docker-compose exec postgres bash
psql -d postgres -U user;
\l
                             List of databases
   Name    | Owner | Encoding |  Collate   |   Ctype    | Access privileges
-----------+-------+----------+------------+------------+-------------------
 postgres  | user  | UTF8     | en_US.utf8 | en_US.utf8 |
 sample    | user  | UTF8     | en_US.utf8 | en_US.utf8 |
 template0 | user  | UTF8     | en_US.utf8 | en_US.utf8 | =c/user          +
           |       |          |            |            | user=CTc/user
 template1 | user  | UTF8     | en_US.utf8 | en_US.utf8 | =c/user          +
           |       |          |            |            | user=CTc/user

```

`docker exec -it docker-compose-laravel_postgres_1 bash`で`postgres`コンテナの中に入ります。
`psql -d postgres -U user;` でPostgreSQLの中に入ります。
`\l`を実行して、現在のデータベースの中を確認し、ymlで定義したsampleデータベースが作成されたことが確認できると思います。


#Laravelのインストール#

さて、Webサーバー、PHP、DBの設定が終わったので、最後にLaravelのインストールをします。

```dockerfile
FROM php:7.2-fpm

#install composer
RUN cd /usr/bin && curl -s http://getcomposer.org/installer | php && ln -s /usr/bin/composer.phar /usr/bin/composer

RUN apt-get update \
&& apt-get install -y \
git \
zip \
unzip \
vim

RUN apt-get update \
    && apt-get install -y libpq-dev \
    && docker-php-ext-install pdo_pgsql

WORKDIR /var/www/html

```
まず`dockerfile`を上記の様に編集します。
`FROM`にはイメージを指定します。
今回は、PHPが使える環境の上にLaravelのインストールを行うので、php:7.2-fpmを指定します。

`RUN`はDockerのイメージがビルドされるときに、コンテナ内で実行するコマンドを指定することができます。

今回はFROMのイメージがビルドされた後、RUNに書かれたコマンドによって、
composer(PHPのパッケージ管理システム）のインストール、
コンテナ内で使用するコマンドのインストール、
docker-php-ext-installを使用したPDOのインストールをします。

`RUN cd /usr/bin && curl~` で composerのインストールを行います。
`RUN ~~ git zip ~~`でコマンドのインストールを行います。
`RUN ~~ docker-php-ext-install pdo_pgsql` でPDOのインストールを行います。

`WORKDIR /var/www/html`でコンテナ内でコマンドを実行するときのディレクトリを指定しています。

```yml
version: '3'

services:
  web:
    image: nginx:1.15.6
    ports:
      - '8000:80'
    depends_on:
      - app
    volumes:
      - ./docker/web/default.conf:/etc/nginx/conf.d/default.conf
      - .:/var/www/html
  app:
    build: ./docker/php
    volumes:
      - .:/var/www/html
    depends_on:
      - postgres
  postgres:
    image: postgres:latest
    restart: always
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      PGPASSWORD: password123
      POSTGRES_DB: sample
      TZ: "Asia/Tokyo"
    ports:
      - 5432:5432
    volumes:
      - postgres:/var/lib/postgresql/data
   
  pgadmin:
    image: dpage/pgadmin4
    restart: always
    ports:
      - 81:80
    environment:
      PGADMIN_DEFAULT_EMAIL: sixeyeddogfromhell@gmail.com
      PGADMIN_DEFAULT_PASSWORD: password
    volumes:
      - pgadmin:/var/lib/pgadmin
    depends_on:
      - postgres
    
volumes:
  postgres:
  pgadmin:
```
`docker-compose.yml`内の`app`の`image: php:7.2-fpm`を`build: ./docker/php`へと変更します。
この変更によって、`docker-compose up`時に`/docker/php`内の`Dockerfile`を参照し、
ビルド時にコマンドを実行します。

```powershell
docker-compose up -d
```
Powershellよりコマンドを実行し、コンテナをビルドします。

```powershell
docker-compose exec app bash
composer create-project --prefer-dist laravel/laravel my-laravel-app
```
Dockerfileにコマンドを記述したことによって、すでに`app`コンテナ内にcomposerなどがインストールされた状態になっていると思います。

`docker-compose exec app bash`で`app`コンテナ内に入り、
`composer create-project --prefer-dist laravel/laravel my-laravel-app`を実行して、新たなLaravelプロジェクトを作ります。

プロジェクトの作成が終わると、開発環境用のフォルダの中に`my-laravel-app`フォルダが作成されたことが確認できると思います。

```env
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=sample
DB_USERNAME=user
DB_PASSWORD=password
```
作成されたファイルの中に、`my-laravel-app/.env`というLaravelの環境変数設定ファイルがあると思いますので、該当の個所を上記の様に変更します。

```powershell
docker-compose down
docker-compose up -d
docker-compose exec app bash
cd my-laravel-app/
php artisan migrate
```
Powershellにてコンテナの再起動を行った後、`app`コンテナの中でLaravelプロジェクトのソースがあるディレクトリで`php artisan migrate`を行います。

`php artisan migrate`コマンドはLaravelのデータベースのバージョン管理用のコマンドです。
ひとまずはLaravelインストール時に作成されるファイルでマイグレーションをします。
これ以降は、マイグレーションコマンドを使用して、開発に沿ったデータベースの変更のバージョン管理を行っていくことができると思います。

```powershell
cd my-laravel-app/
php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.09 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (0.01 seconds)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (0.01 seconds)
```
`app`コンテナ内で、`my-laravel-app`フォルダへ移動後、`php artisan migrate`を実行してマイグレーションをします。


```conf:default.conf
server {
    listen 80;
    root /var/www/html/my-laravel-app/public;
    index index.php index.html index.htm;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }
    location ~ \.php$ {
        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;
    }
}
```

nginxからLaravelアプリへリクエストを渡したいので、
`default.conf`のrootを`root /var/www/html/my-laravel-app/public;`へ変更します。
この変更によって、nginxにきたリクエストはすべて`/var/www/html/my-laravel-app/public/`の`index.php index.html index.htm`いずれかへ渡ります。（ファイルがあれば）

```powershell
docker-compose down
docker-compose up -d
```
Powershellからコンテナを再起動し、ブラウザから　localhost:8000　にアクセスします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286510/ed6502f5-70b6-f148-c270-f543c4371dca.png)
権限関係のエラーがでてしまいました。


```Powershell
docker-compose exec app bash
cd my-laravel-app/
chmod -R 777 storage
```

https://akizora.tech/laravel-log-error-4495

参考に権限付与をしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286510/f7b798a3-962e-1a2a-6a6f-9080ca3df86e.png)

環境が構築できたことを確認できました。


#終わりに#
一度環境を構築してから、復習しつつ記事を書きました。
環境構築時に無視したりとばしたりしたところを確認出来てよかったです。
わからないところも増えました。

内容におかしなところややめたほうがよいものが見受けられましたら、
ご連絡を頂けますと幸いです。