Laravelの開発環境をDockerを使って構築する

**2019/8/10 注意 構成を大幅に見直し、記事も合わせて加筆しています。**
**2019/9/4 [Laravel 6 でも動作するように修正](https://github.com/ucan-lab/docker-laravel/pull/22/commits/2f87190842c5c3b79197834cb37050365575a9bf)を加えました。**
**2019/9/28 要望が多かったので[Nodeコンテナを起動したままの状態に変更](https://github.com/ucan-lab/docker-laravel/pull/24/commits/50e7ac41ec0a9b9b41e4853668a2a80638de4495)しました。**
**2019/10/30 [忙しい人向けの記事を書きました](https://qiita.com/items/d7752b43a9e89cc16b22)**
**2019/12/31 [appコンテナをPHP7.4にアップデートしました。](https://github.com/ucan-lab/docker-laravel/pull/38)**

---

**2020/5/9 この記事の内容を全て書き直しました [最強のLaravel開発環境をDockerを使って構築する【新編集版】](https://qiita.com/ucan-lab/items/5fc1281cd8076c8ac9f4)**

---

# <font color="Red">当記事で使用しているGitHubリポジトリは既にアーカイブされています。使える部分はあるかと思いますが、ご利用は自己責任でお願いします。<font>

## 概要

docker(docker-compose)でLEMP環境(PHP/nginx/MySQL)を構築し、Laravelの新規プロジェクト作成まで行います。

Laravelを動作させる最小docker構成のハンズオン記事も書いてますので、一からdocker構成を作ってみたい人はこちらの記事をおすすめします。

- [【初心者向け】20分でLaravel開発環境を爆速構築するDockerハンズオン](https://qiita.com/ucan-lab/items/56c9dc3cf2e6762672f4)

## 用意する環境

- PHP7.3
- Laravel5.8
- MySQL8.0
- nginx1.17
- その他(Node,Redis,MailHog)

## リポジトリ

~~https://github.com/ucan-lab/docker-laravel-alpine~~

環境構築のコマンドのみ見たい方は [README.md](https://github.com/ucan-lab/docker-laravel-alpine/blob/master/README.md) を参照ください。

## 対象読者

- Laravelが動く環境をdockerで作りたい人

## Docker とは

Dockerとは、一台のマシン上に複数の隔離された環境を構築する軽量なコンテナ型の仮想化技術です。

特定のプロセスの実行環境を `Dockerイメージ` という単位で管理できます。

Dockerのメリットとしては、
仮想マシン(VirtualBox)と違ってハードウェア(CPU, メモリ)をシミュレーションする必要がないためとても軽快です。

- [【図解】Dockerの全体像を理解する -前編-](https://qiita.com/kotaro-dr/items/b1024c7d200a75b992fc)
- [「Docker」を全く知らない人のために「Docker」の魅力を伝えるための「Docker」入門](https://qiita.com/bremen/items/4604f530fe25786240db)

## 補足

コンテナに出たり入ったりすることが多いので下記のようにします。

```console
[mac] $ Macで実行するコマンド
[web] $ dockerの web コンテナ内で実行するコマンド
[app] $ dockerの app コンテナ内で実行するコマンド
[db] $ dockerの db コンテナ内で実行するコマンド
[node] $ dockerの node コンテナ内で実行するコマンド
[redis] $ dockerの redis コンテナ内で実行するコマンド
```

## 前提

- git
  - Macであれば標準でインストールされている
- Docker
  - https://docs.docker.com/docker-for-mac から最新版をダウンロードする
  - 参考記事: [DockerをMacにインストールする](https://qiita.com/kurkuru/items/127fa99ef5b2f0288b81)

```console
[mac] $ git --version
git version 2.22.0

[mac] $ docker --version
Docker version 19.03.1, build 74b1e89

[mac] $ docker-compose --version
docker-compose version 1.24.1, build 4667896b
```

## 最終的なディレクトリ構成

```
[mac] $ tree .
.
├── README.md
├── docker => 各コンテナの設定ファイル
│   ├── ash
│   │   ├── .ash_history
│   │   ├── aliases.sh
│   │   └── color_prompt.sh
│   ├── mysql
│   │   └── my.cnf
│   ├── nginx
│   │   └── default.conf
│   └── php
│       ├── Dockerfile
│       └── php.ini
├── logs => 各種ログファイル
├── src => Laravelプロジェクトをインストールするディレクトリ
├── .env
├── .gitignore
└── README.md
```

***

ここまでで事前準備終了です。

***

## Docker環境を構築する

今回は `handson` という作業ディレクトリを作ります。

```console
[mac] $ mkdir handson
[mac] $ cd handson
```

### GitHubリポジトリからDockerテンプレートをダウンロード

```console
[mac] $ git clone https://github.com/ucan-lab/docker-laravel-alpine
[mac] $ cd docker-laravel-alpine
[mac] $ cp .env-example .env
```

#### 環境設定ファイルの確認

`dockerの.env` の中身を確認します。

```console
[mac] $ less .env
```

データベースの接続情報を記述している箇所があります。
ここは `Laravelの.env` の初期設定に合わせてます。
**必要に応じて** 環境設定ファイルや各コンテナの設定ファイルを変更してください。

### Dockerのビルド、起動

```console
[mac] $ docker-compose build
[mac] $ docker-compose up -d
```

- `build` コマンドで サービスの構築 を行う
- `up` コマンドで コンテナを作成してサービスを開始 します
- `-d` オプションで デタッチド・モードで起動(デフォルトはアタッチド・モード) します。
  - これにより通常はコンテナを実行するとすぐに終了しますが、バックグラウンドで動作するようになります。
- `docker-compose up -d --build` ワンコマンドにまとめてもok

```console
[mac] $ docker-compose ps
           Name                          Command               State                 Ports
---------------------------------------------------------------------------------------------------------
docker-laravel_app_1          docker-php-entrypoint php-fpm    Up      0.0.0.0:18000->8000/tcp, 9000/tcp
docker-laravel_db-testing_1   docker-entrypoint.sh mysqld      Up      0.0.0.0:13307->3306/tcp, 33060/tcp
docker-laravel_db_1           docker-entrypoint.sh mysqld      Up      0.0.0.0:13306->3306/tcp, 33060/tcp
docker-laravel_mail_1         MailHog                          Up      1025/tcp, 0.0.0.0:18025->8025/tcp
docker-laravel_node_1         docker-entrypoint.sh node        Up
docker-laravel_redis_1        docker-entrypoint.sh redis ...   Up      6379/tcp
docker-laravel_web_1          nginx -g daemon off;             Up      0.0.0.0:10080->80/tcp
```

- `ps` コマンドで起動しているコンテナの一覧を表示します。

上記のようにコンテナが表示されていればokです。

### Laravelプロジェクトを新規に作成する場合

```console
[mac] $ docker-compose exec app composer create-project --prefer-dist "laravel/laravel=5.8.*" .
[mac] $ docker-compose exec app composer require predis/predis
```

- `composer create-project laravel/laravel` 新しいLaravelプロジェクトを作成するコマンド
  - `--prefer-dist` はzipでダウンロードする(高速)、デフォルトはgit cloneする
  - `.` カレントディレクトリにインストールする
- `composer require predis/predis` Redisを使ってるので、ライブラリを入れる

http://127.0.0.1:10080

上記のアドレスにアクセスしてLaravelのウェルカムページが表示されればokです。

<img width="912" alt="スクリーンショット 2019-02-09 11.46.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/52879/8c885b3d-d44f-516d-ef22-8df654818667.png">

ポート番号は他の環境と被らないようあえて `10080` 番号に設定してます。

LaravelのDocker環境構築は以上で完了です！！
お疲れ様です🎉🎉🎉

## 必要に応じて...

### Laravelのコマンドを実行したい場合

`php`, `composer`, `artisan` コマンドは `app` コンテナ内で実行します。

```console
[mac] $ docker-compose exec app ash
[app] $ php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table
```

- `ash` エイシェル
  - [Almquist Shell(アルムクィストシェル)](https://ja.wikipedia.org/wiki/Almquist_Shell)
  - Alpine Linuxでは軽量化のため、bashではなくashが採用されています。
- `migrate` すると `users` と `password_resets` テーブルが生成される。
- ※ **migrateエラーが発生する場合** `Laravelの.env` の `DB_HOST` へMySQLのコンテナ名を設定してください。(`DB_HOST=db`)

### MySQLに接続したい場合

MySQLはdbコンテナに入ってMySQLコマンドを実行します。
環境変数に接続情報が入っているのでこのままコピペでログインできます。

```console
[mac] $ docker-compose exec db bash -c 'mysql -uroot -p${MYSQL_PASSWORD} ${MYSQL_DATABASE}'
```

上記のコマンドでMySQLにログインし、MySQLコマンドを実行できます。

```console
[db] mysql> show variables like '%time_zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | JST    |
| time_zone        | SYSTEM |
+------------------+--------+
2 rows in set (0.01 sec)

[db] mysql> show variables like '%char%';
+--------------------------+--------------------------------+
| Variable_name            | Value                          |
+--------------------------+--------------------------------+
| character_set_client     | utf8mb4                        |
| character_set_connection | utf8mb4                        |
| character_set_database   | utf8mb4                        |
| character_set_filesystem | binary                         |
| character_set_results    | utf8mb4                        |
| character_set_server     | utf8mb4                        |
| character_set_system     | utf8                           |
| character_sets_dir       | /usr/share/mysql-8.0/charsets/ |
+--------------------------+--------------------------------+
8 rows in set (0.00 sec)
```

```console
[db] mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| homestead          |
| information_schema |
+--------------------+

[db] mysql> show tables;
+---------------------+
| Tables_in_homestead |
+---------------------+
| migrations          |
| password_resets     |
| users               |
+---------------------+

[db] mysql> select * from migrations;
+----+------------------------------------------------+-------+
| id | migration                                      | batch |
+----+------------------------------------------------+-------+
|  1 | 2014_10_12_000000_create_users_table           |     1 |
|  2 | 2014_10_12_100000_create_password_resets_table |     1 |
+----+------------------------------------------------+-------+
```

- タイムゾーン: `Asia/Tokyo`
- 文字コード: `utf8mb4`

上述の `php artisan migrate` を実行していれば `migrations` テーブルに2件データが入ってるはず。

### MySQL管理ツールから接続したい場合

オススメのMySQL管理ツールは[TablePlus](https://tableplus.io)です。理由はMySQL8.0に対応してるから😊

#### 設定例

<img width="500" alt="スクリーンショット 2019-08-10 4.47.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/94ac8345-6158-8c46-9546-005a1aaf8d77.png">

#### 変更箇所

```
Name: docker-laravel(任意)
Host: 127.0.0.1
Port: 13306
User: homestead
Password: secret
Database: homestead
```

#### TablePlusの画面

<img width="890" alt="スクリーンショット 2019-08-10 4.48.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/a29e5853-c8fa-eebc-d292-bef14e95c22c.png">

### Node(npm, yarn)を実行したい場合

```console
[mac] $ docker-compose exec node ash
[node] $ npm install # OR yarn install
[node] $ npm run dev # OR yarn run dev
```

### Redisを使いたい場合

```console
[mac] $ docker-compose exec redis ash
[redis] $ redis-cli
127.0.0.1:6379> SET hoge bar
OK
127.0.0.1:6379> GET hoge
"bar"
```

### RedisをLaravelで使いたい場合

```console
$ docker-compose exec app php artisan tinker
Redis::set('name', 'hoge');
Redis::get('name');
```

参考: https://readouble.com/laravel/5.8/ja/redis.html

### Laravelのビルトインサーバを使いたい場合

```console
[mac] $ docker-compose exec app php artisan serve --host 0.0.0.0
```

http://127.0.0.1:18000

- `serve` Laravelのビルトインサーバを起動します。コンソール上にログが表示されます。
- `--host` 外部からのアクセスを許可する指定をします。

### データベースを初期化する

```console
$ docker-compose down --volumes --rmi all
$ docker-compose up -d --build
$ docker-compose exec app php artisan migrate
```

## 既存のLaravelプロジェクトの実行環境を構築する

例えば、このLaravelプロジェクトを動かしてみたいなぁという時。

https://github.com/ucan-lab/laravel60-quickstart-basic

```console
[mac] $ mkdir handson
[mac] $ cd handson
```

```console
[mac] $ git clone git@github.com:ucan-lab/docker-laravel.git
[mac] $ git clone git@github.com:ucan-lab/laravel60-quickstart-basic.git
```

```console
[mac] $ cd docker-laravel
```

`.env` を書き換える

```ini
#PROJECT_PATH=./src
PROJECT_PATH=../laravel60-quickstart-basic
```

あとは環境構築して動作確認します😊

```console
[mac] $ docker-compose up -d --build
[mac] $ docker-compose exec app composer install
[mac] $ docker-compose exec app cp .env.example .env
[mac] $ docker-compose exec app php artisan migrate
```

http://127.0.0.1:10080

## 各種バージョン情報

```console
$ docker-compose exec web nginx -V
nginx version: nginx/1.17.3
built by gcc 8.3.0 (Alpine 8.3.0) 
built with OpenSSL 1.1.1c  28 May 2019
TLS SNI support enabled

[mac] $ docker-compose exec app php -v
PHP 7.3.8 (cli) (built: Aug  2 2019 06:11:32) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.8, Copyright (c) 1998-2018 Zend Technologies
    with Xdebug v2.7.2, Copyright (c) 2002-2019, by Derick Rethans

[mac] $ docker-compose exec app composer -V
Composer version 1.9.0 2019-08-02 20:55:32

[mac] $ docker-compose exec db mysql -V
mysql  Ver 8.0.17 for Linux on x86_64 (MySQL Community Server - GPL)

[mac] $ docker-compose exec redis redis-cli --version
redis-cli 5.0.5

[mac] $ docker-compose exec node node -v
v12.8.1

[mac] $ docker-compose exec node npm -v
6.10.2

[mac] $ docker-compose exec node yarn --version
1.17.3
```

※バージョン固定してないものもあり、構築するタイミングによってバージョン差異はあります。

## 今回Dockerを勉強して知ったこと

- [もっぷ先生](https://twitter.com/kobayashi_m42)
- [YYPHP#72](https://yyphp.connpass.com/event/114173)

上記の方々にレビューいただき、今回改めて知ったことを共有します。

## コンテナ名の衝突を避ける

コンテナを起動するとフォルダ名が初期値としてプロジェクト名となってしまい、フォルダ名を変えた場合に、プロジェクト名が勝手に変わってしまう問題があります。

コンテナ名だけでなく、イメージ名、ネットワーク名、ボリューム名の衝突にも気を付ける必要があります。

### COMPOSE_PROJECT_NAME

`COMPOSE_PROJECT_NAME` を使えば、 `container_name` も指定する必要なく、「COMPOSE_PROJECT_NAME 変数 + _ + サービス名 + _ + 連番」という名前になります😊

- [【Docker Compose】container_name ではなくて COMPOSE_PROJECT_NAME 変数を使うと良さそう](https://qiita.com/okashoi/items/1ddf3724ad5c166e417b)
- [dockerでプロジェクト名を指定する](https://qiita.com/reflet/items/b7b384d202a9f5514fb3)

### RUN set -eux

RUN を実行する際にはじめにシェルオプションを設定すると
buildを実行する際にコマンドが表示されるので、処理が途中で失敗した場合などに原因調査しやすいというメリットがあります。

- `-e` オプションによって実行したコマンドが1つでもエラーになれば直ちに終了するようにし、
- `-u` オプションで未定義の変数などを使おうとすればエラーにするようにしている。
- `-x` オプションでは実行コマンドとその引数をトレースとして出力するようにしている。

### 複数行でRUNの実行

Dockerは文を実行するたびにイメージのレイヤーを作成されてしまう。
RUN コマンドは繋げてワンライナーにする方が良い。
コマンドが長くなる場合は `\` で改行して見やすくする。

```docker
# NG
RUN apk update
RUN apk add git

# GOOD!
RUN apk update && \
  apk add git
```

### 一時的な変数は ENV ではなく ARG を使う

`ARG` は Dockerfile 内で使用可能な変数です。

```docker
ARG foo=bar
RUN echo $foo
```

### ADD より COPY

ファイルをコピーする意図であれば、 `ADD` より `COPY` を使った方がより直感的で分かりやすい

```
ADD ./my.cnf /etc/mysql/my.cnf # NG
COPY ./my.cnf /etc/mysql/my.cnf # GOOD!
```

### トップレベルボリュームを使用する

トップレベルボリュームを使用するとデータの永続化が可能になります。名前付きvolume とも呼ぶそうです。
また、ボリュームが隠蔽化されるため誤って削除する心配が少なくなり開発しやすくなります。

### テスト用に使うデータベースを用意する

Laravelの場合、テスト用のデータベースを作成することが多いので、
テスト用のDBも合わせて作成してあると開発環境としてはさらに良い。

`/docker-entrypoint-initdb.d` のディレクトリ配下に `.sql` や `.sh` ファイルを配置すれば初回起動時に実行してくれます。

- [docker-composeでmysql使うとき初回起動時に複数のDBを作る方法](https://qiita.com/takyam/items/de87252fca60a9c914c6)

### ベースイメージはコンテナの軽さを重視するならAlpine

Alpine Linux(アルパイン・リナックス)とは、
[musl](https://ja.wikipedia.org/wiki/Musl)と[BusyBox](https://ja.wikipedia.org/wiki/BusyBox)をベースとした[Linuxディストリビューション](https://ja.wikipedia.org/wiki/Linux%E3%83%87%E3%82%A3%E3%82%B9%E3%83%88%E3%83%AA%E3%83%93%E3%83%A5%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)です。
(用語を説明すると新たな用語の説明をしなければならなくなる説)

セキュリティ・シンプルさ・リソース効率を重視したOSです。

特定のOSのシェルを実行するようなアプリでなければ、Alpineはとても軽量です。

ただ、徹底的に軽量化されているため、AWSやGCPにデプロイした際に苦戦しそうです🤔

Alpine以外のDockerイメージとしては、UbuntuやDebianのイメージが良く使われる傾向にあるみたいです。個人歴にはCentOSとかRedHat系のOSが好きです😊

## 関連記事

- [Laravelの開発環境をLaradockを使って構築する](https://qiita.com/ucan-lab/items/90f74ce801618830e4fc)
- [Laravelの開発環境をHomesteadを使って構築する](https://qiita.com/ucan-lab/items/9b97fbf0b43c0849fd51)
- [Laravelの開発環境をVagrantを使って構築する](https://qiita.com/ucan-lab/items/e4a268e9f227ed6294b4)

## 参考にさせていただいた記事

- [Dockerでサクっとローカル開発環境(LAMP + Laravel)を構築する](https://qiita.com/ProjectEuropa/items/5fbb00848cd8d5b57182)
- [Dockerfileを書くときに気をつけていること10選](https://qiita.com/c18t/items/f3a911ef01f124071c95)
- [VagrantとDockerについて名前しか知らなかったので試した](https://qiita.com/hidekuro/items/fc12344d36d996198e96)
- [Dockerfileを書くときに気をつけていること10選](https://qiita.com/c18t/items/f3a911ef01f124071c95)
- [Dockerfile ARG入門](https://qiita.com/nacika_ins/items/cf8ceb20711bd077f770)
- [DBアクセスで遅くなったテストの実行時間を Docker で 40% 削減した方法](https://qiita.com/negokaz/items/f5f6b24ecc5d4786c6d5)

## さいごに

初めてdocker環境の構築に挑戦しましたが、個人的にはイイ感じに作れたかなと満足してます😊
もっとこうした方がイイよ！っていうアドバイスがあったらコメントいただけると嬉しいです！