Laravel開発環境をDocker

ものすごく有益だったんで以下よりコピペ!大感謝
https://qiita.com/ucan-lab/items/5fc1281cd8076c8ac9f4

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

お急ぎの方は 使い方 からお読みください。

概要

Docker, Docker Compose を使って、nginx, php-fpm, MySQLの実行環境(LEMP)を構築して、最強のLaravel開発環境を構築する記事です。

Laravelが最低限動作して、シンプルで軽量かつベストプラクティスなコンテナ、ディレクトリ構成を考えました。
最小限の構成なので、DockerイメージのビルドはLaradockより10倍以上高速にビルド完了します。

リポジトリ

スターをもらえると嬉しいです☺️

Docker × Laravel シリーズ記事

Laravel シリーズ記事

対象読者

  • Laravelを愛する心の持っている方
  • PHP, Linux, Git, Dockerの知識をある程度持っている方

Dockerについて学びたい方は以前に【初心者向け】20分でLaravel開発環境を爆速構築するDockerハンズオンというハンズオン記事を書いてますので、こちらをお読みいただけると理解が捗るかと思います。

前提

入れておいてね

[mac] $ git --version
git version 2.26.2
[mac] $ docker --version
Docker version 19.03.8, build afacb8b
[mac] $ docker-compose --version
docker-compose version 1.26.0, build d4451659

Docker Content Trust(DCT)を有効にする

~/.bashrc~/.zshrc に追記する。

~/.bashrc
export DOCKER_CONTENT_TRUST=1

DCTは、Dockerイメージを「なりすまし」と「改ざん」から保護するセキュリティ機能です。

  • Docker イメージへ発行者のデジタル署名を付ける
  • イメージの利用時(pull など)に「発行者」と「イメージが改ざんされていないこと」を検証する

push, build, create, pull, run のコマンド実行時に自動で機能します。

コンテナ構成

下記の3つのコンテナで構成します。
LEMP環境を作成します。

├── app
├── web
└── db

よく利用されるシンプルな構成だと思うのでこの構成にしました。
プロジェクトに合わせてご自由にカスタムしてください👍

app コンテナ

  • アプリケーションサーバーのコンテナ
  • PHPのバージョンは7.4系を利用する
    • 7.2系は2020年11月にサポートが切れるので注意
  • Laravel 7.x サーバ要件 を満たす
  • php, composer のベースイメージを利用

web コンテナ

  • アプリケーションサーバーのコンテナ
  • HTTPリクエストを受けて、HTTPレスポンスを返す
  • phpファイルへのアクセスはappコンテナに投げる
  • nodeイメージからyarnコマンドをインストールして、アセットファイルのビルドも担当する
  • nginx, node のベースイメージを利用

db コンテナ

  • データベースサーバーのコンテナ
  • MySQLのバージョンは8.0系を利用する
    • 5.7系は2020年10月にサポートが切れるので注意
    • 本番環境でAmazon Auroraを使用する場合は現時点で5.7互換までしかサポートしていないので注意
  • mysql のベースイメージを利用

ディレクトリ構成

.
├── backend # Laravelプロジェクトのルートディレクトリ
├── infra
│     └── docker
│          ├── mysql
│          │   ├── Dockerfile
│          │   └── my.cnf
│          ├── nginx
│          │   ├── Dockerfile
│          │   └── default.conf
│          └── php
│              ├── Dockerfile
│              ├── php-fpm.d
│              │   └── zzz-www.conf => unixドメインソケットの設定ファイル
│              └── php.ini
├── Makefile
└── docker-compose.yml

カスタマイズしてご利用したい方へ

docker-laravelリポジトリは、テンプレートリポジトリとして設定しています。

テンプレートリポジトリから作成した場合は、コミット履歴を継承されず綺麗な状態で開発を始められます!

ucan-lab_docker-laravel__Build_laravel_development_environment_with_docker-compose_.png

カスタマイズして使う場合は、後述のgit clone等する際はご自身のリポジトリをご指定してご利用ください。

使い方

composer create-project で新規作成したい場合と composer install で環境構築したい場合がありますので、それぞれ説明します。
Makefile を見てもらえると何やってるか分かりやすいです。

注意

webコンテナは80番ポート、dbコンテナは3306ポートを公開しています。

既にポートが使用されている場合は、docker-compose.ymlportsの指定を変更してご利用ください。

A. Laravelプロジェクトの新規作成

backend ディレクトリが空の状態で実行します。

[mac] $ git clone git@github.com:ucan-lab/docker-laravel.git
[mac] $ cd docker-laravel
[mac] $ make create-project

以上の3ステップでLaravelの新規プロジェクトの環境構築は完了です。

A’. Laravelプロジェクトをバージョン指定して新規作成

MakefileのLaravelインストール部分を書き換えます。

Makefile
    docker-compose exec app composer create-project --prefer-dist "laravel/laravel=6.*" .

Makefile を書き換えたら make create-project を実行してください。

B. 既存のLaravelプロジェクトの環境を構築する

docker-laravel/backend ディレクトリにLaravelプロジェクトを配置します。

[mac] $ git clone git@github.com:ucan-lab/docker-laravel.git

# Laravelプロジェクトを docker-laravel/backend へクローンする
[mac] $ git clone git@github.com:laravel/laravel.git docker-laravel/backend

[mac] $ cd docker-laravel
[mac] $ make init

これで既存のLaravelプロジェクトの環境構築は完了です。
記事の解説はここからお読みください

Tips

軽く使い方をご紹介します。

Makeコマンド、Makefileについて

docker-compose コマンドは入力するには長く、プロジェクト毎にエイリアス設定するのはとても面倒です。
Makefile を用意することで、短いコマンドでコマンドが実行できるようになります。

用意している make コマンドと元のdocker-composeコマンドを併記してご紹介します。

基本

# コンテナを作成する
[mac] $ make up
[mac] $ docker-compose up -d

# コンテナを破棄する
[mac] $ make down
[mac] $ docker-compose down

# コンテナを再作成する
[mac] $ make restart
[mac] $ docker-compose down && docker-compose up -d

# コンテナ、イメージ、ボリュームを破棄する
[mac] $ make destroy
[mac] $ docker-compose down --rmi all --volumes

# コンテナ、ボリュームを破棄する
[mac] $ make destroy-volumes
[mac] $ docker-compose down --volumes

# コンテナ、イメージ、ボリュームを破棄して再構築
[mac] $ make remake
[mac] $ docker-compose down --rmi all --volumes && \
    docker-compose up -d --build && \
    docker-compose exec app composer install && \
    docker-compose exec app cp .env.example .env && \
    docker-compose exec app php artisan key:generate && \
    docker-compose exec app php artisan storage:link && \
    docker-compose exec app php artisan migrate:fresh --seed
  • コンテナを破棄するとコンテナ内のデータは破棄されます。
  • イメージを破棄するとDockerイメージのビルドが必要です。
  • ボリュームを破棄すると名前付きボリュームで管理しているデータベースの中身が消えます。

推奨開発パッケージのインストール

個人的に推奨している開発パッケージをまとめてインストールしてくれるコマンドを用意しました。
必要に応じてご利用ください。

また、Laravelのバージョンによっては対応していないパッケージもある可能性があるのでエラーが出た場合はインストール要件等ご確認ください。

[mac] $ make install-recommend-packages

appコンテナに入る

[mac] $ make app
[mac] $ docker-compose exec app bash

webコンテナに入る

[mac] $ make web
[mac] $ docker-compose exec app ash

webコンテナ内のyarnを実行する

web コンテナにyarnコマンドをインストールしています。

[mac] $ make yarn
[mac] $ docker-compose exec web yarn

[mac] $ make yarn-dev
[mac] $ docker-compose exec web yarn dev

[mac] $ make yarn-watch
[mac] $ docker-compose exec web yarn watch

ただし、コンテナ内のyarnを実行するとめちゃくちゃ遅いので、あまり現実的ではありません。
開発時は素直にローカルのyarnを使用した方が良いです。

MacにNode,npm,yarnをインストールする記事を書きました。

dbコンテナに入る

[mac] $ make db
[mac] $ docker-compose exec db bash

dbコンテナのMySQLに接続する

[mac] $ make sql
[mac] $ docker-compose exec db bash -c 'mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE'

Laravelのマイグレーションを実行する

# migrate
[mac] $ make migrate
[mac] $ docker-compose exec app php artisan migrate

# migrate:fresh
[mac] $ make fresh
[mac] $ docker-compose exec app php artisan migrate:fresh --seed

# db:seed
[mac] $ make seed
[mac] $ docker-compose exec app php artisan db:seed

テストの実行

[mac] $ make test
[mac] $ docker-compose exec app php artisan test

解説

以降の内容は記事の解説になります。
過不足等ありましたら、コメントにて質問・補足等いただけるとありがたいです🙏

また、最新のリポジトリと若干の差異がある場合がありますのでご了承ください🙏
(なるべくは同期取りたいと思っています。)

docker-compose.yml

docker-compose.yml
version: "3.8"
volumes:
  php-fpm-socket:
  db-store:
services:
  app:
    build: ./infra/docker/php
    volumes:
      - php-fpm-socket:/var/run/php-fpm
      - ./backend:/work/backend

  web:
    build: ./infra/docker/nginx
    ports:
      - 80:80
    volumes:
      - php-fpm-socket:/var/run/php-fpm
      - ./backend:/work/backend

  db:
    build: ./infra/docker/mysql
    ports:
      - 3306:3306
    volumes:
      - db-store:/var/lib/mysql

app, web, db の3つのコンテナを定義しています。
ミニマムな構成を考えれば appdb コンテナだけあれば十分ですが、本番に近い構成と利便性を考慮してこの構成にしています。

version

version: "3.8"

Docker Compose のバージョンを指定します。

volumes

volumes:
  php-fpm-socket:
  db-store:

名前付きボリュームをマウントしています。
unixソケット(php-fpm-socket)のボリュームは app コンテナと web コンテナで共用したいのでマウントしてます。
データベース(db-store)のデータはコンテナを破棄しても残しておきたいのでボリュームとして定義しています。

services.app

  app:
    build: ./infra/docker/php
    volumes:
      - php-fpm-socket:/var/run/php-fpm
      - ./backend:/work/backend

app(アプリケーションサーバ)コンテナの定義です。PHPを実行します。
build./infra/docker/php/Dockerfile を指しています。Dockerfileの詳細は後述します。
volumes で名前付きボリュームとホストパスをコンテナにマウントしてます。
../backend:/work/backend Laravelのソースコードをマウントしてます。
他はunixソケットで使うものをマウントしてます。

services.web

  web:
    build: ./infra/docker/nginx
    ports:
      - 80:80
    volumes:
      - php-fpm-socket:/var/run/php-fpm
      - ./backend:/work/backend

web(ウェブサーバ)コンテナの定義です。PHP以外の静的コンテンツを返却します。
ports ポートを公開します。(HOST:CONTAINER)
nginxのデフォルトのポート番号は 80 番です。

services.db

  db:
    build: ./infra/docker/mysql
    ports:
      - 3306:3306
    volumes:
      - db-store:/var/lib/mysql

db(データベース)コンテナの定義です。
MySQLのデフォルトのポート番号は 3306 番です。

docker/php/Dockerfile

FROM php:7.4-fpm-buster
SHELL ["/bin/bash", "-oeux", "pipefail", "-c"]

# timezone environment
ENV TZ=UTC \
  # locale
  LANG=en_US.UTF-8 \
  LANGUAGE=en_US:en \
  LC_ALL=en_US.UTF-8 \
  # composer environment
  COMPOSER_ALLOW_SUPERUSER=1 \
  COMPOSER_HOME=/composer \
  # Laravel environment
  DB_CONNECTION=mysql \
  DB_HOST=db \
  DB_DATABASE=laravel_local \
  DB_USERNAME=phper \
  DB_PASSWORD=secret

COPY --from=composer:2.0 /usr/bin/composer /usr/bin/composer

RUN apt-get update && \
  apt-get -y install git libicu-dev libonig-dev libzip-dev unzip locales && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/* && \
  locale-gen en_US.UTF-8 && \
  localedef -f UTF-8 -i en_US en_US.UTF-8 && \
  mkdir /var/run/php-fpm && \
  mkdir /var/log/php && \
  docker-php-ext-install intl pdo_mysql zip bcmath && \
  composer config -g process-timeout 3600 && \
  composer config -g repos.packagist composer https://packagist.jp

COPY ./php-fpm.d/zzz-www.conf /usr/local/etc/php-fpm.d/zzz-www.conf
COPY ./php.ini /usr/local/etc/php/php.ini

WORKDIR /work/backend

FROM 命令

ベースイメージを指定します。(IMAGE:TAG)
PHPはDocker Hubに公式イメージが用意されています。
よほど特殊な環境を構築したい場合を除いては公式イメージをベースイメージとするのが良いでしょう。

FROM php:7.4-fpm-buster

PHPイメージの指定は <image>:<verion>-<variant>-<os> こんな風にタグ付けされていきます。

PHP の version

PHPのタグバージョンは 7.4 のマイナーバージョンまで指定するのがベターです。
パッチバージョンは後方互換性が保証されるので、なるべく最新を当てておきたいです。

PHPのバージョンのリリース頻度ですが、マイナーバージョンのアップデートは年に1回(11〜12月頃)行われています。
パッチバージョンのリリースは月1程度です。このPHPのサポート期間としては初回リリースから3年間です。
執筆時点でサポートされているPHPのバージョンは 7.2 以降となっています。
特に理由がなければ 7.4 を採用してください。

PHP の variant

PHPのイメージタグの種類として cli, apache, fpm, zts の4種類が用意されています。

  • cli(Apacheを経由せず、直接コマンドライン上で実行)
    • ウェブサーバ用途ではない
    • 使い捨てコンテナ
    • 他のイメージを構築するためのベースイメージ
  • apache
    • ウェブサーバ(Apache mpm_prefork) + アプリケーションサーバ(Apache mod_php)の構成
    • PHPと組み合わせたDebianのApache httpdが含まれている
  • fpm(FastCGI Process Manager)
  • zts(phpがZend Thread Safetyでビルドされている)
    • マルチスレッド環境に対応するためのPHPの機構です
    • PHPはシングルスレッド環境(NTS)で動かす方が一般的です。

今回はnginxを使ってるのでfpmを選択します。

PHP の コンテナOS

コンテナOSとしては buster, stretch, alpine の3種類が用意されています。

  • buster (Debian 10) (405MB)
  • stretch (Debian 9) (493MB)
  • alpine (Alpine Linux) (83.5MB)

alpineイメージサイズの小ささには驚くのですが、
タイムゾーンの設定するのにも手間がかかったりメンテナンスも面倒で初心者にオススメはできないという考えに至り buster を選択しています。

他のコンテナOSを使いたい場合は自作しましょう…。

SHELL 命令

SHELL ["/bin/bash", "-oeux", "pipefail", "-c"]

コマンドのシェル形式に使用されるデフォルトのシェルをオーバーライドできます。
Linuxのデフォルトのシェルは ["/bin/sh", "-c"]

おまじないです。。

RUN 命令でコマンドをワンライナーにしているのですが、
それだと失敗した際にどこで落ちたのか分かりづらいためオプションを付けています。

ARG 命令

https://docs.docker.com/engine/reference/builder/#arg
Dockerのビルド時に引数として変数を定義できます。

ENV 命令

ENV 命令は環境変数を設定します。
ARG 命令と違ってこちらはコンテナ起動時にもこの値は使用できます。

# timezone environment
ENV TZ=UTC \
  # locale
  LANG=en_US.UTF-8 \
  LANGUAGE=en_US:en \
  LC_ALL=en_US.UTF-8 \
  # composer environment
  COMPOSER_ALLOW_SUPERUSER=1 \
  COMPOSER_HOME=/composer \
  # Laravel environment
  DB_CONNECTION=mysql \
  DB_HOST=db \
  DB_DATABASE=laravel_local \
  DB_USERNAME=phper \
  DB_PASSWORD=secret

\ をつなげてワンライナーにしています。
こうすることでイメージレイヤー数を減らすことができます。

Laravelの環境変数について

Laravelの .env の内容を書き換えるという手順を省くためサーバー環境変数を設定しています。
ちなみにLaravelの .env よりもサーバー環境変数の値の方が優先されます。

DB_HOST=db について

DB_HOST はデータベースのホストを指します。
未指定の場合は 127.0.0.1 と設定されているので app コンテナを見に行ってしまいます。
そのため、 db とサービス名を指定しています。

※サービス名(db) と コンテナ名(docker-laravel_db_1) は意味が異なるのでご注意ください。

COPY 命令(マルチステージビルド)

新しいファイルまたはディレクトリをコンテナのパスにコピーします。

COPY --from=composer:2.0 /usr/bin/composer /usr/bin/composer

--from オプションを付けると別のイメージのファイルを指定できます。(マルチステージビルド)
composer のインストールがとてもシンプルになっています。

もうすぐComposer Version 2がリリースされます。
いきなり動かなくなってしまうと困るためComposerのバージョンを1系で固定化しました。

追記: 2020年10月24日にComposerバージョン2がリリースされました🎉🎉🎉 に伴って環境をアップグレードしました。

RUN 命令

RUN 命令は現在のイメージの上に任意のコマンドを実行した結果をコミットします。
ワンライナーで実行することによって、イメージレイヤ数を減らすことができます。

RUN apt-get update && \
  apt-get -y install git libicu-dev libonig-dev libzip-dev unzip locales && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/* && \
  locale-gen en_US.UTF-8 && \
  localedef -f UTF-8 -i en_US en_US.UTF-8 && \
  mkdir /var/run/php-fpm && \
  mkdir /var/log/php && \
  docker-php-ext-install intl pdo_mysql zip bcmath && \
  composer config -g process-timeout 3600 && \
  composer config -g repos.packagist composer https://packagist.org
  • Laravelの実行に必要なライブラリのインストール
  • OSの言語設定
  • PHP拡張ライブラリのインストール

を行ってます。

COPY 命令

新しいファイルまたはディレクトリをコンテナのパスにコピーします。

COPY ./php-fpm.d/zzz-www.conf /usr/local/etc/php-fpm.d/zzz-www.conf
COPY ./php.ini /usr/local/etc/php/php.ini

ホスト側に置いているPHPの設定ファイルのコピーをしているだけです。
PHPの設定ファイルも少し解説します。

./docker/php/php-fpm.d/zzz-www.conf
[www]
listen = /var/run/php-fpm/php-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0666

nginxとphp-fpm間の通信ですが、TCPUNIXドメインソケットで通信します。
UNIXドメインソケットの方が遥かにスループットが優れているためこちらを採用しました。

./docker/php/php.ini
zend.exception_ignore_args = off
expose_php = on
max_execution_time = 30
max_input_vars = 1000
upload_max_filesize = 64M
post_max_size = 128M
memory_limit = 256M
error_reporting = E_ALL
display_errors = on
display_startup_errors = on
log_errors = on
error_log = /dev/stderr
default_charset = UTF-8

[Date]
date.timezone = ${TZ}

[mysqlnd]
mysqlnd.collect_memory_statistics = on

[Assertion]
zend.assertions = 1

[mbstring]
mbstring.language = Japanese

php.iniの各設定に関しては別記事にまとめていますので、そちらをご参照ください。

docker/nginx/Dockerfile

./docker/nginx/Dockerfile
FROM node:14.2-alpine as node
FROM nginx:1.18-alpine
SHELL ["/bin/ash", "-oeux", "pipefail", "-c"]

ENV TZ=UTC

RUN apk update && \
  apk add --update --no-cache --virtual=.build-dependencies g++

# node command
COPY --from=node /usr/local/bin /usr/local/bin
# npm command
COPY --from=node /usr/local/lib /usr/local/lib
# yarn command
COPY --from=node /opt /opt
# nginx config file
COPY ./default.conf /etc/nginx/conf.d/default.conf

WORKDIR /work/backend

ベースイメージとしてDocker Hub公式イメージを使用します。

FROM 命令 node

FROM 命令 nginx

COPY 命令

node のベースイメージから nodenpmyarn をコピーしています。

# node command
COPY --from=node /usr/local/bin /usr/local/bin
# npm command
COPY --from=node /usr/local/lib /usr/local/lib
# yarn command
COPY --from=node /opt /opt

/opt/yarn-v1.22.0 と yarn パッケージがインストールされています。
もし npm, yarn どちらか使用したくない場合は削除して使用してokです。

それからnginxの設定ファイルをコピーしています。

/docker/nginx/default.conf
access_log /dev/stdout main;
error_log /dev/stderr warn;

server {
    listen 80;
    root /work/backend/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.html index.htm index.php;

    charset utf-8;

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

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

nginxの設定ファイルはLaravelの公式ドキュメントを参考にしています。
https://readouble.com/laravel/7.x/ja/deployment.html

root でドキュメントルートを設定しているのと、
fastcgi_pass でunixドメインソケットを指定しています。

docker/mysql/Dockerfile

FROM mysql:8.0

ENV TZ=UTC \
  MYSQL_DATABASE=laravel_local \
  MYSQL_USER=phper \
  MYSQL_PASSWORD=secret \
  MYSQL_ROOT_PASSWORD=secret

COPY ./my.cnf /etc/my.cnf

ベースイメージとしてDocker Hub公式イメージを使用します。

環境変数と設定ファイルのコピーだけ行っています。

MySQL8.0

PHP 7.1.16, 7.2.4 以降のバージョンから新しい認証方式 caching_sha2_password にも対応するようなりましたのでMySQL8.0系の導入敷居は下がったかなと思います。

my.cnf

my.cnf の説明です。

/docker/mysql/my.cnf
[mysqld]
character_set_server = utf8mb4
collation_server = utf8mb4_0900_ai_ci

# timezone
default-time-zone = SYSTEM
log_timestamps = SYSTEM

# Error Log
log-error = mysql-error.log

# Slow Query Log
slow_query_log = 1
slow_query_log_file = mysql-slow.log
long_query_time = 1.0
log_queries_not_using_indexes = 0

# General Log
general_log = 1
general_log_file = mysql-general.log

[mysql]
default-character-set = utf8mb4

[client]
default-character-set = utf8mb4

MySQL8.0からデフォルトの文字コードは utf8mb4 文字列照合順序は utf8mb4_0900_ai_ci となっています。
日本語を扱う場合はMySQL8.0.1で新しく導入された日本語用の照合順序 utf8mb4_ja_0900_as_cs_ks を設定することを推奨します。

  • utf8mb4_ja_0900_as_cs_ks
    • utf8mb4 utf8で、マルチバイトを4バイトとする文字コードになります。
    • ja 日本語用
    • 0900 Unicodeのバージョン 9.00
  • as Accent Sensitiveの略称。アクセント違いは異なる文字
    • 「は」と「ぱ」は異なる文字として評価される。
  • cs Case Sensitiveの略称。大文字小文字は異なる文字
    • 「あ」と「ぁ」は異なる文字として評価される。
  • ks Kana Sensitiveの略称。カタカナひらがなは異なる文字
    • 「あ」と「ア」は異なる文字として評価される。