はじめに#
Docker Composeの基本的な書き方を習得したら、次のステップは実際の開発プロジェクトで使える構成を組み立てることです。モダンなWebアプリケーションでは、フロントエンド、バックエンドAPI、データベース、キャッシュサーバーなど複数のサービスが連携して動作します。
本記事では、React/Node.js + PostgreSQL + Redisという実践的な3層構成をDocker Composeで構築する方法を解説します。サービス間の依存関係をdepends_onとhealthcheckで適切に制御し、開発時のホットリロード設定で効率的な開発環境を実現する手法を紹介します。
この記事を読み終えると、以下のことができるようになります。
- Web+DB+キャッシュ構成のcompose.yamlを設計・実装できる
depends_onとhealthcheckを組み合わせてサービスの起動順序を制御できる
- 開発時のホットリロード設定でコード変更を即座に反映できる
- 実際のプロジェクトで使えるcompose.yamlのテンプレートを持てる
前提として、Docker Composeの基本操作を理解していることを想定しています。compose.yamlの基本構文に不安がある場合は、Docker Compose入門を参照してください。
Web+DB+キャッシュ構成の全体像#
構成するサービス#
今回構築する構成は、以下の4つのサービスで構成されます。
| サービス |
役割 |
使用イメージ |
| frontend |
Reactフロントエンド(開発サーバー) |
node:22-alpine |
| api |
Node.js/Express APIサーバー |
node:22-alpine |
| db |
PostgreSQLデータベース |
postgres:18 |
| cache |
Redisキャッシュサーバー |
redis:8-alpine |
アーキテクチャ図#
各サービスの関係性を図で示します。
flowchart TB
subgraph DockerCompose["Docker Compose 環境"]
frontend["frontend\nReact:5173"]
api["api\nExpress:3000"]
db["db\nPostgres:5432"]
cache["cache\nRedis:6379"]
dbData[("db-data\n(Volume)")]
cacheData[("cache-data\n(Volume)")]
frontend -->|"API呼出"| api
api --> db
api --> cache
db --> dbData
cache --> cacheData
end
browser["ホスト:5173\n(ブラウザ)"] --> frontend通信フロー#
- ブラウザからfrontend(React開発サーバー)にアクセス
- frontendからapiサーバーにHTTPリクエストを送信
- apiはキャッシュ(Redis)を確認し、キャッシュヒットならそのまま返却
- キャッシュミスの場合、db(PostgreSQL)からデータを取得
- 取得したデータをキャッシュに保存し、クライアントに返却
サービス定義例#
各サービスの設定を順番に解説します。
PostgreSQLサービス(db)#
データベースサービスの定義から始めます。PostgreSQLは他のサービスの基盤となるため、最も重要な設定です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
services:
db:
image: postgres:18
container_name: myapp-db
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: apppassword
POSTGRES_DB: myapp_development
TZ: Asia/Tokyo
volumes:
- db-data:/var/lib/postgresql/data
- ./docker/db/init:/docker-entrypoint-initdb.d:ro
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
|
ポイントとなる設定を説明します。
環境変数
POSTGRES_USER、POSTGRES_PASSWORD、POSTGRES_DBでデータベースの初期設定を行います。TZでタイムゾーンを日本時間に設定しています。
ボリューム設定
db-dataという名前付きボリュームでデータを永続化します。./docker/db/initディレクトリに配置したSQLファイルは、コンテナ初回起動時に自動実行されます。:ro(read-only)を付けることで、コンテナからの書き込みを防止しています。
healthcheck設定
pg_isreadyコマンドでPostgreSQLが接続を受け付けられる状態かを確認します。$${POSTGRES_USER}のようにドル記号を2つ重ねることで、Composeの変数展開を避けてコンテナ内で環境変数を参照できます。
Redisサービス(cache)#
セッション管理やAPIレスポンスのキャッシュに使用するRedisの設定です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
services:
cache:
image: redis:8-alpine
container_name: myapp-cache
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- cache-data:/data
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
|
commandによる設定カスタマイズ
redis-serverのオプションでRedisの動作をカスタマイズしています。
| オプション |
説明 |
--appendonly yes |
AOF(Append Only File)永続化を有効化 |
--maxmemory 256mb |
最大メモリ使用量を256MBに制限 |
--maxmemory-policy allkeys-lru |
メモリ上限到達時、LRU方式で古いキーを削除 |
healthcheck設定
redis-cli pingコマンドでRedisサーバーの応答を確認します。正常な場合はPONGが返却されます。
Node.js APIサービス(api)#
Express.jsで構築されたAPIサーバーの設定です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
services:
api:
build:
context: ./api
dockerfile: Dockerfile
container_name: myapp-api
environment:
NODE_ENV: development
PORT: 3000
DATABASE_URL: postgresql://appuser:apppassword@db:5432/myapp_development
REDIS_URL: redis://cache:6379
volumes:
- ./api:/app
- /app/node_modules
ports:
- "3000:3000"
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
|
DATABASE_URLとREDIS_URL
Docker Composeでは、サービス名がそのままホスト名として解決されます。db:5432でPostgreSQLに、cache:6379でRedisに接続できます。
ボリュームマウントの工夫
1
2
3
|
volumes:
- ./api:/app # ソースコードをマウント
- /app/node_modules # node_modulesはコンテナ内のものを使用
|
/app/node_modulesを匿名ボリュームとしてマウントすることで、ホストのnode_modulesがコンテナ内を上書きすることを防ぎます。これにより、ホストとコンテナで異なるOS(例:WindowsホストとLinuxコンテナ)でも正しく動作します。
depends_onとcondition
condition: service_healthyを指定することで、依存サービスのhealthcheckが成功するまでAPIサーバーの起動を待機します。
Reactフロントエンドサービス(frontend)#
Viteを使用したReactアプリケーションの開発サーバーです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
services:
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: myapp-frontend
environment:
NODE_ENV: development
VITE_API_URL: http://localhost:3000
volumes:
- ./frontend:/app
- /app/node_modules
ports:
- "5173:5173"
depends_on:
api:
condition: service_healthy
restart: unless-stopped
|
VITE_API_URL
ViteではVITE_プレフィックスを付けた環境変数がクライアントサイドで利用可能になります。ブラウザからAPIにアクセスするため、localhost:3000を指定しています。
depends_onとhealthcheckの活用#
サービスの起動順序と依存関係を適切に制御することは、安定した開発環境の構築に不可欠です。
depends_onの基本#
depends_onには2つの記法があります。
短縮形式(Short Syntax)
1
2
3
4
5
|
services:
api:
depends_on:
- db
- cache
|
この形式では、依存サービスが「起動した」ことのみを保証します。サービスが実際にリクエストを受け付けられる状態かどうかは確認しません。
長形式(Long Syntax)
1
2
3
4
5
6
7
8
|
services:
api:
depends_on:
db:
condition: service_healthy
restart: true
cache:
condition: service_started
|
長形式では追加のオプションを指定できます。
| オプション |
説明 |
condition |
依存サービスの状態条件 |
restart |
依存サービス再起動時に自動再起動するか |
required |
依存サービスが必須かどうか(デフォルト: true) |
conditionの種類
| 値 |
説明 |
service_started |
サービスが起動したら(デフォルト) |
service_healthy |
healthcheckが成功したら |
service_completed_successfully |
サービスが正常終了したら |
healthcheckの設定方法#
healthcheckは、コンテナが「正常に動作している」かを定期的に確認する仕組みです。
1
2
3
4
5
6
|
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
|
各パラメータの意味
| パラメータ |
説明 |
デフォルト |
test |
ヘルスチェックコマンド |
なし |
interval |
チェック間隔 |
30s |
timeout |
タイムアウト時間 |
30s |
retries |
失敗許容回数 |
3 |
start_period |
起動猶予期間 |
0s |
start_periodは、コンテナ起動直後の初期化期間を指定します。この期間中のヘルスチェック失敗はカウントされません。
testコマンドの形式
1
2
3
4
5
6
7
8
|
# exec形式(推奨)
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
# shell形式
test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
# 文字列形式(CMD-SHELLと同等)
test: curl -f http://localhost:3000/health || exit 1
|
各サービスのhealthcheck例#
PostgreSQL
1
2
3
4
5
6
|
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
|
Redis
1
2
3
4
5
6
|
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
|
Node.js API
1
2
3
4
5
6
|
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
|
依存関係のチェーン#
複数サービスの依存関係を正しく定義することで、起動順序が自動的に決定されます。
起動順序:
db, cache (並列) → api → frontend
停止順序:
frontend → api → db, cache (並列)
Composeは依存関係を解析し、依存されていないサービスから順番に起動します。停止時は逆順になります。
開発時のホットリロード設定#
開発効率を高めるために、コード変更を即座にコンテナに反映させるホットリロード設定を行います。
バインドマウントによるソース同期#
ホストのソースコードをコンテナ内にマウントすることで、ファイル変更がリアルタイムで反映されます。
1
2
3
4
5
|
services:
api:
volumes:
- ./api:/app
- /app/node_modules
|
Node.jsのホットリロード設定#
APIサーバーでnodemonを使用したホットリロードを設定します。
Dockerfile(api/Dockerfile)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
FROM node:22-alpine
WORKDIR /app
# 依存関係のインストール
COPY package*.json ./
RUN npm ci
# nodemonをグローバルインストール
RUN npm install -g nodemon
# ソースコードのコピー(開発時はマウントで上書き)
COPY . .
EXPOSE 3000
# 開発時はnodemonで起動
CMD ["nodemon", "--watch", "src", "-e", "js,ts,json", "src/index.js"]
|
compose.yamlでの設定
1
2
3
4
5
6
7
8
9
10
|
services:
api:
build:
context: ./api
dockerfile: Dockerfile
volumes:
- ./api:/app
- /app/node_modules
environment:
NODE_ENV: development
|
Vite(React)のホットリロード設定#
Viteは標準でHMR(Hot Module Replacement)をサポートしていますが、Docker環境では追加設定が必要です。
vite.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
host: '0.0.0.0', // コンテナ外からのアクセスを許可
port: 5173,
watch: {
usePolling: true, // Dockerボリュームマウント時に必要
},
hmr: {
port: 5173,
},
},
})
|
Dockerfile(frontend/Dockerfile)
1
2
3
4
5
6
7
8
9
10
11
12
|
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
EXPOSE 5173
CMD ["npm", "run", "dev", "--", "--host"]
|
Docker Compose Watchの活用#
Docker Compose 2.22.0以降では、developセクションでwatch機能を利用できます。
1
2
3
4
5
6
7
8
9
10
11
12
|
services:
api:
build:
context: ./api
dockerfile: Dockerfile
develop:
watch:
- action: sync
path: ./api/src
target: /app/src
- action: rebuild
path: ./api/package.json
|
actionの種類
| action |
説明 |
sync |
ファイル変更をコンテナに同期 |
rebuild |
ファイル変更時にイメージを再ビルド |
sync+restart |
同期後にコンテナを再起動 |
watchモードは以下のコマンドで起動します。
実践compose.yamlサンプル#
これまでの設定をまとめた完全なcompose.yamlを示します。
ディレクトリ構成#
my-project/
├── compose.yaml
├── .env
├── api/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
│ └── index.js
├── frontend/
│ ├── Dockerfile
│ ├── package.json
│ ├── vite.config.js
│ └── src/
│ └── App.jsx
└── docker/
└── db/
└── init/
└── 01_schema.sql
完全なcompose.yaml#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
name: myapp
services:
# PostgreSQLデータベース
db:
image: postgres:18
container_name: myapp-db
environment:
POSTGRES_USER: ${DB_USER:-appuser}
POSTGRES_PASSWORD: ${DB_PASSWORD:-apppassword}
POSTGRES_DB: ${DB_NAME:-myapp_development}
TZ: Asia/Tokyo
volumes:
- db-data:/var/lib/postgresql/data
- ./docker/db/init:/docker-entrypoint-initdb.d:ro
ports:
- "${DB_PORT:-5432}:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
networks:
- backend
# Redisキャッシュ
cache:
image: redis:8-alpine
container_name: myapp-cache
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- cache-data:/data
ports:
- "${REDIS_PORT:-6379}:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
networks:
- backend
# Node.js APIサーバー
api:
build:
context: ./api
dockerfile: Dockerfile
container_name: myapp-api
environment:
NODE_ENV: development
PORT: 3000
DATABASE_URL: postgresql://${DB_USER:-appuser}:${DB_PASSWORD:-apppassword}@db:5432/${DB_NAME:-myapp_development}
REDIS_URL: redis://cache:6379
volumes:
- ./api:/app
- /app/node_modules
ports:
- "${API_PORT:-3000}:3000"
depends_on:
db:
condition: service_healthy
restart: true
cache:
condition: service_healthy
restart: true
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
networks:
- backend
- frontend
# Reactフロントエンド
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: myapp-frontend
environment:
NODE_ENV: development
VITE_API_URL: http://localhost:${API_PORT:-3000}
volumes:
- ./frontend:/app
- /app/node_modules
ports:
- "${FRONTEND_PORT:-5173}:5173"
depends_on:
api:
condition: service_healthy
restart: unless-stopped
networks:
- frontend
networks:
backend:
driver: bridge
frontend:
driver: bridge
volumes:
db-data:
cache-data:
|
環境変数ファイル(.env)#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# Database
DB_USER=appuser
DB_PASSWORD=apppassword
DB_NAME=myapp_development
DB_PORT=5432
# Redis
REDIS_PORT=6379
# API
API_PORT=3000
# Frontend
FRONTEND_PORT=5173
|
初期化SQL(docker/db/init/01_schema.sql)#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
-- テーブル作成例
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS posts (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
content TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- インデックス作成
CREATE INDEX IF NOT EXISTS idx_posts_user_id ON posts(user_id);
|
起動と動作確認#
全サービスの起動
1
2
3
4
5
6
7
8
|
# バックグラウンドで起動
docker compose up -d
# ログを確認しながら起動
docker compose up
# 特定サービスのみ起動
docker compose up -d db cache
|
サービス状態の確認
1
2
3
4
5
|
# 全サービスの状態確認
docker compose ps
# ヘルスチェック状態を含めて確認
docker compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}"
|
ログの確認
1
2
3
4
5
6
7
8
|
# 全サービスのログ
docker compose logs
# 特定サービスのログをフォロー
docker compose logs -f api
# 最新100行のみ表示
docker compose logs --tail 100 api
|
停止と削除
1
2
3
4
5
6
7
8
|
# サービス停止(コンテナは残る)
docker compose stop
# サービス停止とコンテナ削除
docker compose down
# ボリュームも含めて削除(データ初期化)
docker compose down -v
|
まとめ#
本記事では、Docker Composeを使用してReact/Node.js + PostgreSQL + Redisの実践的な開発環境を構築する方法を解説しました。
学んだポイント
- Web+DB+キャッシュの3層構成におけるサービス設計
depends_onの長形式とconditionによる起動順序の制御
healthcheckによるサービス正常性の確認
- バインドマウントとDocker Compose Watchによるホットリロード設定
- 環境変数とネットワーク分離を活用した保守性の高いcompose.yaml
この構成をベースに、ログ収集(Fluentd)、メトリクス監視(Prometheus)、リバースプロキシ(Nginx)などを追加していくことで、より本格的な開発・ステージング環境を構築できます。
次のステップとして、複数のCompose設定ファイルを使い分ける手法や、本番環境向けの設定について学ぶと、さらに実践的なDocker活用ができるようになります。
参考リンク#