用于生产 ERP 部署的 Docker:完整操作指南

在生产中使用 Docker 部署 ERP 系统。涵盖多阶段构建、Docker Compose 编排、卷管理、网络和扩展策略。

E
ECOSIRE Research and Development Team
|2026年3月16日6 分钟阅读1.2k 字数|

用于生产 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 堆栈:

  1. 应用程序服务器:ERP运行时(Odoo、NestJS或类似)
  2. 数据库:具有持久卷存储的 PostgreSQL
  3. 反向代理:Nginx处理SSL终止、静态文件和请求路由
  4. 缓存层:Redis用于会话存储、作业队列和应用程序缓存
  5. 后台工作人员:用于电子邮件、报告和集成的异步作业处理器

可选服务包括备份容器(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 GB1.8 GB 4 分钟
单级(阿尔卑斯)650 MB3.5 分钟
多级(高山)180 MB5 分钟
多阶段 + 精简依赖120 MB5.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 守护进程重新启动)生产服务
代码0always 类似,但尊重手动停止大多数制作服务

使用 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 发布——帮助企业充满信心地部署企业软件。

E

作者

ECOSIRE Research and Development Team

在 ECOSIRE 构建企业级数字产品。分享关于 Odoo 集成、电商自动化和 AI 驱动商业解决方案的洞见。

通过 WhatsApp 聊天