Docker + Traefik + mkcert を使ったローカル開発環境構築
安定して動くローカル開発環境構築は開発者の永遠のテーマですが、今回、Docker + Traefik + mkcert を使うことで、ブラウザで開いたときに証明書エラーが出ないローカル開発環境を構築したので、まとめてみます。
全体の設定
mkcert で証明書を作成する
mkcert はローカル開発環境で使える SSL 証明書を作成するツールで、Windows、macOS、Linux で利用することができます。今回は Windows 版を前提に説明します。管理者でターミナルを開いて、以下のように入力します。
winget install mkcert # mkcert のインストール
mkcert -install # ローカル CA の登録と信頼されたルート証明機関への登録
mkcert "*.dev.test" # dev.test のワイルドカード証明書の作成
ワイルドカード証明書のドメインを *.dev.test にしたのには理由があって、*.test だと、Edge や Chrome で証明書エラー(NET::ERR_CERT_COMMON_NAME_INVALID)が出るためです。これは mkcert の Github issue でも報告されていて、ブラウザ側の実装なのでどうにもならないです。
もう一つ個人的にハマったポイントとして、*.dev.test のワイルドカード証明書が有効になるのは、example.dev.test や app.dev.test のように1レベルのサブドメインのみということです。なので、後で出てくる Vite 用の別ドメインも vite.example.dev.test としてしまうとワイルドカード証明書が使えないので、vite-examle.dev.test のようにする必要があります。これはワイルドカード証明書作成時に
Created a new certificate valid for the following names 📜
- "*.dev.test"
Reminder: X.509 wildcards only go one level deep, so this won't match a.b.dev.test
のように書かれていたのですが、うっかり見落としていました。
上記のコマンドを実行すると、_wildcard.dev.test.pem と _wildcard.dev.test-key.pem の2つのファイルができあがるので、これを Traefik 用のフォルダに移動します。
Traefik の設定
Traefik はオープンソースのリバースプロキシおよびロードバランサで、Docker と連携させることで複数のサイトを SSL 化させることができます。
Traefik 自体も Docker で提供されているので、以下のような compose.yaml を作成します。
services:
traefik:
image: "traefik:3"
container_name: "traefik"
command:
- "--providers.docker=true"
- "--providers.docker.exposedByDefault=false" # `traefik.enable=true`を指定したコンテナのみ対象にする。
- "--providers.docker.network=webdev" # 対象とするネットワークを明示的に指定する。
- "--providers.file.filename=/etc/traefik/tls.yaml" # SSL 証明書の設定
# `web`エントリポイントの設定をする。
- "--entryPoints.web.address=:80"
- "--entryPoints.web.http.redirections.entryPoint.permanent=true"
- "--entryPoints.web.http.redirections.entryPoint.scheme=https"
- "--entryPoints.web.http.redirections.entryPoint.to=websecure"
# `websecure`エントリポイントの設定をする。
- "--entryPoints.websecure.address=:443"
- "--entryPoints.websecure.http.tls=true"
networks: ["webdev"]
ports: ["80:80", "443:443"]
restart: "always"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- ".:/etc/traefik:ro"
networks:
webdev:
name: webdev
driver: bridge
また、SSL 証明書の設定のための tls.yaml も作成します。
tls:
certificates:
- certFile: /etc/traefik/_wildcard.dev.test.pem
keyFile: /etc/traefik/_wildcard.dev.test-key.pem
これで全体の設定は完了です。docker compose up -d --build を実行してコンテナを起動します。
個別サイトの設定
次に個別のサイトの設定を行います。PHP + MariaDB + Node.js が動く環境であれば、こんな感じです。こちらも設定が終わったら、docker compose up -d --build を実行してコンテナを起動します。
name: example
services:
app:
image: thecodingmachine/php:8.3-v5-apache-node24
ports:
- '80'
environment:
PHP_EXTENSIONS: bcmath ctype curl dom fileinfo gd imagick mbstring mysqlnd pdo_mysql tokenizer xdebug xml zip
PHP_INI_MEMORY_LIMIT: 512M
PHP_INI_DATE__TIMEZONE: Asia/Tokyo
PHP_INI_MBSTRING__LANGUAGE: Japanese
APACHE_DOCUMENT_ROOT: /var/www/html/public
VIRTUAL_HOST: 'example.dev.test'
TZ: Asia/Tokyo
LANG: C.UTF-8
STARTUP_COMMAND_1: sudo apt update; sudo apt install -y jpegoptim optipng pngquant gifsicle mariadb-client
volumes:
- '..:/var/www/html'
labels:
traefik.enable: true
traefik.docker.network: webdev
# Apache
traefik.http.routers.example.entrypoints: websecure
traefik.http.routers.example.rule: Host(`example.dev.test`)
traefik.http.routers.example.service: example
traefik.http.services.example.loadbalancer.server.port: 80
networks: ["webdev"]
mariadb:
image: 'mariadb:10'
networks: ["webdev"]
ports:
- '${FORWARD_DB_PORT:-3306}:3306'
environment:
TZ: Asia/Tokyo
MARIADB_ROOT_PASSWORD: '${DB_PASSWORD:-password}'
MARIADB_DATABASE: '${DB_DATABASE:-mariadb}'
MARIADB_USER: '${DB_USERNAME:-mariadb}'
MARIADB_PASSWORD: '${DB_PASSWORD:-password}'
MARIADB_ALLOW_EMPTY_PASSWORD: 'yes'
volumes:
- 'mariadb-data:/var/lib/mysql'
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
retries: 3
timeout: 5s
networks:
webdev:
external: true
name: "webdev"
volumes:
mariadb-data:
driver: local
ドメインの設定は C:\windows\System32\drivers\etc\hosts ファイルを直接編集するか、PowerToys のホストファイルエディターで編集します。
127.0.0.1 example.dev.test
これで、https://example.dev.test/ をブラウザで開くと、証明書の警告が出ないページが表示されると思います。
Vite の HMR 用の設定を追加する
Laravel で開発する場合、コードを修正するたびに npm run build でアセットをビルドするのは面倒なので、npm run dev で Vite の HMR(Hot Module Replacement) を有効にして開発したいところ。ただ、そのままだと、https://example.dev.test/ は HTTPS で通信しているのに、HMR で生成されるコンテンツは HTTP で配信されるため、混在コンテンツとして扱われ、CSS などが読み込まれません。
そこで追加の設定を行います。まず、compose.yaml を以下のように書き換えます。
name: example
services:
app:
image: thecodingmachine/php:8.3-v5-apache-node24
ports:
- '80'
environment:
PHP_EXTENSIONS: bcmath ctype curl dom fileinfo gd imagick mbstring mysqlnd pdo_mysql tokenizer xdebug xml zip
PHP_INI_MEMORY_LIMIT: 512M
PHP_INI_DATE__TIMEZONE: Asia/Tokyo
PHP_INI_MBSTRING__LANGUAGE: Japanese
APACHE_DOCUMENT_ROOT: /var/www/html/public
VIRTUAL_HOST: 'example.dev.test'
TZ: Asia/Tokyo
LANG: C.UTF-8
STARTUP_COMMAND_1: sudo apt update; sudo apt install -y jpegoptim optipng pngquant gifsicle mariadb-client
volumes:
- '..:/var/www/html'
labels:
traefik.enable: true
traefik.docker.network: webdev
# Apache
traefik.http.routers.example.entrypoints: websecure
traefik.http.routers.example.rule: Host(`example.dev.test`)
traefik.http.routers.example.service: example
traefik.http.services.example.loadbalancer.server.port: 80
# Vite
traefik.http.routers.vite-example.entrypoints: websecure
traefik.http.routers.vite-example.rule: Host(`vite-example.dev.test`)
traefik.http.routers.vite-example.service: vite-example
traefik.http.services.vite-example.loadbalancer.server.port: 5173
networks: ["webdev"]
mariadb:
image: 'mariadb:10'
networks: ["webdev"]
ports:
- '${FORWARD_DB_PORT:-3306}:3306'
environment:
TZ: Asia/Tokyo
MARIADB_ROOT_PASSWORD: '${DB_PASSWORD:-password}'
MARIADB_DATABASE: '${DB_DATABASE:-mariadb}'
MARIADB_USER: '${DB_USERNAME:-mariadb}'
MARIADB_PASSWORD: '${DB_PASSWORD:-password}'
MARIADB_ALLOW_EMPTY_PASSWORD: 'yes'
volumes:
- 'mariadb-data:/var/lib/mysql'
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
retries: 3
timeout: 5s
networks:
webdev:
external: true
name: "webdev"
volumes:
mariadb-data:
driver: local
さらに、Laravel の vite.config.js を以下のように書き換えます。
import { defineConfig, loadEnv } from 'vite';
import laravel from 'laravel-vite-plugin';
import { bunny } from 'laravel-vite-plugin/fonts';
import tailwindcss from '@tailwindcss/vite';
// ============================================================
// ✏️ プロジェクトごとにここだけ変更する
// ============================================================
const PROJECT = {
input: ['resources/css/app.css', 'resources/js/app.js'],
port: 5173,
strictPort: true, // ポートが塞がっていたら起動失敗させる
fonts: [
bunny('Inter', { weights: [400, 500, 600] }),
],
};
// ============================================================
function buildHmrConfig(hmrHost, port) {
return {
protocol: hmrHost ? 'wss' : 'ws',
host: hmrHost || 'localhost',
clientPort: hmrHost ? 443 : port,
};
}
function buildServerConfig(env, { port, strictPort }) {
return {
host: '0.0.0.0',
port,
cors: true,
strictPort,
hmr: buildHmrConfig(env.VITE_HMR_HOST, port),
watch: {
ignored: ['**/storage/framework/views/**'],
},
};
}
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
return {
plugins: [
laravel({
input: PROJECT.input,
refresh: true,
fonts: PROJECT.fonts,
}),
tailwindcss(),
],
server: buildServerConfig(env, PROJECT),
};
});
この vite.config.js では、環境変数 VITE_HMR_HOST の有無で設定を切り替えているので、.env に
VITE_HMR_HOST=vite-example.dev.test
を追加します。最後に、hosts ファイルに
127.0.0.1 vite-example.dev.test
を追加すれば準備完了です。
あとは、サイトを追加するたびに、hosts にドメインの設定を追加し、それに合わせて compose.yaml(と vite.config.js)を書き換えていけば OK です。準備は多少面倒ですが、HTTPS が有効な開発環境を好きなだけ増やせるのでおすすめです。
ディスカッション
コメント一覧
まだ、コメントがありません