React + Vite のフロントエンドと、Cloudflare Workers + Hono の API サーバーを別々に起動していたのですが、docker-compose up だけで立ち上げられるようにしました。
やりたかったこと
これまではローカル開発時に、フロントエンドと API を別ターミナルで起動していました。
npm run dev
npm run dev:api
この状態だと、毎回新しいターミナルを2つ開いてそれぞれでコマンドを打たなければいけないので、すごく面倒に感じていました。
そこで Docker Compose を使って、次の2つを同時に起動できるようにしました。
- Vite dev server
- Wrangler dev server
前提
このプロジェクトでは、package.json に次のスクリプトがあります。
{
"scripts": {
"dev": "vite",
"dev:api": "wrangler dev worker/index.ts --local --port 8787"
}
}
フロントエンドは Vite、API は Cloudflare Workers を wrangler dev で起動しています。
また、Vite 側では /api へのリクエストを API サーバーへプロキシしています。
docker-compose.yml を追加する
追加した docker-compose.yml は次のような構成です。
services:
api:
image: node:22-bookworm-slim
working_dir: /app
environment:
CI: "true"
ports:
- "8787:8787"
volumes:
- .:/app
- api_node_modules:/app/node_modules
- wrangler_state:/app/.wrangler
command: >
sh -c "npm ci &&
npx wrangler d1 migrations apply kinkyo-note-db --local --persist-to .wrangler/state &&
npm run dev:api -- --ip 0.0.0.0 --persist-to .wrangler/state"
frontend:
image: node:22-bookworm-slim
working_dir: /app
environment:
API_PROXY_TARGET: http://api:8787
CHOKIDAR_USEPOLLING: "true"
ports:
- "5173:5173"
volumes:
- .:/app
- frontend_node_modules:/app/node_modules
depends_on:
- api
command: >
sh -c "npm ci &&
npm run dev -- --host 0.0.0.0"
volumes:
api_node_modules:
frontend_node_modules:
wrangler_state:
これで次のコマンドだけで、フロントエンドと API サーバーを同時に起動できます。
docker-compose up
Vite のプロキシ先を環境変数で切り替える
もともと Vite のプロキシ先は localhost:8787 にしていました。
proxy: {
'/api': 'http://localhost:8787',
},
ただし Docker Compose 内では、frontend コンテナから見た localhost は frontend コンテナ自身です。
API コンテナへアクセスするには、Compose のサービス名である api を使う必要があります。
そのため、vite.config.ts を次のように変更しました。
const apiProxyTarget = process.env.API_PROXY_TARGET ?? 'http://localhost:8787'
export default defineConfig({
server: {
proxy: {
'/api': apiProxyTarget,
},
},
})
ローカルで直接 npm run dev する場合は、これまで通り http://localhost:8787 を使います。
Docker Compose で起動する場合だけ、docker-compose.yml 側で次の環境変数を渡します。
environment:
API_PROXY_TARGET: http://api:8787
0.0.0.0 で待ち受ける
コンテナ内で開発サーバーを起動する場合、localhost だけで待ち受けているとホスト側ブラウザからアクセスできません。
そのため、Vite と Wrangler の両方で 0.0.0.0 を指定しています。
command: >
sh -c "npm ci &&
npm run dev -- --host 0.0.0.0"
command: >
sh -c "npm ci &&
npm run dev:api -- --ip 0.0.0.0 --persist-to .wrangler/state"
ローカル D1 のマイグレーションも起動時に実行する
API では Cloudflare D1 を使っているため、初回起動時にローカル D1 のテーブルが存在しないと API が正しく動きません。
そこで API サービスの起動コマンド内で、wrangler d1 migrations apply を実行しています。
npx wrangler d1 migrations apply kinkyo-note-db --local --persist-to .wrangler/state
--persist-to .wrangler/state を指定して、wrangler dev と同じローカル永続化ディレクトリを使うようにしています。
Compose では .wrangler を volume にしているため、コンテナを停止してもローカル D1 の状態は保持されます。
volumes:
- wrangler_state:/app/.wrangler
ローカル D1 の状態をリセットしたい場合は、volume ごと削除します。
docker-compose down -v
node_modules は volume に分ける
ソースコードは bind mount しています。
volumes:
- .:/app
ただし、このままだとホスト側の node_modules とコンテナ側の node_modules が混ざりやすくなります。
そこで node_modules はサービスごとに named volume に分けました。
volumes:
- api_node_modules:/app/node_modules
volumes:
- frontend_node_modules:/app/node_modules
これにより、依存関係はコンテナ内に閉じつつ、ソースコードの変更はホストからそのまま反映できます。
まとめ
Docker Compose を追加したことで、開発開始時のコマンドは次だけになりました。
docker-compose up
フロントエンドと API サーバーを別々に起動する必要がなくなり、ローカル D1 のマイグレーションも起動時に自動で適用されるようになりました。
小さな変更ですが、開発環境の再現性と立ち上げやすさがかなり改善しました。