docker-compose 多容器编排实战:Web、数据库与 Redis 一键启动全栈

一、为什么本地开发需要 Compose

一个真实 Web 项目往往不止一个进程:API 服务、数据库、缓存、反向代理、消息队列和监控。手工执行多个 docker run 时,参数很快会失控:忘记挂载数据卷会丢库,忘记加入网络会无法解析服务名,环境变量不一致又会导致“在我机器上正常”。

Compose 将这些参数声明在 YAML 中,提供:

  • 一条命令启动和停止整个应用;
  • 服务名自动提供 DNS;
  • 网络、卷和环境变量可版本化;
  • 新成员不需要逐项安装数据库;
  • 开发、测试和生产可通过覆盖文件复用配置。

Compose v2 是 Go 实现并集成到 Docker CLI 中,命令格式为 docker compose;旧版 Python Compose 使用 docker-compose,两者配置大部分兼容,但应优先使用 v2。

二、实战 1:FastAPI、PostgreSQL、Redis

目录结构:

1
2
3
4
5
6
7
demo/
├── compose.yaml
├── .env
└── app/
├── Dockerfile
├── requirements.txt
└── main.py

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
services:
api:
build: ./app
environment:
DATABASE_URL: postgresql://app:app@db:5432/app
REDIS_URL: redis://redis:6379/0
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
networks: [backend]
ports:
- "8000:8000"

db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 3s
retries: 10
networks: [backend]

redis:
image: redis:7-alpine
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
networks: [backend]

volumes:
pgdata:
redisdata:

networks:
backend:
driver: bridge

应用文件:

1
2
3
4
5
6
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY main.py .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
1
2
3
fastapi==0.115.6
uvicorn[standard]==0.34.0
redis==5.2.1
1
2
3
4
5
6
7
8
9
10
11
import os
from fastapi import FastAPI
from redis import Redis

app = FastAPI()
redis = Redis.from_url(os.environ["REDIS_URL"], decode_responses=True)

@app.get("/")
def index():
redis.incr("requests")
return {"service": "api", "requests": redis.get("requests")}

启动:

1
2
3
docker compose up -d --build
docker compose ps
curl http://localhost:8000/

depends_on 只保证启动顺序时并不足够。使用 condition: service_healthy 后,API 会等待数据库和 Redis 健康检查通过。应用本身仍应实现重试,因为健康检查通过后服务也可能在运行期间短暂重启。

三、Nginx 反向代理

生产或接近生产的开发环境中,可以增加 Nginx:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
services:
nginx:
image: nginx:1.27-alpine
ports:
- "80:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- api
networks: [frontend, backend]

networks:
frontend:
backend:

default.conf

1
2
3
4
5
6
7
8
server {
listen 80;
location / {
proxy_pass http://api:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

Nginx 连接的是 api:8000,不是宿主机的 localhost:8000。在容器网络中,localhost 始终表示当前容器。

四、实战 2:WordPress 经典组合

WordPress 需要应用、MySQL、反向代理和可选管理工具:

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
services:
wordpress:
image: wordpress:php8.3-apache
environment:
WORDPRESS_DB_HOST: mysql:3306
WORDPRESS_DB_USER: wp
WORDPRESS_DB_PASSWORD: change-me
WORDPRESS_DB_NAME: wordpress
depends_on:
mysql:
condition: service_healthy
volumes:
- wordpress:/var/www/html
networks: [lamp]

mysql:
image: mysql:8.4
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wp
MYSQL_PASSWORD: change-me
MYSQL_ROOT_PASSWORD: root-change-me
volumes:
- mysql:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 5s
retries: 20
networks: [lamp]

phpmyadmin:
image: phpmyadmin:5
environment:
PMA_HOST: mysql
ports:
- "8081:80"
depends_on:
- mysql
networks: [lamp]

volumes:
wordpress:
mysql:

networks:
lamp:

数据库密码应通过 .env 或密钥管理系统提供,不要把生产密码提交到仓库。

五、实战 3:ELK 日志收集

Elasticsearch、Logstash 和 Kibana 资源消耗较大。开发机应限制 JVM 堆:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.15.3
environment:
discovery.type: single-node
xpack.security.enabled: "false"
ES_JAVA_OPTS: "-Xms512m -Xmx512m"
ports:
- "9200:9200"
volumes:
- esdata:/usr/share/elasticsearch/data

kibana:
image: docker.elastic.co/kibana/kibana:8.15.3
environment:
ELASTICSEARCH_HOSTS: http://elasticsearch:9200
ports:
- "5601:5601"
depends_on:
- elasticsearch

volumes:
esdata:

日志平台不应与核心数据库共用无上限资源。Compose 可以通过 mem_limit 做开发环境保护,生产则应使用更完善的资源调度方案。

六、网络、卷与多环境配置

自定义 bridge 网络支持服务名解析和网络隔离。可以让 Nginx 同时加入 frontendbackend,数据库只加入 backend,这样外部入口无法直接访问数据库。

named volume 由 Docker 管理,适合数据库;bind mount 直接映射宿主机目录,适合源代码热加载和配置文件。数据库生产环境通常优先 named volume 或独立存储。

.env

1
2
3
POSTGRES_USER=app
POSTGRES_PASSWORD=local-password
POSTGRES_DB=app

compose.yaml 中可以写 ${POSTGRES_PASSWORD}。注意 .env 是变量插值来源,不等同于自动注入所有容器。需要注入时,明确写 env_fileenvironment

开发覆盖文件:

1
2
3
4
5
services:
api:
volumes:
- ./app:/app
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload

启动生产配置:

1
docker compose -f compose.yaml -f compose.prod.yaml up -d --build

七、常用调试命令与踩坑

1
2
3
4
5
6
docker compose logs -f api
docker compose exec db psql -U app -d app
docker compose top
docker compose config
docker compose down
docker compose down -v

down -v 会删除 named volume 中的数据,不能在生产环境随意执行。容器内时区默认可能是 UTC,业务应统一使用 UTC 存储、在展示层转换。volume 权限问题通常来自宿主机 UID 与容器用户不同,应使用非 root 用户并调整目录属主。

八、小结

Compose 适合单机开发、测试、演示和中小规模部署。它不是 Kubernetes 的简单替代品,但能把多容器系统的依赖、网络和数据生命周期清楚地表达出来。团队应提交 Compose 文件、示例环境变量和健康检查,同时把密码、证书和生产密钥交给专门的 Secret 系统管理。


docker-compose 多容器编排实战:Web、数据库与 Redis 一键启动全栈
https://blog.calcguide.tech/2026-08-10-docker-compose多容器编排实战/
作者
王争气
发布于
2026年8月10日
许可协议