Docker Compose 开发:本地基础设施
愉快的入门体验(“克隆并运行 pnpm dev:infra”)和痛苦的入门体验(“首先设置 PostgreSQL,然后配置 Redis,然后......”)之间的区别取决于您的 Docker Compose 设置如何很好地满足您的基础设施要求。精心设计的 docker-compose.dev.yml 可以让任何机器上的任何开发人员在几分钟内运行完全相同的基础设施。
本指南涵盖了生产质量本地开发堆栈的模式:服务配置、运行状况检查、网络、卷管理以及与应用程序启动序列的集成。
要点
- 在本地使用 PostgreSQL 的非默认端口 (5433) 以避免与系统安装发生冲突
- 对服务依赖项的健康检查可防止“连接被拒绝”启动错误
- 命名卷在容器重新启动之间保留数据库数据 - 绑定安装在 Windows 上无法可靠工作
- 使用
env_file将环境变量从.env.local文件加载到容器中- 将
docker-compose.dev.yml与docker-compose.prod.yml分开 — 它们有不同的用途depends_on.condition: service_healthy模式等待实际准备就绪,而不仅仅是容器启动- 使用
profiles选择可选服务(电子邮件、监控)- 运行
docker compose(v2) 而不是docker-compose(v1) — 插件语法是最新的
完整的开发堆栈
# infrastructure/docker-compose.dev.yml
name: ecosire-dev
services:
# ─── PostgreSQL ─────────────────────────────────────────────────
postgres:
image: postgres:17-alpine
container_name: ecosire-postgres
environment:
POSTGRES_DB: ecosire_dev
POSTGRES_USER: ecosire
POSTGRES_PASSWORD: dev_password_change_in_prod
ports:
- "5433:5432" # 5433 externally — avoids conflicts with system PostgreSQL
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d # Run SQL on first start
command: >
postgres
-c shared_buffers=256MB
-c effective_cache_size=1GB
-c work_mem=16MB
-c maintenance_work_mem=128MB
-c checkpoint_completion_target=0.9
-c wal_buffers=16MB
-c max_connections=100
-c log_min_duration_statement=100
-c log_statement=ddl
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ecosire -d ecosire_dev"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
# ─── Redis ──────────────────────────────────────────────────────
redis:
image: redis:7-alpine
container_name: ecosire-redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: >
redis-server
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--appendonly yes
--appendfsync everysec
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
# ─── Authentik (Identity Provider) ──────────────────────────────
authentik-server:
image: ghcr.io/goauthentik/server:2024.12
container_name: ecosire-authentik
command: server
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgres
AUTHENTIK_POSTGRESQL__USER: ecosire
AUTHENTIK_POSTGRESQL__PASSWORD: dev_password_change_in_prod
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_SECRET_KEY: dev-secret-key-change-in-production-32chars
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
AUTHENTIK_DISABLE_STARTUP_ANALYTICS: "true"
volumes:
- authentik_media:/media
- authentik_certs:/certs
ports:
- "9000:9000" # HTTP
- "9443:9443" # HTTPS
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "ak healthcheck"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s # Authentik takes time to initialize
restart: unless-stopped
authentik-worker:
image: ghcr.io/goauthentik/server:2024.12
container_name: ecosire-authentik-worker
command: worker
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgres
AUTHENTIK_POSTGRESQL__USER: ecosire
AUTHENTIK_POSTGRESQL__PASSWORD: dev_password_change_in_prod
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_SECRET_KEY: dev-secret-key-change-in-production-32chars
volumes:
- authentik_media:/media
- authentik_certs:/certs
- /var/run/docker.sock:/var/run/docker.sock # For Authentik's proxy
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
# ─── Mailpit (Email Testing) ─────────────────────────────────────
mailpit:
image: axllent/mailpit:latest
container_name: ecosire-mailpit
ports:
- "1025:1025" # SMTP
- "8025:8025" # Web UI
environment:
MP_MAX_MESSAGES: 200
MP_SMTP_AUTH_ACCEPT_ANY: true
MP_SMTP_AUTH_ALLOW_INSECURE: true
restart: unless-stopped
profiles:
- email # Optional — use `docker compose --profile email up`
# ─── pgAdmin (Database GUI) ─────────────────────────────────────
pgadmin:
image: dpage/pgadmin4:latest
container_name: ecosire-pgadmin
environment:
PGADMIN_DEFAULT_EMAIL: [email protected]
PGADMIN_DEFAULT_PASSWORD: admin
PGADMIN_CONFIG_SERVER_MODE: "False"
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False"
ports:
- "5050:80"
volumes:
- pgadmin_data:/var/lib/pgadmin
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
profiles:
- tools # Optional
networks:
default:
name: ecosire-dev-network
volumes:
postgres_data:
name: ecosire-postgres-data
redis_data:
name: ecosire-redis-data
authentik_media:
name: ecosire-authentik-media
authentik_certs:
name: ecosire-authentik-certs
pgadmin_data:
name: ecosire-pgadmin-data
Package.json 脚本
将 Docker Compose 命令连接到 monorepo 脚本中:
{
"scripts": {
"dev:infra": "docker compose -f infrastructure/docker-compose.dev.yml up -d",
"dev:infra:down": "docker compose -f infrastructure/docker-compose.dev.yml down",
"dev:infra:logs": "docker compose -f infrastructure/docker-compose.dev.yml logs -f",
"dev:infra:reset": "docker compose -f infrastructure/docker-compose.dev.yml down -v && pnpm dev:infra",
"dev:infra:email": "docker compose -f infrastructure/docker-compose.dev.yml --profile email up -d",
"dev:infra:tools": "docker compose -f infrastructure/docker-compose.dev.yml --profile tools up -d"
}
}
--profile 标志允许可选服务(使用 Mailpit 进行电子邮件测试、使用 pgAdmin 进行数据库 GUI)保持休眠状态,直到明确请求为止。
数据库初始化脚本
将 SQL 文件放入 infrastructure/init-scripts/ — 它们在第一个容器启动时运行:
-- infrastructure/init-scripts/01-create-databases.sql
-- Create all databases Authentik needs separately from the app DB
CREATE DATABASE authentik;
GRANT ALL PRIVILEGES ON DATABASE authentik TO ecosire;
-- Create test database for CI
CREATE DATABASE ecosire_test;
GRANT ALL PRIVILEGES ON DATABASE ecosire_test TO ecosire;
-- infrastructure/init-scripts/02-extensions.sql
-- Enable PostgreSQL extensions
\c ecosire_dev;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- Trigram search
CREATE EXTENSION IF NOT EXISTS "btree_gin"; -- Composite GIN indexes
\c ecosire_test;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
初始化脚本按字母顺序运行。 \c database_name psql 元命令切换活动数据库。
环境变量集成
您的应用程序从 monorepo 根目录中的 .env.local 读取。 Docker 服务需要知道如何使用服务名称(而不是 localhost)相互连接:
# .env.local (monorepo root)
# PostgreSQL — use 5433 externally (host) or 5432 internally (container network)
DATABASE_URL=postgresql://ecosire:dev_password_change_in_prod@localhost:5433/ecosire_dev
# Redis
REDIS_URL=redis://localhost:6379
# Authentik — use 9000 for external calls from your dev machine
AUTHENTIK_URL=http://localhost:9000
# Use service name for server-to-server calls within Docker network
AUTHENTIK_INTERNAL_URL=http://authentik-server:9000
# Email (Mailpit SMTP)
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_SECURE=false
# Application
NODE_ENV=development
对于在 Docker 内运行且需要与其他服务通信的应用程序,请使用服务名称。对于在主机上运行的应用程序(开发模式下的 NestJS、Next.js),请将 localhost 与主机映射端口一起使用。
健康检查深入探讨
健康检查可防止级联启动失败。 depends_on.condition: service_healthy 等待实际准备就绪,而不仅仅是容器启动:
# Without health checks — can fail because PostgreSQL isn't ready
depends_on:
- postgres
# With health checks — waits for PostgreSQL to accept connections
depends_on:
postgres:
condition: service_healthy
为您自己的服务定制健康检查:
// apps/api/src/health/health.controller.ts
@Get()
@Public()
@HealthCheck()
async check() {
return this.health.check([
() => this.db.isHealthy('database'),
() => this.redis.isHealthy('redis'),
]);
}
# If your API is also dockerized
api:
image: ecosire-api:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
卷管理
命名卷在重新启动之间保留数据。了解何时使用每种卷类型:
| 类型 | 坚持 | 性能 | 用于 |
|---|---|---|---|
| 命名卷 | 是的 | 优秀 | 数据库数据 |
| 绑定安装 | 是的 | 好 (Linux),差 (macOS) | 源代码热重载 |
| tmpfs | 没有 | 优秀 | 临时文件,秘密 |
# Use bind mounts for source code (enables hot reload)
volumes:
- ./apps/api/src:/app/src # Code changes reflected immediately
# Use named volumes for data
volumes:
- postgres_data:/var/lib/postgresql/data
# Use tmpfs for ephemeral data
volumes:
- type: tmpfs
target: /tmp
在具有 Docker Desktop 的 macOS 上,绑定挂载使用 gRPC FUSE,这比 Linux 上慢得多。对于 NestJS 和 Next.js 开发服务器,直接在主机上(而不是在 Docker 中)运行它们以获得本机文件系统性能。
生产 Docker Compose
生产撰写文件在结构上有所不同 - 无本地端口、重启策略、生产资源限制:
# infrastructure/docker-compose.prod.yml
name: ecosire-prod
services:
postgres:
image: postgres:17-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
# No port mapping — only accessible within Docker network
restart: always
deploy:
resources:
limits:
memory: 2G
reservations:
memory: 512M
redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 1gb
volumes:
- redis_data:/data
restart: always
# No port mapping — internal only
volumes:
postgres_data:
redis_data:
生产环境不会向外部公开端口——应用程序通过内部 Docker 网络进行连接。 Nginx 处理外部流量。
常见陷阱和解决方案
陷阱1:端口与系统服务冲突
PostgreSQL、Redis 和其他服务通常作为系统服务运行。始终映射到开发中的非标准端口:
- PostgreSQL:
5433:5432(不是5432:5432) - Redis:保留
6379:6379(很少发生冲突) - 运行
lsof -i :5432检查什么正在使用默认端口
陷阱 2:Linux 上的卷权限问题
Linux 上的 Docker 卷默认使用 root 所有权。如果您的容器用户不是 root,请设置正确的所有权:
postgres:
image: postgres:17-alpine
user: "999:999" # postgres user's UID:GID
# Or use init container to fix permissions
陷阱 3:Authentik 初始化需要 60 秒以上
Authentik 在首次启动时运行数据库迁移。健康检查中的 start_period: 60s 为其提供了时间。如果依赖服务在 Authentik 准备就绪之前启动,它们将会失败。使用 service_healthy 条件并为其提供足够的 start_period。
陷阱 4:Mac 上的 Docker 桌面资源限制
默认 Docker Desktop 分配 2 个 CPU 和 2GB RAM — 不足以同时运行 PostgreSQL + Redis + Authentik。将 Docker 桌面设置 > 资源增加到至少 4 个 CPU 和 6GB RAM。
陷阱 5:docker-compose 与 docker compose
旧的 docker-compose (v1,用 Python 编写)已被弃用。使用 docker compose (v2,插件)。检查您的版本:docker compose version。如果您看到 Docker Compose version v2.x.x,则您正在使用 v2。
常见问题
开发期间我应该在 Docker 中运行应用程序服务(NestJS、Next.js)吗?
通常不需要 - 对于主动开发,请在主机上运行应用程序服务,以实现更快的热重载和更轻松的调试。仅将 Docker 用于稳定且不需要频繁重启的基础设施服务(数据库、缓存、身份提供商)。例外情况是,如果您的应用程序具有开发操作系统和生产环境之间不同的本机依赖项。
如何在 Docker Compose 工作流程中处理数据库迁移?
启动基础架构后从主机运行迁移:pnpm dev:infra && pnpm db:migrate。开发期间不要在 Docker 容器内运行迁移 - 您将失去使 Drizzle 迁移安全的类型检查和 IDE 集成。对于初始数据库创建,请使用 Docker 的 initdb.d 脚本。
如何备份和恢复本地 Docker 卷?
使用 docker run --rm -v postgres_data:/data -v $(pwd):/backup alpine tar czf /backup/postgres-backup.tar.gz /data 进行备份。使用 tar xzf 使用相同的方法进行恢复。对于开发,您还可以使用 pg_dump 转储并使用 psql 恢复,因为您已经暴露了端口。
如何与其他团队成员共享 Docker Compose 状态?
Docker Compose 文件通过 git 共享,但卷中的数据是本地的。每个开发人员都从一个空数据库开始,并运行迁移/种子来填充它。使用种子脚本(提交到存储库)创建一致的测试数据。共享的 docker-compose.dev.yml 确保每个人都使用相同的服务版本和配置。
为什么在开发中使用 Mailpit 而不是真正的电子邮件?
Mailpit 是一个本地 SMTP 服务器,它捕获所有外发电子邮件并提供 Web UI 来查看它们。它可以防止在开发过程中意外向真实用户发送真实电子邮件,不需要 SMTP 凭据,并且允许您验证电子邮件模板而无需检查收件箱。将您的应用程序配置为使用 SMTP_HOST=localhost SMTP_PORT=1025 并访问 http://localhost:8025 以查看捕获的电子邮件。
后续步骤
用于本地开发的精心设计的 Docker Compose 设置是一项投资,每次新开发人员加入或启动新机器时都会带来回报。 ECOSIRE 在 Docker Compose 中运行 PostgreSQL 17、Redis 7 和 Authentik,以便整个团队进行本地开发。
需要帮助设计您的本地开发基础设施或将您的应用程序容器化以用于生产? 探索我们的 DevOps 服务 以了解我们如何提供帮助。
作者
ECOSIRE Research and Development Team
在 ECOSIRE 构建企业级数字产品。分享关于 Odoo 集成、电商自动化和 AI 驱动商业解决方案的洞见。
相关文章
AWS EC2 Deployment Guide for Web Applications
Complete AWS EC2 deployment guide: instance selection, security groups, Node.js deployment, Nginx reverse proxy, SSL, auto-scaling, CloudWatch monitoring, and cost optimization.
Cloud vs On-Premise ERP in 2026: The Definitive Guide
Cloud vs on-premise ERP in 2026: total cost analysis, security comparison, scalability, compliance, and the right deployment model for your business.
Building Custom Odoo Modules: Developer Tutorial
Step-by-step tutorial for building custom Odoo 19 modules. Covers module structure, models, views, security, wizards, and best practices for production-ready code.