用于生产 ERP 部署的 Docker:完整操作指南
在 Docker 容器中运行 ERP 系统的组织报告称,与传统的裸机部署相比,部署周期缩短了 73%,与环境相关的事件减少了 45%。 Docker 将 ERP 部署从需要多天、容易出错的流程转变为任何团队成员都可以执行的可重复、版本控制的操作。
本指南涵盖了在生产 Docker 环境中运行企业 ERP 系统的整个生命周期(包括 Odoo、自定义 NestJS 后端和 Next.js 前端)。
要点
- 多阶段 Docker 构建将 ERP 容器镜像大小减少 60-80%,提高部署速度
- Docker Compose 将 ERP、数据库、反向代理和缓存服务编排为单个可部署单元
- 命名卷和绑定挂载确保容器重启和升级期间的数据持久性
- 健康检查和重启策略提供从瞬时故障中自动恢复
Docker 化 ERP 堆栈的架构
生产 ERP 部署通常涉及五个或更多互连服务。 Docker Compose 以声明方式定义这些服务,确保跨环境的部署一致。
服务拓扑
标准 Docker 化 ERP 堆栈:
- 应用程序服务器:ERP运行时(Odoo、NestJS或类似)
- 数据库:具有持久卷存储的 PostgreSQL
- 反向代理:Nginx处理SSL终止、静态文件和请求路由
- 缓存层:Redis用于会话存储、作业队列和应用程序缓存
- 后台工作人员:用于电子邮件、报告和集成的异步作业处理器
可选服务包括备份容器(cron 上的 pg_dump)、监控 sidecar(Prometheus 导出器)和日志传送器(Fluent Bit)。
ERP 应用程序的多阶段构建
多阶段构建对于生产 Docker 镜像至关重要。它们将构建时依赖性与运行时分离,生成精益、安全的映像。
NestJS 后端构建
# Stage 1: Install dependencies and build
FROM node:20-alpine AS builder
WORKDIR /app
# Install pnpm
RUN corepack enable
# Copy workspace configuration
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY packages/ ./packages/
COPY apps/api/package.json ./apps/api/
# Install dependencies
RUN pnpm install --frozen-lockfile
# Copy source and build
COPY apps/api/ ./apps/api/
RUN pnpm --filter @ecosire/db build
RUN pnpm --filter @ecosire/types build
RUN pnpm --filter @ecosire/validators build
RUN pnpm --filter @ecosire/api build
# Stage 2: Production runtime
FROM node:20-alpine AS runner
WORKDIR /app
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
COPY --from=builder --chown=appuser:appgroup /app/apps/api/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/apps/api/package.json ./
USER appuser
EXPOSE 3001
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1
CMD ["node", "dist/main.js"]
Next.js 前端构建
FROM node:20-alpine AS builder
WORKDIR /app
RUN corepack enable
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY packages/ ./packages/
COPY apps/web/package.json ./apps/web/
RUN pnpm install --frozen-lockfile
COPY apps/web/ ./apps/web/
RUN pnpm --filter @ecosire/web build
FROM node:20-alpine AS runner
WORKDIR /app
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
COPY --from=builder --chown=appuser:appgroup /app/apps/web/.next/standalone ./
COPY --from=builder --chown=appuser:appgroup /app/apps/web/.next/static ./.next/static
COPY --from=builder --chown=appuser:appgroup /app/apps/web/public ./public
USER appuser
EXPOSE 3000
ENV NODE_ENV=production
CMD ["node", "server.js"]
图像大小比较
| 构建类型 | 图像尺寸 | 构建时间 |
|---|---|---|
| 单阶段(全节点镜像) | 1.8 GB | 1.8 GB 4 分钟 |
| 单级(阿尔卑斯) | 650 MB | 3.5 分钟 |
| 多级(高山) | 180 MB | 5 分钟 |
| 多阶段 + 精简依赖 | 120 MB | 5.5 分钟 |
5.5 分钟的构建时间是可以接受的,因为它发生在 CI 中,而不是在开发人员机器上。
用于生产的 Docker Compose
version: "3.8"
services:
api:
build:
context: .
dockerfile: apps/api/Dockerfile
environment:
- DATABASE_URL=postgresql://app:${DB_PASSWORD}@db:5432/ecosire
- REDIS_URL=redis://redis:6379
- NODE_ENV=production
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
networks:
- backend
- frontend
web:
build:
context: .
dockerfile: apps/web/Dockerfile
environment:
- API_URL=http://api:3001
- NODE_ENV=production
depends_on:
- api
restart: unless-stopped
networks:
- frontend
db:
image: postgres:17-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ecosire
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d ecosire"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- backend
redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD} --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
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./infrastructure/nginx/production.conf:/etc/nginx/conf.d/default.conf:ro
- ./certbot/conf:/etc/letsencrypt:ro
- ./certbot/www:/var/www/certbot:ro
depends_on:
- web
- api
restart: unless-stopped
networks:
- frontend
volumes:
postgres-data:
redis-data:
networks:
frontend:
backend:
网络隔离
上面的配置使用两个网络:
- 前端:Nginx、Web 和 API(两者的 Nginx 代理)
- 后端:API、数据库和 Redis
无法从 Nginx 容器或外部网络访问数据库和 Redis。这种网络分段是一项关键的安全实践。
卷管理和数据持久化
卷是 Docker 化 ERP 部署中最关键的部分。丢失卷就丢失数据。
卷类型
| 类型 | 使用案例 | 坚持 | 性能 |
|---|---|---|---|
| 命名卷 | 数据库、Redis | 容器移除后仍然存在 | 本机文件系统速度 |
| 绑定坐骑 | 配置文件、日志 | 与主机文件系统绑定 | 本机文件系统速度 |
| tmpfs 安装 | 临时文件、机密 | 仅内存,重启后丢失 | 内存速度 |
Docker Volume 的备份策略
#!/bin/bash
# backup-volumes.sh - Run via cron every 6 hours
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/opt/backups"
# Stop the application briefly for consistent backup
docker compose stop api web
# Backup PostgreSQL
docker compose exec -T db pg_dump -U app ecosire | gzip > "$BACKUP_DIR/db_$TIMESTAMP.sql.gz"
# Backup Redis
docker compose exec -T redis redis-cli -a "$REDIS_PASSWORD" BGSAVE
sleep 5
docker cp $(docker compose ps -q redis):/data/dump.rdb "$BACKUP_DIR/redis_$TIMESTAMP.rdb"
# Restart services
docker compose start api web
# Upload to S3
aws s3 sync "$BACKUP_DIR" "s3://company-backups/docker-volumes/" --exclude "*.tmp"
# Retain 30 days locally
find "$BACKUP_DIR" -name "*.gz" -mtime +30 -delete
find "$BACKUP_DIR" -name "*.rdb" -mtime +30 -delete
健康检查和重启策略
生产容器必须自我报告其运行状况并自动从故障中恢复。
应用程序健康检查端点
// health.controller.ts
@Controller('health')
export class HealthController {
constructor(
private readonly db: DatabaseService,
private readonly redis: RedisService,
) {}
@Get()
@Public()
async check() {
const checks = {
database: await this.checkDatabase(),
redis: await this.checkRedis(),
uptime: process.uptime(),
memory: process.memoryUsage(),
};
const healthy = checks.database && checks.redis;
return { status: healthy ? 'ok' : 'degraded', checks };
}
private async checkDatabase(): Promise<boolean> {
try {
await this.db.execute('SELECT 1');
return true;
} catch {
return false;
}
}
private async checkRedis(): Promise<boolean> {
try {
await this.redis.ping();
return true;
} catch {
return false;
}
}
}
重启策略选择
| 政策 | 行为 | 使用案例 |
|---|---|---|
| 代码0 | 永不重启 | 开发、一次性任务 |
| 代码0 | 仅在非零退出时重新启动 | 工人,批量作业 |
| 代码0 | 始终重新启动(包括 docker 守护进程重新启动) | 生产服务 |
| 代码0 | 与 always 类似,但尊重手动停止 | 大多数制作服务 |
使用 unless-stopped 进行生产服务。这可确保容器在服务器重新启动或 Docker 守护程序重新启动后重新启动,但在维护期间遵循手动 docker compose stop 命令。
部署工作流程
使用 Docker Compose 滚动更新
#!/bin/bash
# deploy.sh - Zero-downtime deployment
set -e
echo "Pulling latest code..."
git pull origin main
echo "Building new images..."
docker compose build --no-cache api web
echo "Rolling update - API first..."
docker compose up -d --no-deps api
sleep 10
# Verify API health
if ! curl -sf http://localhost:3001/health > /dev/null; then
echo "API health check failed, rolling back..."
docker compose up -d --no-deps api
exit 1
fi
echo "Rolling update - Web..."
docker compose up -d --no-deps web
sleep 5
# Verify Web health
if ! curl -sf http://localhost:3000 > /dev/null; then
echo "Web health check failed, rolling back..."
docker compose up -d --no-deps web
exit 1
fi
echo "Deployment complete!"
docker compose ps
数据库迁移安全
切勿在应用程序启动时运行迁移。相反,将它们作为单独的步骤运行:
# Run migrations before deploying new containers
docker compose run --rm api npx drizzle-kit push
# Then deploy the new version
docker compose up -d
此模式可确保如果迁移失败,旧版本将继续运行而不受影响。
日志记录和调试
集中日志记录
# Add to docker-compose.yml
services:
api:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
labels: "service"
labels:
service: "ecosire-api"
常用调试命令
# View logs for a specific service
docker compose logs -f api --tail 100
# Execute a shell inside a running container
docker compose exec api sh
# View resource usage
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}"
# Inspect container networking
docker compose exec api ping db
# View container environment variables
docker compose exec api env | sort
常见问题
我们如何在 Docker 中处理数据库迁移?
在部署新的应用程序容器之前,将迁移作为单独的步骤运行。使用 docker compose run --rm api npx drizzle-kit push (或 ORM 的迁移命令)作为预部署步骤。切勿在容器启动命令中嵌入迁移执行——失败的迁移不应阻止当前版本继续运行。
Docker 的性能开销是多少?
在 Linux 上,Docker 的性能开销可以忽略不计——对于 CPU 密集型工作负载来说通常低于 2%,而对于 I/O 密集型工作负载则没有可测量的差异。在 macOS 和 Windows 上,Docker 在虚拟机内运行,增加了 5-15% 的开销。对于生产(应该是 Linux),Docker 并不是一个有意义的性能问题。
我们如何在 Docker 中管理机密?
切勿将机密放入 Dockerfile 或 docker-compose.yml 文件中。使用从版本控制、Docker 密钥(对于 Swarm 模式)或外部密钥管理器(AWS Secrets Manager、HashiCorp Vault)中排除的环境变量文件 (.env)。对于 Docker Compose,项目根目录下的 .env 文件是最简单的方法。
我们应该使用 Docker Swarm 还是 Kubernetes?
对于大多数中小型企业 ERP 部署,Docker Compose 就足够了。 Docker Swarm 以最小的复杂性开销添加了多主机编排。当您需要自动扩展、复杂的网络策略或服务网格功能时,Kubernetes 是合适的选择。请参阅我们的 Kubernetes 扩展指南 和 微服务架构指南 了解决策框架。
我们如何在 Docker 中处理 Odoo 自定义模块?
将自定义模块安装为指向您的插件目录的绑定安装卷。在 Dockerfile 中,确保在 odoo.conf 中配置插件路径。对于 CI/CD,构建一个自定义 Docker 镜像来烘焙您的模块,确保版本一致性。请参阅我们现有的 Docker Odoo 部署指南 了解 Odoo 特定的配置。
接下来会发生什么
Docker is the foundation for modern ERP deployment.一旦您的容器化堆栈稳定,请探索零停机部署策略、生产监控和基础设施即代码来构建完全自动化的操作管道。
联系 ECOSIRE 获取 Docker 部署咨询,或探索我们的 Odoo 实施服务 进行完全托管的容器化 ERP 部署。
由 ECOSIRE 发布——帮助企业充满信心地部署企业软件。
作者
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
相关文章
Odoo 与 NetSuite 中端市场比较:2026 年完整买家指南
2026 年中端市场的 Odoo 与 NetSuite:逐个功能评分、50 个用户的 5 年 TCO、实施时间表、行业适合度和双向迁移指南。
后市场整合:将翻新产品连接到 Odoo ERP
为翻新电子产品卖家提供将 Back Market 与 Odoo ERP 集成的指南。自动执行分级、订单、库存和质量合规性。
2026 年电子商务业务最佳 ERP:前 8 名比较
比较 2026 年排名前 8 的电子商务 ERP:Odoo、NetSuite、SAP B1、Acumatica、Brightpearl、Cin7、Dear Inventory 和 QuickBooks Commerce 的定价。