1379
Help us understand the problem. What are the problem?

posted at

updated at

【超入門】20分でLaravel開発環境を爆速構築するDockerハンズオン

前置きが長いので、Docker, Git, GitHubの設定が終わってる人はここから始められます。

【実際にLaravel開発を始める方へ】

本記事はハンズオンということもあり、初歩的な内容かつ最小限の構成になっております。
実際に開発を始める場合はより実用的なDocker構成の記事を書いてますので、こちらの記事をご参考ください。

概要

DockerとDocker Composeを使ってLEMP環境(PHP/nginx/MySQL)を構築し、Laravelの新規プロジェクト作成、構築した環境を破棄してから環境の再構築までをハンズオン形式で行います。

動作環境

Windows(WSL2), Mac(M1 Mac含む), Linux環境で動作するように書いてます。
筆者の実行環境はM1じゃないMacです。

更新履歴

  • 2022/02/23 Laravel9がリリースされたので、合わせて記事をリニューアルしました。
    • PHP8.1, Composer2.2, Laravel9
  • 2020/09/09 Laravel8(当時はLaravel6が出た頃)がリリースされ、こちらの記事も内容が古くなったので全て書き直しました。
  • 2021/04/25 記事内容をアップデートしました
  • 2021/04/29 docker-compose を docker compose へ変更
  • 2021/06/06 Chrome 91より10080ポートへのアクセスがブロックされるため8080へ変更

目的

Dockerの細かい概念などはすっ飛ばして、
このハンズオンの通りに進めればDocker×Laravelの環境を構築できます。
習うより慣れろで行ってみましょう!

ハンズオン終了時のGitHub

ハンズオンイベント

【ぺちオブ】ゆーきゃんプレゼンツ!Laravel + Docker 環境構築ハンズオン

あの有名なぺちオブのイベントとしてハンズオン講師をやらせていただきました!
ありがとうございます🙏

DockerでLaravelのローカル環境を構築するハンズオン(オンライン)

2021/05/01 第二回のハンズオンイベントを開催します。

【初心者向け】DockerでLaravel9のローカル環境を構築するハンズオン(オンライン)

2022/02/23 第三回のハンズオンイベントを開催します。

レビュワー

スペシャルサンクス🙇‍♂️🙏

前提条件

  • Mac基準ですが、Windowsでも動作することを確認済み
  • コマンドは [環境] $ とprefixを付けてます。
  • 所要時間は事前準備部分を除いて20分を目安としてます。
  • エディタはファイル編集時にお好みのもので開いて作業ください🙏

構築環境、使用技術

それぞれ、どういった技術なのか抑えておくと理解がしやすいです。

dockerおすすめ書籍

冒頭で習うより慣れろと言いましたが、習っておくのもそれはそれで良いことなのでオススメの書籍や記事をご紹介します。私はこの辺りの記事や本を読んで勉強したのでオススメしてます。

マンガでわかるdockerシリーズ

@llminatoll さんがDockerについてマンガで分かりやすく説明されてます。
初めての方はこちらのマンガでDockerの概念を学んでから進めると理解しやすいです。

dockerおすすめ記事・サイト

公式系

記事

スライド

事前準備

Dockerのインストール確認

Dockerが使える状態になっていればokです。

[mac] $ docker --version
Docker version 20.10.10, build b485636

【Macユーザー向け】

【Windowsユーザー向け】

Git Bash を起動した際は下記のコマンドを実行ください。

$ exec winpty bash

【Windowsユーザー(WSL2)向け】

GitHubアカウントの作成

Gitの初期設定

user.emailuser.name が設定されていればok!
Gitの初期設定を行ってない場合、下記の記事を参考に初期設定をお願いします。

Mac Git 初期設定

最終的に下記のように表示されればokです。

$ git config --list | grep user
user.email=xxx@yyy.com
user.name=zzz

GitHub SSH接続設定

$ ssh -T github.com
Warning: Permanently added 'github.com,52.69.186.44' (RSA) to the list of known hosts.
Hi ucan-lab! You've successfully authenticated, but GitHub does not provide shell access.

successfully authenticated の文字が出ればok!
Warningは気にしなくてok!

GitHub SSH接続設定を行ってない場合、下記の記事を参考に初期設定をお願いします。
もしくはハンズオンをHTTPSに置き換えて進めてください。

Mac GitHub SSH接続設定

最終的にgit cloneやgit pushができれば良いのでHTTPSでもokです。

Docker起動確認

インストールしたDockerを起動してください。
running 状態になっていればokです。

スクリーンショット 2019-09-29 1.39.55.png

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


今回のハンズオンのゴール

  • 3層アーキテクチャのコンテナの構築
    • ウェブサーバー(web)
      • nginxで静的コンテンツ配信サーバを構築
    • アプリケーションサーバー(app)
      • nginxを経由してPHPを動作させるアプリケーションサーバを構築
      • PHPパッケージ管理ツールComposerのインストール
    • データベースサーバー(db)
      • MySQLデータベースサーバーの構築
  • Laravelをインストールしてwelcome画面の表示
  • LaravelとMySQLを連携し、マイグレーションを実行
  • GitHubにソースコードをアップロード
  • Docker環境の破棄
  • Docker環境をGitHubから再構築

🐳【初心者向け】20分でLaravel開発環境を爆速構築するDockerハンズオン🐳

  • 所要時間: 20分目安(解説読みながらだと1時間半程度)

作業ディレクトリを作成

[mac] $ mkdir docker-laravel-handson
[mac] $ cd docker-laravel-handson

【補足】最終的なディレクトリ構成

.
├── README.md (この名前にするとGitHubで見た時にHTMLに変換して表示してくれる)
├── infra (*1)
│   ├── mysql (*1)
│   │   ├── Dockerfile
│   │   └── my.cnf (*1)
│   ├── nginx (*1)
│   │   └── default.conf (*1)
│   └── php (*1)
│       ├── Dockerfile (この名前にするとファイル名の指定を省略できる)
│       └── php.ini (*1)
├── docker-compose.yml (この名前にするとファイル名の指定を省略できる)
└── src (*1)
    └── Laravelをインストールするディレクトリ

このディレクトリ構成を目指します。
(*1) 任意の名前に変更してもokです。

リモートリポジトリを作成

GitHub Create remote repository
GitHub docker-laravel-handson

リポジトリ名 docker-laravel-handson のGitHubリモートリポジトリを作成する。

初回コミット

[mac] $ echo "# docker-laravel-handson" >> README.md
[mac] $ git init
[mac] $ git add README.md
[mac] $ git commit -m "first commit"
[mac] $ git branch -M main

【補足】master → main デフォルトブランチの変更

※ 2020/10/1 からデフォルトブランチがmasterからmainに変更されました。

【補足】git init 時のメッセージ

$ git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint: 
hint: 	git config --global init.defaultBranch <name>
hint: 
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint: 
hint: 	git branch -m <name>

下記の設定をしておくと次回以降は main ブランチがデフォルトブランチとなります。

$ git config --global init.defaultBranch main

リモートリポジトリの登録

リモートリポジトリを登録します。
リモートリポジトリ先は適宜置き換えてください。

[mac] $ git remote add origin git@github.com:ucan-lab/docker-laravel-handson.git

リモートリポジトリ先が正しく設定されていることを確認してください。

[mac] $ git remote -v
origin  git@github.com:ucan-lab/docker-laravel-handson.git (fetch)
origin  git@github.com:ucan-lab/docker-laravel-handson.git (push)

# もしリモートリポジトリ先を間違えた場合は下記のコマンドから変更できます。
[mac] $ git remote set-url origin <リモートリポジトリ>

リモートリポジトリ先の名前を origin と付けられていること。
リモートリポジトリ先のURL等に間違いがないことを確認する。

リモートリポジトリへプッシュ

GitHubへpushします。

[mac] $ git push -u origin main
  • -u オプションは --set-upstream の省略
  • ローカルリポジトリの現在のブランチ(main)をリモートリポジトリ(origin)のmainにpush先を設定
  • 以降は git push だけで git push origin main と同義

GitHubのリモートリポジトリのmainブランチへpushされていることを確認します。

【補足】お好みのGUIエディタで開く

ymlファイルをCUIエディタで書いていくのは大変なのでお好みのGUIエディタ(今回はVSCode)で開きます。
code コマンドがインストールされていれば、簡単にプロジェクトをVSCodeで開けます。

[mac] $ code .

ScreenShot 2020-10-15 2.34.39.png

また、 control + shift + @ で統合ターミナルをVSCodeで開くことができます。
ファイルの編集もコマンドの実行もVSCodeだけで行えます。

ScreenShot 2020-10-15 2.36.26.png

アプリケーションサーバ(app)コンテナを作る

PHPアプリケーションサーバコンテナを作成します。
公式のPHP-FPMイメージをベースイメージとしてカスタマイズします。

【補足】ディレクトリ構成

.
├── infra
│   └── php
│       ├── Dockerfile
│       └── php.ini # PHPの設定ファイル
├── src # Laravelをインストールするディレクトリ
└── docker-compose.yml

【補足】【重要】ファイル編集について

touch コマンドで空ファイルを作成してます。
作成後はお好みのエディタで編集ください。

タイプミス防止のため空ファイルを先に作成してます。

コマンドを実行する場所を間違えないように cd コマンドは使用しないように注意してください。
当ハンズオンはこの流れで進めていきます。

あれこれ試したいと思いますが、まずはハンズオンが上手くいってから色々試してみてください。

docker-compose.yml を作成する

[mac] $ touch docker-compose.yml

作成した docker-compose.yml を下記の通りに編集します。

docker-compose.yml
version: "3.9"
services:
  app:
    build: ./infra/php
    volumes:
      - ./src:/data
  • docker-compose.yml ファイルはインデント(半角スペース)が意味を持ちます。注意してコピペしてください。

docker-compose.yml: 設定値の補足

docker-compose.yml
version: "3.9"

Docker Composeファイルのバージョンを指定しています。

通常はメジャー番号とマイナー番号を両方指定します。
ちなみにマイナーバージョンを指定しない場合はデフォルト0が使用されます。

docker-compose.yml
# 下記は同じ指定になる
version: "3"
version: "3.0"
docker-compose.yml
services:
  app: # => サービス名は任意
    build: ./infra/php
    volumes:
      - ./src:/data

サービス名に app (アプリケーションサーバー)の名前を付けて定義しています。
サービス名は任意に決められます。

build: で指定しているのはビルドコンテキストを指定します。
ビルドコンテキストとは、docker buildを実行する際の現在の作業ディレクトリのことをビルドコンテキスト(build context)と呼びます。

Dockerfile が置かれている ./infra/php ディレクトリをビルドコンテキストとして指定します。
Dockerビルドの際は Dockerfile のファイルを探すので、ファイル名の指定は不要です。

volumes: ではホスト側のディレクトリや名前付きボリュームをコンテナ側へマウントしたい時に指定します。
今回はホスト側の ./src ディレクトリをappサービスのコンテナ内 /data へマウントしてます。

./infra/php/Dockerfile を作成する

  • Composerコマンドのインストール
  • Laravelで必要なPHP拡張機能のインストール
    • bcmath, pdo_mysql が不足しているので追加インストール
[mac] $ mkdir -p infra/php
[mac] $ touch infra/php/Dockerfile
  • mkdir -p 必要に応じて親ディレクトリも作成してくれるオプションです。

下記のコードを丸ごとコピーして Dockerfile へ貼り付けてください。

infra/php/Dockerfile
FROM php:8.1-fpm-buster

ENV COMPOSER_ALLOW_SUPERUSER=1 \
  COMPOSER_HOME=/composer

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

RUN apt-get update && \
  apt-get -y install --no-install-recommends git unzip libzip-dev libicu-dev libonig-dev && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/* && \
  docker-php-ext-install intl pdo_mysql zip bcmath

COPY ./php.ini /usr/local/etc/php/php.ini

WORKDIR /data

【補足】./infra/php/Dockerfile

Dockerfileはテキストファイルであり、Dockerイメージを作り上げるために実行する命令をこのファイルに含めることができます。

各種Docker命令を解説します。

FROM php:8.1-fpm-buster

FROM命令はイメージビルドのためのベースイメージを設定します。
FROM イメージ名:タグ名 で指定します。

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

ENV COMPOSER_ALLOW_SUPERUSER=1 \
  COMPOSER_HOME=/composer

ENV命令はコンテナ内のサーバー環境変数を設定します。

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

RUN apt-get update && \
  apt-get -y install --no-install-recommends git unzip libzip-dev libicu-dev libonig-dev && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/* && \
  docker-php-ext-install intl pdo_mysql zip bcmath

Debian系のパッケージ管理ツールは apt-get, apt とありますが、Dockerfile内で apt を実行するとCLIインターフェース向けではないと警告が表示されるため、apt-getを使用します。

apt-get update インストール可能なパッケージの「一覧」を更新します。
実際のパッケージのインストール、アップグレードなどは行いません。

apt-get -y install --no-install-recommends xxx Laravelのインストールに必要なパッケージをインストールします。
下記のパッケージをインストールしておけばokだと思います。

-y オプションを付けることで問い合わせがあった場合はすべて「y」と答えます。

--no-install-recommends 不要なパッケージのインストールを防止します。

apt-get clean && rm -rf /var/lib/apt/lists/* ここはパッケージインストールで使用したキャッシュファイルを削除しています。

phpの公式Dockerイメージには、docker-php-ext-install, docker-php-ext-enable, docker-php-ext-configure のPHP拡張ライブラリを簡単に利用するための便利コマンドが予め用意されています。

docker-php-ext-install intl pdo_mysql zip bcmath PHPの拡張ライブラリをインストールしています。

COPY ./php.ini /usr/local/etc/php/php.ini

WORKDIR /data

./infra/php/php.ini を作成する

php.ini はPHPの設定ファイル

  • PHPエラーメッセージの設定
  • PHPエラーログの設定
  • メモリ等の設定(お好みで)
  • タイムゾーン設定
  • 文字コード設定
[mac] $ touch infra/php/php.ini
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 = Asia/Tokyo

[mysqlnd]
mysqlnd.collect_memory_statistics = on

[Assertion]
zend.assertions = 1

[mbstring]
mbstring.language = Japanese

src ディレクトリを作成

bindマウントするためのsrcディレクトリを作成します。

[mac] $ mkdir src

build & up

  • appコンテナの作成
  • PHPのバージョン確認
  • Laravelで必要なPHP拡張機能の確認
[mac] $ docker compose build

ビルドは、docker-compose.ymlDockerfile を元にDockerイメージを作成します。
作成したDockerイメージからDockerコンテナを生成します。

[mac] $ docker compose up -d
  • docker compose コマンドは docker-compose.yml があるディレクトリで実行します。
  • docker compose updocker-compose.yml に定義したサービスを起動します。
  • -d 「デタッチド」モードでコンテナを起動します。
    • デフォルトは「アタッチド」モードで全てのコンテナログを画面上に表示
    • 「デタッチド」モードではバックグラウンドで動作
[mac] $ docker compose ps
NAME                           COMMAND                  SERVICE             STATUS              PORTS
docker-laravel-handson-app-1   "docker-php-entrypoi…"   app                 running             9000/tcp

docker-laravel-handson_app_1 コンテナの State が Up になっていたら正常に起動している状態です。

  • docker compose ps コンテナ一覧を表示します。
    • Name: コンテナ名
    • Command: 最後に実行されたコマンド
    • State: 状態(Up)はコンテナが起動している状態
    • Ports: 9000/tcp(コンテナのポート)
      • 9000番のホストポートは公開されていないので、コンテナの外からはアクセスできない

appコンテナ内ミドルウェアのバージョン確認(コンテナに入ってコマンド実行)

作成したappコンテナの中に入ってPHP, Composerのバージョン、インストール済みの拡張機能を確認します。
Laravel 9.xのサーバ要件に必要な拡張機能が入っていることを確認します。

[mac] $ docker compose exec app bash
ユーザー名@コンテナID:ディレクトリ#
root@927707a27bba:/data#

# PHPのバージョン確認
[app] # php -v
PHP 8.1.3 (cli) (built: Feb 18 2022 20:07:43) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.3, Copyright (c) Zend Technologies

# Composerのバージョン確認
[app] # composer -V
Composer version 2.2.6 2022-02-04 17:00:38

# インストール済みの拡張機能の一覧
[app] # php -m
[PHP Modules]
bcmath
Core
ctype
curl
date
dom
fileinfo
filter
ftp
hash
iconv
intl
json
libxml
mbstring
mysqlnd
openssl
pcre
PDO
pdo_mysql
pdo_sqlite
Phar
posix
readline
Reflection
session
SimpleXML
sodium
SPL
sqlite3
standard
tokenizer
xml
xmlreader
xmlwriter
zip
zlib

[Zend Modules]

exit でコンテナの外に出ます。

[app] $ exit

control + d でもコンテナから出られます。

【補足】docker compose exec

appコンテナ内に入ってphpコマンドを実行しています。

  • docker compose exec 実行中のコンテナ内で、コマンドを実行します。
  • app サービス名(コンテナ名)を指定します。

PHPのバージョン確認(コンテナの外からコマンド実行)

コンテナの外から php コマンドを実行することもできます。

  • docker compose exec [サービス名] [実行したいコマンド]
[mac] $ docker compose exec app php -v
PHP 8.1.3 (cli) (built: Feb 18 2022 20:07:43) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.3, Copyright (c) Zend Technologies

結果にコミット

[mac] $ git status
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	docker-compose.yml
	infra/php/Dockerfile
	infra/php/php.ini

[mac] $ git add .
[mac] $ git status
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   docker-compose.yml
	new file:   infra/php/Dockerfile
	new file:   infra/php/php.ini

[mac] $ git commit -m "feat create docker app container"
[main e1e6eeb] feat create docker app container
 3 files changed, 48 insertions(+)
 create mode 100644 docker-compose.yml
 create mode 100644 infra/php/Dockerfile
 create mode 100644 infra/php/php.ini

[mac] $ git log

コンテナを破棄する

docker-compose.yml を変更するので、一度コンテナを破棄しておきます。

[mac] $ docker compose down

ウェブサーバー(web)コンテナを作る

nginxウェブサーバーコンテナを作成します。
nginxのベースイメージをそのまま利用します。

【補足】ディレクトリ構成

.
├── infra
│   └── nginx
│       └── default.conf # nginxの設定ファイル
├── src
│  └── public # 動作確認用に作成
│       ├── index.html # HTML動作確認用
│       └── phpinfo.php # PHP動作確認用
└─── docker-compose.yml

docker-compose.yml へ追記する

  • ポート転送の設定(今回は8080ポートにする)
  • タイムゾーンの設定
version: "3.9"
services:
  app:
    build: ./infra/php
    volumes:
      - ./src:/data

  # 追記
  web:
    image: nginx:1.20-alpine
    ports:
      - 8080:80
    volumes:
      - ./src:/data
      - ./infra/nginx/default.conf:/etc/nginx/conf.d/default.conf
    working_dir: /data
  • docker-compose.yml ファイルはインデント(半角スペース)が意味を持ちます。注意してコピペしてください。
  • app コンテナの設定と同じインデントレベルで貼り付けます。

【補足】docker-compose.yml

    image: nginx:1.20-alpine

コンテナを起動させるイメージを指定します。
nginx | Docker Hubを指定してます。
今回は公式のnginxイメージをそのまま利用しています。(Dockerfileは不要)

ちなみにnginxは1.10, 1.12 等の偶数のバージョンが安定バージョンになります。
特に理由がなければ偶数バージョンをご利用ください。

    ports:
      - 8080:80

nginxへ外(ホスト側)からコンテナ内へアクセスさせるため公開用のポートを設定します。
ホスト側:コンテナ側 と設定します。

    volumes:
      - ./src:/data
      - ./infra/nginx/default.conf:/etc/nginx/conf.d/default.conf

ホスト側にあるディレクトリ、ファイルをコンテナ内へマウントさせています。

infra/nginx/default.conf を作成する

[mac] $ mkdir infra/nginx
[mac] $ touch infra/nginx/default.conf

Laravel公式にnginxの設定例が用意されているので、こちらを流用します。
https://readouble.com/laravel/8.x/ja/deployment.html

default.conf
server {
    listen 80;
    server_name example.com;
    root /data/public;

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

    index 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 app:9000;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

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

root, fastcgi_pass ドキュメントルート設定を書き換えてます。
nginxの設定を詳しく知りたい方は下記の記事がオススメです。

build & up

[mac] $ docker compose up -d
  • docker-compose コマンドは docker-compose.yml があるディレクトリで実行します。
[mac] $ docker compose ps
NAME                           COMMAND                  SERVICE             STATUS              PORTS
docker-laravel-handson-app-1   "docker-php-entrypoi…"   app                 running             9000/tcp
docker-laravel-handson-web-1   "/docker-entrypoint.…"   web                 running             0.0.0.0:8080->80/tcp

docker-laravel-handson_web_1 コンテナの State が Up になっていたら正常に起動している状態です。

また、Ports の項目が、appコンテナは9000/tcp でwebコンテナは 0.0.0.0:8080->80/tcp と表示形式が異なってます。
これはホスト上の8080番ポートをコンテナの80番ポートへ割り当てています。

nginxのバージョン確認

[mac] $ docker compose exec web nginx -v
nginx version: nginx/1.20.2

webコンテナの確認

  • webコンテナの動作確認
  • HTMLとPHPが表示されるか
[mac] $ mkdir src/public
[mac] $ echo "Hello World" > src/public/index.html
[mac] $ echo "<?php phpinfo();" > src/public/phpinfo.php

「Hello World」が表示されることを確認する。
webサーバーが正しく動作することを確認できました。

phpinfoの情報が表示されることを確認する。
webサーバーがappサーバーへphpを実行させ結果を返してくれることを確認できました。

[mac] $ rm -rf src/*

確認用に作成したHTML, PHPファイルは不要なので削除します。

結果にコミット

[mac] $ git status
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   docker-compose.yml

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	infra/nginx/default.conf

[mac] $ git add .
[mac] $ git status
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   docker-compose.yml
	new file:   infra/nginx/default.conf

[mac] $ git commit -m "feat create docker web container"
[main 1e26387] feat create docker web container
 2 files changed, 41 insertions(+)
 create mode 100644 infra/nginx/default.conf

[mac] $ git log

Laravelをインストールする

  • app コンテナに入り、Laravelをインストール
  • welcomeページが表示されるか
[mac] $ docker compose exec app bash
[app] $ composer create-project --prefer-dist "laravel/laravel=9.*" .
[app] $ chmod -R 777 storage bootstrap/cache
[app] $ php artisan -V
Laravel Framework 9.1.0

[app] $ exit

Laravel ウェルカム画面の表示


LaravelのWelcome画面が表示されることを確認する。ライトモードとダークモードで色合いが変わります。

結果にコミット

[mac] $ git status
[mac] $ git add .
[mac] $ git status
[mac] $ git commit -m "feat laravel install"
[mac] $ git log

今回は大量のファイルがインストールで生成されているので、差分は省略してます。

コンテナを破棄する

docker-compose.yml を変更するので、一度コンテナを破棄しておきます。

[mac] $ docker compose down

データベース(db)コンテナを作る

MySQLデータベースコンテナを作成します。

【補足】mysql/mysql-serverイメージ

Docker公式がメンテしている mysql イメージと OracleのMySQLチームがメンテしている mysql/mysql-server の2種類あります。
オススメは mysql/mysql-server の方です。
mysql の方だとM1 Macが動作しないといった報告があります。

【補足】ディレクトリ構成

.
├── infra
│   └── mysql
│       ├── Dockerfile
│       └── my.cnf # MySQLの設定ファイル
└── docker-compose.yml

docker-compose.yml へ追記する

  • データベース名やユーザー名等の接続情報とタイムゾーンの設定は環境変数で渡す
  • トップレベルvolumeを使用してデータの永続化
version: "3.9"
services:
  app:
    build: ./infra/php
    volumes:
      - ./src:/data

  web:
    image: nginx:1.20-alpine
    ports:
      - 8080:80
    volumes:
      - ./src:/data
      - ./infra/nginx/default.conf:/etc/nginx/conf.d/default.conf
    working_dir: /data

  # 追記
  db:
    build: ./infra/mysql
    volumes:
      - db-store:/var/lib/mysql

volumes:
  db-store:

./infra/mysql/Dockerfile を作成する

[mac] $ mkdir infra/mysql
[mac] $ touch infra/mysql/Dockerfile

下記のコードを丸ごとコピーして Dockerfile へ貼り付けてください。

infra/mysql/Dockerfile
FROM mysql/mysql-server:8.0

ENV MYSQL_DATABASE=laravel \
  MYSQL_USER=phper \
  MYSQL_PASSWORD=secret \
  MYSQL_ROOT_PASSWORD=secret \
  TZ=Asia/Tokyo

COPY ./my.cnf /etc/my.cnf
RUN chmod 644 /etc/my.cnf

コメントでいただいたのですが、Windows環境でボリュームマウントを行うと、ファイルパーミッションが777となるようです。
my.cnf に書き込み権限が付いてるとMySQLの起動時にエラーが発生します。
その対策としてボリュームマウントではなくDockerfileを作成して my.cnf ファイルコピー、読み取り専用に権限変更してます。

infra/mysql/my.cnf を作成する

細かい解説はしませんが、主に下記の設定をしています。
ハンズオンでただ動かす程度ですから設定ファイル自体必要ないと思います。

  • 文字コード、照合順序の設定
  • タイムゾーンの設定
  • ログ設定
[mac] $ touch infra/mysql/my.cnf
my.cnf
[mysqld]
# default
skip-host-cache
skip-name-resolve
datadir = /var/lib/mysql
socket = /var/lib/mysql/mysql.sock
secure-file-priv = /var/lib/mysql-files
user = mysql

pid-file = /var/run/mysqld/mysqld.pid

# character set / collation
character_set_server = utf8mb4
collation_server = utf8mb4_ja_0900_as_cs_ks

# 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

default コメント部分はコンテナに元々入っていた設定を踏襲しています。

文字コードと照合順序は、 utf8mb4, utf8mb4_ja_0900_as_cs_ks を選択するのが現状良いとされています。

タイムゾーンでSYSTEMを設定するとOSのタイムゾーンが設定されます。

build & up

[mac] $ docker compose build
[mac] $ docker compose up -d
  • docker compose コマンドは docker-compose.yml があるディレクトリで実行します。
[mac] $ docker compose ps
NAME                           COMMAND                  SERVICE             STATUS               PORTS
docker-laravel-handson-app-1   "docker-php-entrypoi…"   app                 running              9000/tcp
docker-laravel-handson-db-1    "/entrypoint.sh mysq…"   db                  running (starting)   33060-33061/tcp
docker-laravel-handson-web-1   "/docker-entrypoint.…"   web                 running              0.0.0.0:8080->80/tcp

docker-laravel-handson_db_1 コンテナの State が Up になっていたら正常に起動している状態です。

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

マイグレーション実行(エラーが発生します)

[mac] $ docker compose exec app bash
[app] $ php artisan migrate

   Illuminate\Database\QueryException 

  SQLSTATE[HY000] [2002] Connection refused (SQL: select * from information_schema.tables where table_schema = laravel and table_name = migrations and table_type = 'BASE TABLE')

  at vendor/laravel/framework/src/Illuminate/Database/Connection.php:712
    708▕         // If an exception occurs when attempting to run a query, we'll format the error
    709▕         // message to include the bindings with SQL, which will make this exception a
    710▕         // lot more helpful to the developer instead of just the database's errors.
    711▕         catch (Exception $e) {
  ➜ 712▕             throw new QueryException(
    713▕                 $query, $this->prepareBindings($bindings), $e
    714▕             );
    715▕         }
    716▕     }

      +36 vendor frames 
  37  artisan:37
      Illuminate\Foundation\Console\Kernel::handle(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))


[app] $ exit

SQLSTATE[HY000] [2002] Connection refused このエラーはよく見るMySQLのエラーです。
MySQLに接続拒否されたエラーなので、この場合は大体MySQLへの接続設定に誤りがあります。

src/.env のDB接続設定を修正する。

[mac] $ code src/.env
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=phper
DB_PASSWORD=secret

src/.env.example も同様にDB接続設定を修正しておきます。

[mac] $ vim src/.env.example
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=phper
DB_PASSWORD=secret

【補足】DB_HOST=db

php artisan migrateapp サービスのコンテナが php を実行しています。
app から db サービスのコンテナへ通信できることが必要です。

.env
DB_HOST=127.0.0.1

これだと app コンテナ内のデータベースに接続しようと試みます。
appコンテナ内にデータベースはないので、もちろんエラーになります。

$ docker network list
NETWORK ID     NAME                             DRIVER    SCOPE
be6ba90aeade   bridge                           bridge    local
98a3fc98010d   docker-laravel-handson_default   bridge    local
be953fa99c81   host                             host      local
a8e65b8280d4   none                             null      local

docker-laravel-handson_default のデフォルトネットワークが構築されてます。
docker network inspect コマンドでネットワークの詳細を見れます。

$ docker network inspect docker-laravel-handson_default
[
    {
        "Name": "docker-laravel-handson_default",
        "Id": "98a3fc98010ddb61798d623c544d9a2a79762e85f94fc7c1660f2a76c83da8ed",
        "Created": "2022-02-21T16:02:09.872079294Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.21.0.0/16",
                    "Gateway": "172.21.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "1be61fbec051bd6f995a78ed3fe39b088a864f5eea01448e4fada84896a8cfee": {
                "Name": "docker-laravel-handson-app-1",
                "EndpointID": "1a920f7a384c0b53578c2b1f66cc80501d4879e0d9edac0e97dbbdb5c8b964bf",
                "MacAddress": "02:42:ac:15:00:03",
                "IPv4Address": "172.21.0.3/16",
                "IPv6Address": ""
            },
            "2485795092e86232ea8cbe91373f7cf71c568f25382fcde6a0e9252dd5b0a95b": {
                "Name": "docker-laravel-handson-db-1",
                "EndpointID": "4073d7ee440ba55e167e319e85ec05e9681a100e2505abd2debbfab09144daee",
                "MacAddress": "02:42:ac:15:00:02",
                "IPv4Address": "172.21.0.2/16",
                "IPv6Address": ""
            },
            "e1fa6a28aff5b6de5045e88e116e8ef1f978e0b7327b977de3771a4100b41c75": {
                "Name": "docker-laravel-handson-web-1",
                "EndpointID": "ff568293928f6fbf7c017548c3aff80bd91108a023dfc291d4eaf035c08dc44e",
                "MacAddress": "02:42:ac:15:00:04",
                "IPv4Address": "172.21.0.4/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "default",
            "com.docker.compose.project": "docker-laravel-handson",
            "com.docker.compose.version": "2.1.1"
        }
    }
]

Containers を見ると app, web, db コンテナがデフォルトネットワークに所属していることが分かります。
コンテナはネットワークを共有している場合のみ相互に通信できます。

各コンテナはホスト名を検索し、app, web, db のサービス名から適切なコンテナのIPアドレスを取得できます。

.env
DB_HOST=db

まとめるとLaravelのartisanコマンドはappのコンテナで実行され、dbコンテナのIPを解決してdbコンテナと通信できます。

マイグレーション実行(再実行)

[mac] $ docker compose exec app bash
[app] $ php artisan migrate

Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (58.09ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (42.39ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (70.60ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (62.23ms)

新しいターミナルを開いて、下記のコマンドを実行します。

[mac] $ docker compose exec db bash
[db] $ mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE
[mysql] > show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| laravel            |
+--------------------+
2 rows in set (0.00 sec)

[mysql] > show tables;
+------------------------+
| Tables_in_laravel      |
+------------------------+
| failed_jobs            |
| migrations             |
| password_resets        |
| personal_access_tokens |
| users                  |
+------------------------+
5 rows in set (0.00 sec)

[mysql] > desc users;
+-------------------+-----------------+------+-----+---------+----------------+
| Field             | Type            | Null | Key | Default | Extra          |
+-------------------+-----------------+------+-----+---------+----------------+
| id                | bigint unsigned | NO   | PRI | NULL    | auto_increment |
| name              | varchar(255)    | NO   |     | NULL    |                |
| email             | varchar(255)    | NO   | UNI | NULL    |                |
| email_verified_at | timestamp       | YES  |     | NULL    |                |
| password          | varchar(255)    | NO   |     | NULL    |                |
| remember_token    | varchar(100)    | YES  |     | NULL    |                |
| created_at        | timestamp       | YES  |     | NULL    |                |
| updated_at        | timestamp       | YES  |     | NULL    |                |
+-------------------+-----------------+------+-----+---------+----------------+
8 rows in set (0.01 sec)

[mysql] > SELECT * FROM users;
Empty set (0.01 sec)

MySQLを開いているターミナルは閉じずに残しておきましょう。

試しにデータを作ってみる

[mac] $ docker compose exec app bash
[app] $ php artisan tinker

# 下記のコードはコピペして実行できます。

$user = new App\Models\User();
$user->name = 'phper';
$user->email = 'phper@example.com';
$user->password = Hash::make('secret');
$user->save();

MySQLを開いているターミナルに戻って下記のSQLを実行します。

[mysql] > SELECT * FROM users\G
*************************** 1. row ***************************
               id: 1
             name: phper
            email: phper@example.com
email_verified_at: NULL
         password: $2y$10$/es6/J88KaxbbwKElD4qveMz/W5HdEb2T.r8loJdkLsqlGvHakUp6
   remember_token: NULL
       created_at: 2022-02-21 16:10:17
       updated_at: 2022-02-21 16:10:17
1 row in set (0.00 sec)

結果にコミット

[mac] $ git status
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   docker-compose.yml
        modified:   src/.env.example

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        infra/mysql/Dockerfile
        infra/mysql/my.cnf

[mac] $ git add .
[mac] $ git status

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   docker-compose.yml
        new file:   infra/mysql/Dockerfile
        new file:   infra/mysql/my.cnf
        modified:   src/.env.example

[mac] $ git commit -m "feat create docker db container"
[mac] $ git log

GitHubにpush

[mac] $ git push

リモートリポジトリへpushされていることを確認します。

Docker環境の再構築

Docker環境の破棄

コンテナの停止、ネットワーク・名前付きボリューム・コンテナイメージ、未定義コンテナを削除

[mac] $ docker compose down --rmi all --volumes --remove-orphans

作業ディレクトリの削除

プロジェクトを削除するので、GUIエディタは閉じておきましょう。

[mac] $ cd ..
[mac] $ rm -rf docker-laravel-handson

Visual Studio Code等のGUIエディタで開いている場合は、一度エディタを終了しましょう。

環境の再構築

GitHubからリポジトリをクローン

** 自身のリポジトリ先に適宜変更してください **

[mac] $ git clone git@github.com:ucan-lab/docker-laravel-handson.git
[mac] $ cd docker-laravel-handson
[mac] $ docker compose up -d

/data/public/../vendor/autoload.php を開くのに失敗してエラーになっていることを確認します。
git cloneが終わった状態では app コンテナ内に /data/vendor ディレクトリが存在しないためです。

Laravelインストール

app コンテナに入ります。

[mac] $ docker compose exec app bash

書き込み権限がないとキャッシュやログにエラーを書き込めないので、権限を付与しておきます。

[app] $ chmod -R 777 storage bootstrap/cache

vendor ディレクトリへライブラリ群をインストールします。
composer.lock ファイルを参照します。

[app] $ composer install

画面を開いて確認します。

500 SERVER ERROR だと何が原因なのか分かりません。
ログファイルを見てエラーを確認します。

[app] $ cat storage/logs/laravel.log

...

[2022-02-15 15:07:29] production.ERROR: No application encryption key has been specified. {"exception":"[object] (Illuminate\\Encryption\\MissingAppKeyException(code: 0): No application encryption key has been specified. at /data/vendor/laravel/framework/src/Illuminate/Encryption/EncryptionServiceProvider.php:79)
[stacktrace]
#0 /data/vendor/laravel/framework/src/Illuminate/Support/helpers.php(302): Illuminate\\Encryption\\EncryptionServiceProvider->Illuminate\\Encryption\\{closure}(NULL)
#1 /data/vendor/laravel/framework/src/Illuminate/Encryption/EncryptionServiceProvider.php(81): tap(NULL, Object(Closure))
#2 /data/vendor/laravel/framework/src/Illuminate/Encryption/EncryptionServiceProvider.php(60): Illuminate\\Encryption\\EncryptionServiceProvider->key(Array)
#3 /data/vendor/laravel/framework/src/Illuminate/Encryption/EncryptionServiceProvider.php(32): Illuminate\\Encryption\\EncryptionServiceProvider->parseKey(Array)
#4 /data/vendor/laravel/framework/src/Illuminate/Container/Container.php(873): Illuminate\\Encryption\\EncryptionServiceProvider->Illuminate\\Encryption\\{closure}(Object(Illuminate\\Foundation\\Application), Array)
#5 /data/vendor/laravel/framework/src/Illuminate/Container/Container.php(758): Illuminate\\Container\\Container->build(Object(Closure))
#6 /data/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(855): Illuminate\\Container\\Container->resolve('encrypter', Array, true)
#7 /data/vendor/laravel/framework/src/Illuminate/Container/Container.php(694): Illuminate\\Foundation\\Application->resolve('encrypter', Array)
#8 /data/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(840): Illuminate\\Container\\Container->make('encrypter', Array)
#9 /data/vendor/laravel/framework/src/Illuminate/Container/Container.php(1027): Illuminate\\Foundation\\Application->make('encrypter')
#10 /data/vendor/laravel/framework/src/Illuminate/Container/Container.php(947): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#11 /data/vendor/laravel/framework/src/Illuminate/Container/Container.php(908): Illuminate\\Container\\Container->resolveDependencies(Array)
#12 /data/vendor/laravel/framework/src/Illuminate/Container/Container.php(758): Illuminate\\Container\\Container->build('App\\\\Http\\\\Middle...')
#13 /data/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(855): Illuminate\\Container\\Container->resolve('App\\\\Http\\\\Middle...', Array, true)
#14 /data/vendor/laravel/framework/src/Illuminate/Container/Container.php(694): Illuminate\\Foundation\\Application->resolve('App\\\\Http\\\\Middle...', Array)
#15 /data/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(840): Illuminate\\Container\\Container->make('App\\\\Http\\\\Middle...', Array)
#16 /data/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(206): Illuminate\\Foundation\\Application->make('App\\\\Http\\\\Middle...')
#17 /data/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(180): Illuminate\\Foundation\\Http\\Kernel->terminateMiddleware(Object(Illuminate\\Http\\Request), Object(Illuminate\\Http\\Response))
#18 /data/public/index.php(55): Illuminate\\Foundation\\Http\\Kernel->terminate(Object(Illuminate\\Http\\Request), Object(Illuminate\\Http\\Response))
#19 {main}
"} 

例外が発生した時のログは下記のフォーマットで出力されます。

storage/logs/laravel.log
[YYYY-MM-DD HH:II:SS] 環境.ログレベル: ログメッセージ
トレースログ

トレースログは例外が発生した場所から呼び出し元の場所を遡って表示してくれます。
一番大事なのは最初のログメッセージです。

storage/logs/laravel.log
1[2022-02-15 15:07:29] production.ERROR: No application encryption key has been specified. 

「アプリケーション暗号化キーが指定されていません。」というエラーになります。
.envAPP_KEY に値が設定されてないと表示されるエラーです。

[app] $ ls -l
total 328
drwxr-xr-x 25 root     root        800 Feb 15 15:05 .
drwxr-xr-x  1 root     root       4096 Feb 15 15:05 ..
-rw-r--r--  1 root     root        258 Feb 15 15:03 .editorconfig
-rw-r--r--  1 root     root        897 Feb 15 15:03 .env.example
-rw-r--r--  1 root     root        152 Feb 15 15:03 .gitattributes
-rw-r--r--  1 root     root        207 Feb 15 15:03 .gitignore
-rw-r--r--  1 root     root        175 Feb 15 15:03 .styleci.yml
-rw-r--r--  1 root     root       3958 Feb 15 15:03 README.md
drwxr-xr-x  7 root     root        224 Feb 15 15:03 app
-rwxr-xr-x  1 root     root       1686 Feb 15 15:03 artisan
drwxr-xr-x  4 root     root        128 Feb 15 15:03 bootstrap
-rw-r--r--  1 root     root       1746 Feb 15 15:03 composer.json
-rw-r--r--  1 root     root     285701 Feb 15 15:03 composer.lock
drwxr-xr-x 17 www-data www-data    544 Feb 15 15:03 config
drwxr-xr-x  6 root     root        192 Feb 15 15:03 database
drwxr-xr-x  4 www-data www-data    128 Feb 15 15:03 lang
-rw-r--r--  1 root     root        473 Feb 15 15:03 package.json
-rw-r--r--  1 root     root       1175 Feb 15 15:03 phpunit.xml
drwxr-xr-x  6      101 ssh         192 Feb 15 15:03 public
drwxr-xr-x  5 root     root        160 Feb 15 15:03 resources
drwxr-xr-x  6 root     root        192 Feb 15 15:03 routes
drwxr-xr-x  5 root     root        160 Feb 15 15:03 storage
drwxr-xr-x  6 root     root        192 Feb 15 15:03 tests
drwxr-xr-x 44 root     root       1408 Feb 15 15:06 vendor
-rw-r--r--  1 root     root        559 Feb 15 15:03 webpack.mix.js

.env.example はありますが、 .env ファイルが存在しません。
理由は .env はGit管理対象外に設定されているからです。

$ cat .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
/.idea
/.vscode

そもそも .env ファイルがないのが問題なので、.env.example を元にコピーして作成します。
(composer create-project 時は .env は作成されますが、 composer install 時は .env ファイルは作成されません。)

[app] $ cp .env.example .env

エラー画面の見た目が変わりました。
これは .envAPP_DEBUG=true が設定されているためです。

エラーメッセージはログと同じく .envAPP_KEY= の値がないと出ています。
アプリケーションキーはこのコマンドで生成できます。

[app] $ php artisan key:generate
Application key set successfully.

とりあえず、Welcome画面が表示されました。

続いて、 public/storage から storage/app/public へのシンボリックリンクを張ります。
システムで生成したファイル等をブラウザからアクセスできるよう公開するためにシンボリックリンクを張ってます。

[app] $ php artisan storage:link

最後にマイグレーションを実行して適用されればOKです。

[app] $ php artisan migrate

Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (49.19ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (84.15ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (39.74ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (54.08ms)

お疲れ様でした🙌🙌 コンテナを停止して終了してください。

[app] $ exit
[mac] $ docker compose down

オマケ

GitHub Actions を使ってビルドテストを書く

$ git switch -c laravel-testing

プリリクエストを作りたいので、ブランチを切ります。

$ mkdir -p .github/workflows
$ touch .github/workflows/laravel-testing.yml

GitHub Actions のワークフロー設定ファイルを追加します。

.github/workflows/laravel-testing.yml
name: Laravel Testing

on:
  pull_request:

jobs:
  laravel-testing:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Docker Version
        run: docker version

      - name: Build Docker Images
        run: docker-compose build

      - name: Create & Start Docker Containers
        run: docker-compose up -d

      - name: OS Version
        run: |
          docker-compose exec -T app cat /etc/os-release
          docker-compose exec -T app cat /etc/debian_version

      - name: PHP Version
        run: docker-compose exec -T app php --version

      - name: Composer Version
        run: docker-compose exec -T app composer --version

      - name: Install Dependencies
        run: docker-compose exec -T app composer install

      - name: Laravel Version
        run: docker-compose exec -T app php artisan --version

      - name: Laravel Setting
        run: |
          docker-compose exec -T app cp .env.example .env
          docker-compose exec -T app php artisan key:generate

      - name: Laravel Migrate Testing
        run: docker-compose exec -T app php artisan migrate

      - name: Laravel Rollback Testing
        run: docker-compose exec -T app php artisan migrate:refresh

      - name: Laravel Seeding Testing
        run: docker-compose exec -T app php artisan db:seed

      - name: Laravel PHPUnit Testing
        run: docker-compose exec -T app php artisan test
$ git add .
$ git commit -m "feat laravel testing on github actions"
$ git push -u origin HEAD

...

remote:      https://github.com/ucan-lab/docker-laravel-handson/pull/new/laravel-testing

ターミナルに表示されるURLを開いてプルリクエスト作成します。

Detailsからワークフローの詳細ログを確認できます。

【補足】GitHub Actionsの無料枠

Public Repositoryの場合はActionsを無料で使用できます。
Privateの場合は無料枠を超えると追加で課金が必要なので注意してください。

【補足】Laravelのログをコンテナログに表示する

src/.env を修正する。

LOG_CHANNEL=stderr

src/routes/web.php

Route::get('/', function () {
    logger('welcome route.');
    return view('welcome');
});

$ docker compose logs
# -f でログウォッチ
$ docker compose logs -f
# サービス名を指定してログを表示
$ docker compose logs -f app

【補足】MySQLクライアントツールで接続したい

docker-compose.ymldb サービスに下記設定を追記して、コンテナを再起動して設定を反映してください。

    ports:
      - 33060:3306

dbコンテナへのポート公開設定がないとホストからアクセスが行えません。
MySQLのデフォルトポートは3306ですが、ポートが被らないように設定例では33060ポートを公開してます。

MySQLクライアントツールは「Sequel Ace」がオススメです。

Host: localhost
Username: phper
Password: secret
Database: laravel
Port: 33060

【補足】Windowsエラーメモ

Error response from daemon

$ docker compose up -d --build
docker: Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers).

解決策をいくつか

【補足】マイグレーションエラーの補足

Host '172.27.0.2' is not allowed to connect to this MySQL server

$ php artisan migrate
   Illuminate\Database\QueryException  : SQLSTATE[HY000] [1130] Host '172.27.0.2' is not allowed to connect to this MySQL server (SQL: select * from information_schema.tables where table_schema = homestead and table_name = migrations and table_type = 'BASE TABLE')

  at /work/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664
    660|         // If an exception occurs when attempting to run a query, we'll format the error
    661|         // message to include the bindings with SQL, which will make this exception a
    662|         // lot more helpful to the developer instead of just the database's errors.
    663|         catch (Exception $e) {
  > 664|             throw new QueryException(
    665|                 $query, $this->prepareBindings($bindings), $e
    666|             );
    667|         }
    668| 

  Exception trace:

  1   PDOException::("SQLSTATE[HY000] [1130] Host '172.27.0.2' is not allowed to connect to this MySQL server")
      /work/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  2   PDO::__construct("mysql:host=db;port=3306;dbname=homestead", "homestead", "secret", [])
      /work/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  Please use the argument -v to see more details.

このエラーが発生した場合は my.cnf を作成する前に docker compose up -d でビルドしてしまった可能性が高いです。

$ docker compose down --volumes --rmi all
$ docker compose up -d --build

設定ファイルがない状態でMySQLの初期化が行われたでデータが永続化されてしまってるので一度ボリューム毎削除してビルドし直せばokです。

豆知識

【補足】Makefileを使おう

install:
	@make clean
	@make build
	@make up
	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 chmod -R 777 storage bootstrap/cache
	@make fresh
clean:
	docker compose down --rmi all --volumes --remove-orphans
build:
	docker compose build --no-cache --force-rm
up:
	docker compose up -d
down:
	docker compose down
fresh:
	docker compose exec app php artisan migrate:fresh --seed
app:
	docker compose exec app bash
sql:
	docker compose exec db bash -c 'mysql -u $$MYSQL_USER -p$$MYSQL_PASSWORD $$MYSQL_DATABASE'
  • コマンドの前の空白はスペースではなく、タブで書く必要があります。
  • @make を使うとエコーバックされなくなります。
  • MakefileはC/C++のコンパイル定義に使われます。
    • エイリアス的に使うのは正しい使い方ではないです。
$ make install

とコマンドを打つとこれ一発で環境構築をやってくれます。
README.mdにコマンドの手順を書いてもいいですが、Makefileに書いてあった方が楽かなと思ってこっちを採用してます。

使用頻度の高いコマンドを定義しておくと良いです。

【補足】docker compose と docker-compose コマンドの違い

2021/03/10 Compose CLIが技術プレビュー版として利用可能になりました。(Docker Desktop for Mac, for Windows 3.2.1以降で利用可能です)

技術プレビュー版では本番環境で用いることは推奨されていませんが、今度は docker compose コマンドに移行になります。(docker-compose のほぼすべてのコマンドがリプレース済みです。)

【補足】depends_onの使い所

他の解説記事で登場する depends_on のご紹介です。

depends_on はサービス間の依存関係を表現します。
簡単な使い方の例をご紹介します。

docker-compose.yml
version: "3.9"
services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres
  • docker compose up 依存関係の順序でサービスを開始します。

    • この例では、dbredis のサービス開始後に web がサービス開始します。
  • docker compose stop 依存関係の順序でサービスを停止します。

    • この例では、web のサービス停止後に db, redis がサービス停止します。
  • 注意として、サービスの起動、停止順序を制御するだけで稼働順を制御するわけではないです。

    • 起動: 動き始めること(例: PC の電源ボタンを押すこと)
    • 稼働: 働きはじめること(例: PC が利用可能になること)
  • https://docs.docker.com/compose/compose-file/compose-file-v3/#depends_on

  • https://kotaroooo0-dev.hatenablog.com/entry/2020/07/25/000000

管理するサービスが増えて来た時に、どのサービスがどのサービスに依存してるのか明示して分かりやすくするために導入するのが良いと思います。
ハンズオンで取り入れると説明がめんどくさいので省略しています。(depends_on は実務でも特に使用してません)

【補足】linksは非推奨

links は コンテナ間の通信をするための設定です。
また、 depends_on と同じくサービスの起動順序を決定します。

links は古い記述方法で現在は非推奨となっています。

web:
  links:
    - "db"
    - "db:database"
    - "redis"

【補足】Composer公式のインストール方法は非推奨

Composer公式のインストール方法をそのまま利用すると下記のようになります。

FROM php:8.1-fpm-buster

RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
  && php -r "if (hash_file('sha384', 'composer-setup.php') === '906a84df04cea2aa72f40b5f787e49f22d4c2f19492ac310e8cba5b96ac8b64115ac402c8cd292b8a03482574915d1a8') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
  && php composer-setup.php \
  && php -r "unlink('composer-setup.php');" \
  && mv composer.phar /usr/local/bin/composer

問題となるのが4行目のハッシュ値チェックの箇所です。
常に最新版を取得するので、インストーラが更新されるたびにハッシュ値の修正が必要になります。

常に最新版を取得したい時は下記の指定でokです。

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

それでも勝手にバージョンが上がると困るので下記のようにマイナーバージョンまで指定してバージョン止めするのがベターです。

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

【補足】ADD命令の使い所

COPY 命令と似た命令で ADD 命令があります。
ADD 命令の特徴として、下記の内容があります。

  • リモートからもファイル追加ができる。
  • 圧縮ファイルが自動解凍される。

https://docs.docker.com/engine/reference/builder/#add
https://docs.docker.com/engine/reference/builder/#copy

COPY に比べて ADD は複雑な挙動になっています。
利用者が知らずに使うとセキュリティリスクが生じてしまいます。

ADD 命令を使う場合は注意して利用してください。

【補足】MySQL8.0の認証方式

MySQL8.0.4以降はデフォルトのログイン認証方式が mysql_native_password から caching_sha2_password に変更されています。

PHP 7.1.16, 7.2.4 以降は caching_sha2_password に対応しています。

古い記事だと mysql_native_password の認証方式に戻す手順の名残があるので注意してください。

【補足】Sequel ProでMySQL8.0に接続できない

Sequel ProはMySQL8.0に対応してません。

Sequel Aceという後継アプリが登場しているのでこちらを使いましょう。

【補足】コンテナOSのalpineは上級者向け

DockerHub公式のphpイメージのコンテナOSとしては buster, stretch, alpine の3種類が用意されています。

各Dockerイメージの容量は下記の通りでした。

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

容量が小さいとデプロイやテストが高速化したりメリットはあります。
しかし、タイムゾーンを設定するだけでも一手間かかったり保守性が著しく下がります。

メリットよりデメリットの方が大きいので、よほどの理由がない限りコンテナOSはDebianもしくはUbuntuを利用しましょう。

【補足】container_nameは指定しない

コンテナ名ですが、重複したコンテナ名を複数立ち上げることができません。
下記のように container_name を明示的に指定して重複を避ける方法があります。

docker-compose.yml
version: "3.9"
services:
  app: # ここはサービス名(コンテナ名ではない)
    image: php:8.1-fpm-buster
    container_name: myapp

container_name は被らない名前を付ける必要があるので、管理するコンテナが増えるほど名付けがめんどくさくなります。

そもそも、Docker Composeで立ち上げたコンテナは、 COMPOSE_PROJECT_NAME 変数 + _ + サービス名 + _ + 連番 とコンテナ名が自動的に命名されます。
COMPOSE_PROJECT_NAME に入るデフォルト値は docker-compose.yml があるディレクトリ名になります。

そのため他の階層で同じディレクトリ名でDocker Composeを使って立ち上げない限り被ることはありません。
その場合も .env ファイルで COMPOSE_PROJECT_NAME の環境変数を設定してあげればokです。

【補足】Node.js のコンテナは作らない

本番デプロイ用にNode.js用のコンテナを用意するのはアリだと思います。
しかし、ローカル環境ではディスクアクセスが多すぎるため開発効率が著しく下がります。

現時点ではローカル用のためにNode.jsコンテナは用意しない方が良いと思われます。
各PCにNode.jsをインストールしてもらって、バージョン固定化するためには .node-versionpackages.jsonengines を指定して対策しましょう。

【補足】RUN命令はワンライナーで実行する

Dockerfileで命令を実行するたびにイメージレイヤーが作成されます。
命令が増えれば増えるほどイメージレイヤーも増えていき、最終的なDockerイメージの容量が肥大化します。

これを防ぐために && でコマンドを繋げてワンライナーで実行します。

infra/php/Dockerfile
FROM php:8.1-fpm-buster

RUN apt-get update && \
  apt-get -y install --no-install-recommends git unzip libzip-dev libicu-dev libonig-dev && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/*

また、下記のように中間ファイルやキャッシュファイルを削除するRUN命令を書いてもイメージレイヤーが増えるのでむしろ容量は増えてしまいます。

infra/php/Dockerfile
RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/*

【補足】Dockerfileは変更頻度が低い順に書く

Dockerfileの上から順に命令を実行し、イメージレイヤーを生成します。
イメージビルド時にイメージレイヤーのキャッシュがあれば、新しくイメージレイヤーを使うのではなく再利用します。

更新頻度の高い設定ファイル系などはなるべく後の方に記述します。

【補足】実務で使う場合はさらなる考慮が必要

  • 設定値はイメージに含めず、環境変数で実行時に外から注入
  • データベースはコンテナを使わない(Amazon RDS等)
  • ログファイルは書かずに、標準出力(Amazon CloudWatch等)
  • Dockerイメージをコンテナレジストリに登録(Amazon ECR等)
  • コンテナの実行場所、コンテナ管理(Amazon ECS等)
  • サーバー環境変数の管理(Amazon Secrets Manage等)
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
1379
Help us understand the problem. What are the problem?