Docker Compose V2 实战指南:从零搭建较完整的 Web 应用栈
说明: 本文为配置思路与示例整理,不代表作者已在自己的服务器上逐项验证全部命令。执行涉及公网暴露、账户权限、数据删除或服务重启的操作前,请先备份,并结合官方文档与实际环境核验。
本文通过一个完整的实战案例,带你从零开始使用 Docker Compose V2 部署一个包含 Nginx、Node.js、PostgreSQL 和 Redis 的较完整的 Web 应用栈。包含配置详解、健康检查、数据持久化和常用运维命令。
前言
在实际项目中,一个完整的 Web 应用通常需要多个服务协同工作:Web 服务器、应用服务器、数据库、缓存……手动逐个启动这些服务不仅繁琐,而且容易出错。Docker Compose 正是解决这一痛点的利器。
本文将带你从零搭建一个较完整的的多容器应用栈,不是 Hello World,而是你可以直接用在真实项目中的配置。
最终效果
完成本文后,你将拥有:
- Nginx 反向代理 + 静态文件服务
- Node.js 应用服务(带健康检查)
- PostgreSQL 数据库(带持久化和初始化脚本)
- Redis 缓存(带密码认证和持久化)
项目结构
myapp/
├── docker-compose.yml
├── nginx/
│ └── nginx.conf
├── app/
│ ├── Dockerfile
│ └── src/
│ └── index.js
├── db/
│ └── init/
│ └── 01-schema.sql
└── .env第一步:环境变量配置
创建 .env 文件管理所有敏感配置:
# .env
POSTGRES_USER=appuser
POSTGRES_PASSWORD=changeme_strong_password_here
POSTGRES_DB=myapp_db
REDIS_PASSWORD=redis_secret_here
APP_PORT=3000
NODE_ENV=production⚠️ 安全提示:
.env文件不要提交到 Git 仓库,务必在.gitignore中添加.env。
第二步:编写 Docker Compose 配置
这是核心配置,每个细节都有注释说明:
# docker-compose.yml
services:
# ─── Nginx 反向代理 ───
nginx:
image: nginx:1.27-alpine
container_name: myapp-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- app_static:/app/static:ro
depends_on:
app:
condition: service_healthy
restart: unless-stopped
networks:
- frontend
# ─── Node.js 应用 ───
app:
build:
context: ./app
dockerfile: Dockerfile
container_name: myapp-node
environment:
- NODE_ENV=${NODE_ENV}
- DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
- REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379
- PORT=3000
volumes:
- app_static:/app/public
- app_logs:/app/logs
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
networks:
- frontend
- backend
# ─── PostgreSQL 数据库 ───
postgres:
image: postgres:16-alpine
container_name: myapp-postgres
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./db/init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
networks:
- backend
# ─── Redis 缓存 ───
redis:
image: redis:7-alpine
container_name: myapp-redis
command: >
redis-server
--requirepass ${REDIS_PASSWORD}
--appendonly yes
--maxmemory 256mb
--maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- backend
# ─── 持久化卷 ───
volumes:
postgres_data:
driver: local
redis_data:
driver: local
app_static:
driver: local
app_logs:
driver: local
# ─── 网络隔离 ───
networks:
frontend:
driver: bridge
backend:
driver: bridge关键设计要点
1. 健康检查(healthcheck)
没有健康检查的 depends_on 只保证容器启动顺序,不保证服务就绪。加上 condition: service_healthy 后,Nginx 会等 Node.js 真正能响应请求后才启动,避免 502 错误。
2. 网络隔离
将网络分为 frontend 和 backend 两层。Nginx 只在 frontend,Redis 和 PostgreSQL 只在 backend,Node.js 横跨两层。这样即使缓存被攻破,攻击者也无法直接访问数据库。
3. 数据持久化
使用 Docker Named Volumes 而不是 bind mount,数据存储在 Docker 管理的目录中,不会因容器删除而丢失。
第三步:Nginx 配置
# nginx/nginx.conf
worker_processes auto;
error_log /var/log/nginx/error.log warn;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'rt=$request_time';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
gzip_types text/plain application/json application/javascript text/css;
# ─── 限流配置 ───
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
upstream app_server {
server app:3000;
keepalive 32;
}
server {
listen 80;
server_name _;
# 静态文件直接由 Nginx 提供,不经过 Node.js
location /static/ {
alias /app/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# API 限流
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://app_server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
location / {
proxy_pass http://app_server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}第四步:应用 Dockerfile
# app/Dockerfile
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY src/ ./src/
FROM node:22-alpine
RUN apk add --no-cache wget
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/src ./src
COPY package.json ./
# 非 root 用户运行
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
RUN mkdir -p /app/logs /app/public && chown -R appuser:appgroup /app
USER appuser
EXPOSE 3000
CMD ["node", "src/index.js"]🔒 安全要点:始终以非 root 用户运行容器化进程,可以大幅降低容器逃逸的风险。
第五步:数据库初始化脚本
-- db/init/01-schema.sql
-- 此文件在 PostgreSQL 首次启动时自动执行
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE posts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
title VARCHAR(200) NOT NULL,
content TEXT,
published BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_posts_user_id ON posts(user_id);
CREATE INDEX idx_posts_created_at ON posts(created_at DESC);第六步:启动和验证
# 构建并启动所有服务
docker compose up -d --build
# 查看服务状态
docker compose ps
# 输出示例:
# NAME IMAGE STATUS PORTS
# myapp-nginx nginx:1.27-alpine Up (healthy) 0.0.0.0:80->80/tcp
# myapp-node myapp-app Up (healthy)
# myapp-postgres postgres:16-alpine Up (healthy) 5432/tcp
# myapp-redis redis:7-alpine Up (healthy) 6379/tcp
# 查看日志
docker compose logs -f app
# 测试 API
curl -s http://localhost/health常用运维命令速查
# 查看实时日志(所有服务)
docker compose logs -f
# 重启单个服务(修改 Nginx 配置后)
docker compose restart nginx
# 进入容器调试
docker compose exec app sh
docker compose exec postgres psql -U appuser -d myapp_db
# 执行一次性任务(如数据库迁移)
docker compose run --rm app npx prisma migrate deploy
# 停止并清理(保留数据卷)
docker compose down
# 停止并彻底清理(⚠️ 删除所有数据!)
docker compose down -v
# 查看资源占用
docker compose stats数据备份
# PostgreSQL 备份
docker compose exec postgres pg_dump -U appuser myapp_db > backup_$(date +%Y%m%d).sql
# Redis 备份
docker compose exec redis redis-cli -a $REDIS_PASSWORD BGSAVE
docker cp myapp-redis:/data/dump.rdb ./redis_backup_$(date +%Y%m%d).rdb常见问题排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 容器反复重启 | 健康检查失败 | docker compose logs <服务名> 查看具体错误 |
| 502 Bad Gateway | 后端未就绪 | 确保 depends_on 配置了 condition: service_healthy |
| 数据库连接被拒 | 密码/用户名不匹配 | 检查 .env 和 docker-compose.yml 的环境变量一致性 |
| 磁盘空间不足 | 日志或数据卷过大 | 定期 docker system prune,配置日志轮转 |
| 容器间通信失败 | 网络隔离问题 | 确保服务在同一个 Docker network 中 |
总结
本文展示了一个较完整的 Docker Compose 部署方案的核心要素:
- 健康检查 + 条件依赖:确保服务启动顺序正确
- 网络分层隔离:提升安全性
- 数据卷持久化:保障数据安全
- 多阶段构建:减小镜像体积
- 非 root 运行:降低安全风险
- 限流配置:保护后端服务
这些实践可以直接应用到你自己的项目中。建议从这个模板开始,根据实际需求增减服务。
评论
游客无需注册即可评论。
你提交的昵称、邮箱、网址和评论内容会保存在服务端,用于展示评论身份、接收回复及必要的安全审计。
浏览器会本地保存已填游客信息和评论草稿,方便下次免填。
回复提醒会通过站内消息和邮件通知。