【Docker + Laravel12】DockerでLEMP + Laravel12 + Viteの開発環境を考えてみた〜MailHogも添えて〜

## はじめに
この記事で紹介する構成は、以下の記事・サイトに基づいております。
スマートかつ分かりやすくご説明してくださっております。基本的な開発環境の構築をしたい方は、そちらをご覧ください。
- 全体的な構成

https://qiita.com/hinako_n/items/f15646ea548bcdc8ac6c
- `nginx/default.conf`記述一部

https://tech.torico-corp.com/blog/Laravel%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E7%B0%A1%E5%8D%98%E3%81%ABReact%E3%82%92%E9%96%8B%E7%99%BA%E3%81%A7%E3%81%8D%E3%82%8B%E7%92%B0%E5%A2%83%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B/

また、これは私の初記事・初ちゃんとした開発となります。拙い表現や誤りがあると思いますが、優しく指摘していただければと思います。

## ディレクトリ構成
ディレクトリ構成は下記のようにします。
webディレクトリには、Laravelプロジェクトのファイルが入ります。
```
(project_root)
│
├─── docker
│    ├─── mysql
│    │    ├─── my.conf
│    │    └─── Dockerfile
│    ├─── nginx
│    │    ├─── default.conf
│    │    └─── Dockerfile
│    ├─── php
│    │    ├─── php.ini (empty)
│    │    └─── Dockerfile
│    ├─── web
│    │    └─── (some files)
│    └─── compose.yaml
└─── (some files)
```

## 環境立ち上げ
ここにあることをやれば、とりあえず動くはずです。
目的とかどうでも良いという方は、ここだけ見てやってみてください。

### 準備
docker-composeを利用して開発環境を立ち上げます。以下のファイルを適切なディレクトリに置きます。

#### `docker/compose.yaml`
```:docker/compose.yaml
services:
  php:
    container_name: php
    build: ./php
    ports:
      - 5173:5173
    volumes:
      - ./web/:/app
    depends_on:
      - mysql

  server:
    container_name: server
    build: ./nginx
    ports:
      - 8000:80
    depends_on:
      - php
    volumes:
      - ./web/:/app

  mysql:
    container_name: mysql
    build: ./mysql
    ports:
      - 3306:3306
    environment:
      MYSQL_DATABASE: (お好きなDB名)
      MYSQL_USER: (お好きなユーザ名)
      MYSQL_PASSWORD: (お好きなパスワード)
      MYSQL_ROOT_PASSWORD: (お好きなルートパスワード)
      TZ: 'Asia/Tokyo'
    volumes:
      - (お好きなDB用ボリューム名):/var/lib/mysql

  pma:
    container_name: pma
    image: phpmyadmin:5
    ports:
      - 8080:80
    environment:
      PMA_HOST: mysql
    depends_on:
      - mysql

  mail:
    container_name: mailer
    image: mailhog/mailhog
    ports:
      - 8025:8025
      - 1025:1025
    environment:
      MH_STORAGE: maildir
      MH_MAILDIR_PATH: /tmp
    volumes:
      - (お好きなメールサーバ用ボリューム名):/tmp

volumes:
  (DB用ボリューム名):
  (メールサーバ用ボリューム名):
```

#### Dockerfile
- `php/Dockerfile`
```:php/Dockerfile
FROM php:8.3-fpm

ENV TZ Asia/Tokyo

RUN apt-get update && apt-get install -y \
	git \
	curl \
	zip \
	unzip \
    && docker-php-ext-install pdo_mysql

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

RUN apt-get install -y npm
RUN npm install n -g

WORKDIR /app
```
- `nginx/Dockerfile`
```:nginx/Dockerfile
FROM nginx:1.20-alpine

ENV TZ Asia/Tokyo

COPY default.conf /etc/nginx/conf.d/default.conf
```
- `mysql/Dockerfile`
```:mysql/Dockerfile
FROM mysql:8.0

COPY my.conf /etc/my.conf
```

#### `nginx/default.conf`
```:nginx/default.conf
server {
    listen 80;
    listen [::]:80;
    server_name example.com;
    root /app/public;

    add_header X-Frame-Options "SAMEORIGIN";
    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 ^~ /_vite/ {
        proxy_pass http://php:5173/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }


    location ~ ^/index\.php(/|$) {
        fastcgi_pass php:9000;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_hide_header X-Powered-By;
    }

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

#### `mysql/my.conf`
```:mysql/my.conf
[mysqld]
# character
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
```

#### コンテナ群立ち上げ
ファイルが準備できたら、dockerディレクトリに移動しコンテナを立ち上げましょう。
`docker % docker compose up -d`

### Laravelプロジェクト作成
- phpコンテナへログイン
`docker compose exec -it php bash`
- Laravelプロジェクト作成
`composer create-project 'laravel/laravel' ./ --prefer-dist`

上記の作成コマンドではカレントディレクトリに直接ファイルが作成されます。
プロジェクト名で管理したい場合は、`./`の部分をプロジェクト名にしてください。
`composer create-project 'laravel/laravel' hogehoge --prefer-dist`

### Viteサーバ立ち上げ（nodeの機能やホットリロードの有効化）
`web/vite.config.js`に以下の記述を追加します。追加場所は、`difineConfig`の中、`plugins`の外です。
```:web/vite.config.js
export default defineConfig({
    plugins: [
        (some writing)
    ],

    //add codes from
    server: {
        host: '0.0.0.0',
    }
});
```
`npm install`を実行します。
そしてViteサーバを立ち上げます。
`npm run dev`
ちなみに`vite.config.js`を編集せずともできます。その場合は、以下コマンドでViteサーバを立ち上げます。
`npm run dev -- --host 0.0.0.0`

この時点で`localhost:8000`にブラウザでアクセスすると、laravelのデフォルトページが表示されます。
- デフォルト以外のDBシステムは利用しない
- メールは使わない

上記の方はここで終わりです。開発頑張ってください。

### DB接続
デフォルト以外のDBを利用するのであれば`web/.env`，`web/config/database.php`の記述を編集します。つまり、Laravelの環境ファイルの記述を編集します。
下記は編集箇所抜粋です。
```:web/.env
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=(compose.yamlで記入したDB名)
DB_USERNAME=root
DB_PASSWORD=(compose.tamlで記入したルートパスワード)
```
```:web/config/database.php
'default' => env('DB_CONNECTION', 'mysql'),
```

### メールサーバ接続
デフォルトでは**メールを送信すると全て`web/storage/logs/laravel.log`へ流れます**。実質的なメール送受信はこれで再現されてはいるので、それで構わなければ編集不要です。開発頑張ってください。
送信したメールをイメージ通りのメールとして見たい場合は、`web/.env`を編集します。
下記は編集箇所抜粋です。
```:web/.env
MAIL_MAILER=smtp
MAIL_SCHEME=null
MAIL_HOST=mailer
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="(お好きな送信元テストメアド)"
MAIL_FROM_NAME="(お好きな送信元名)"
```

## 目的
この構成は、以下に示すことを目的として構成しました。

- Larvel12で開発すること
ただただLaravelを使いたかったのです。
- Laravel12のBladeテンプレート主軸で開発すること
SSR(サーバサイドレンダリング)で簡易アプリを作りたかったのです。
- Laravelの機能でメール認証をすること
せっかく機能としてあるので、メールアドレス認証をしたかったのです。
- [ Laravel + Tailwind CSS ] + Nginx 構成でやりやすく開発すること
LEMP環境での開発、Laravelと共にインストールされるTailwind CSSの利用、ホットリロードの利用をしたかったのです。
- MySQLをメインで使うこと
LEMP環境での開発・今までの経験的にこっちの方が慣れていたので入れたかったのです。

「LaravelはAPIメインでしょ」という考えがあったりなかったりすると思いますが、Laravel10くらいからだんだんフロント側の機能が豊富になってきているそうです。
やろうと思えばSPAもLaravel主軸で作れるとか。

## 構成図
最終的に、図のような構成となりました。表し方が違う箇所があるかもですがご容赦ください。
![arch.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4087640/509ed2cb-7fbf-4954-909b-b13a36409b4a.png)

いわゆるLEMP環境に近いと思います。
見ていただくと分かる通り、けっこう欲張りセットになっています。メールの送信テストはもちろん、DBをGUIで操作・閲覧できます。
「phpコンテナの中でNode.jsが動いている」というのはそこまで不思議ではないとは思います。BladeテンプレートでTailwind CSSを使ったり、それをホットリロードでリアルタイムに弄りまくる都合上、別でNodeコンテナを立ち上げるか迷う構成にはなります。
**元からSPA開発を考えている方は別でNodeコンテナを立て、そちらでViteサーバを立てるなりページ開発するなりすることをおすすめします**。

## 補足説明：`default.conf`について
以下については、[始めにご紹介した記事](#はじめに)にて説明していただいておりますので、そちらを御覧ください。
- 各`Dockerfile` `compose.yaml`記述
- `mysql/my.conf`記述
- `nginx/default.conf`におけるViteホットリロードに関する記述

### `default.conf`のViteホットリロードに関する記述
この記事では、以下の部分がこれに当たります
```:nginx/default.conf
    location ^~ /_vite/ {
        proxy_pass http://php:5173/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
```
一方で、始めにご紹介したブログ記事では、`proxy_pass`が以下となっています。
```:default.conf
proxy_pass http://host.docker.internal:5173
```
この違いは、**Viteサーバをphpコンテナに立てるか、ホストコンピュータ上に立てるか**にあります。

**私の記事では、phpコンテナ上でViteサーバを立てます**。そのため、nginxコンテナ⇔phpコンテナ間で通信が必要になるため、phpコンテナのサービス名を使ってパスを通しています。
ちなみに、phpコンテナでViteサーバを立てた理由としては、Laravelプロジェクトを作成するときにおまけでVite関連のファイルも作ってくれるので、それを使いたかったからです。~~横着です。~~

一方で**ブログ記事の方では、ホストコンピュータ上でViteサーバを立てています**。これはあくまでも「見る限りでは…」って感じなので確証こそありませんが…
この場合、**コンテナ間通信ではないため先程の手が使えません**。ではどうするかというと、紹介されているように`host.docker.internal`を使ってホストコンピュータ上へアクセスすることで、パスを通しています。こやつはコンテナ⇔ホストの橋渡しです。

私のように「**Bladeテンプレート主軸で開発したい！**」という場合は、私のようにphpコンテナでViteサーバを立てる方式が適しているのではと考えています。
メインの開発やサーバ起動をホストコンピュータ上で行い、「**dockerにはプロキシサーバ・APIサーバのみを求める**」みたいな場合は、ブログ記事のような`host.docker.internal`方式が良いのかなと思います。

## 最後に
この環境は、既にいろいろな記事で説明されていると思うので、ご紹介した記事やさらに他の記事とも見比べてみてほしいと思います。その中で、この記事が何かしらのお役に立てたら嬉しいです。