Implementación de Docker para ERP de producción: una guía de operaciones completa

Implementar sistemas ERP con Docker en producción. Cubre compilaciones de varias etapas, orquestación de Docker Compose, administración de volúmenes, redes y estrategias de escalado.

E
ECOSIRE Research and Development Team
|16 de marzo de 202610 min de lectura2.1k Palabras|

Docker para la implementación de ERP de producción: una guía de operaciones completa

Las organizaciones que ejecutan sistemas ERP en contenedores Docker reportan ciclos de implementación un 73 % más rápidos y un 45 % menos de incidentes relacionados con el entorno en comparación con las implementaciones tradicionales. Docker transforma la implementación de ERP de un proceso de varios días propenso a errores a una operación repetible y controlada por versiones que cualquier miembro del equipo puede ejecutar.

Esta guía cubre el ciclo de vida completo de la ejecución de sistemas ERP empresariales, incluidos Odoo, backends NestJS personalizados y frontends Next.js, en entornos Docker de producción.

Conclusiones clave

  • Las compilaciones de Docker en varias etapas reducen el tamaño de las imágenes de los contenedores ERP entre un 60 y un 80 %, lo que mejora la velocidad de implementación.
  • Docker Compose organiza servicios de ERP, base de datos, proxy inverso y caché como una única unidad implementable
  • Los volúmenes con nombre y los montajes vinculados garantizan la persistencia de los datos en los reinicios y actualizaciones del contenedor.
  • Las comprobaciones de estado y las políticas de reinicio proporcionan recuperación automática de fallas transitorias

Arquitectura de una pila ERP Dockerizada

Una implementación de ERP de producción normalmente implica cinco o más servicios interconectados. Docker Compose define estos servicios de forma declarativa, lo que garantiza una implementación coherente en todos los entornos.

Topología del servicio

La pila ERP Dockerizada estándar:

  1. Servidor de aplicaciones: el tiempo de ejecución del ERP (Odoo, NestJS o similar)
  2. Base de datos: PostgreSQL con almacenamiento de volumen persistente
  3. Proxy inverso: Nginx maneja la terminación SSL, archivos estáticos y enrutamiento de solicitudes
  4. Capa de caché: Redis para almacenamiento de sesiones, colas de trabajos y almacenamiento en caché de aplicaciones
  5. Trabajadores en segundo plano: procesadores de trabajos asíncronos para correos electrónicos, informes e integraciones

Los servicios opcionales incluyen contenedores de respaldo (pg_dump en cron), sidecars de monitoreo (exportadores de Prometheus) y transportistas de registros (Fluent Bit).


Construcciones de varias etapas para aplicaciones ERP

Las compilaciones de varias etapas son esenciales para la producción de imágenes de Docker. Separan las dependencias del tiempo de construcción del tiempo de ejecución, produciendo imágenes sencillas y seguras.

Compilación del backend de 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"]

Construcción de interfaz de 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"]

Comparación de tamaño de imagen

Tipo de construcciónTamaño de imagenTiempo de construcción
De una sola etapa (imagen de nodo completa)1,8 GB4 minutos
Monoetapa (Alpino)650MB3,5 minutos
Multietapa (Alpino)180 MB5 minutos
Multietapa + depósitos podados120 MB5,5 minutos

El tiempo de compilación de 5,5 minutos es aceptable porque ocurre en CI, no en máquinas de desarrollo.


Docker Compose para producción

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:

Aislamiento de red

La configuración anterior utiliza dos redes:

  • frontend: Nginx, web y API (Nginx representa ambos)
  • backend: API, base de datos y Redis

No se puede acceder a la base de datos y a Redis desde el contenedor Nginx o la red externa. Esta segmentación de la red es una práctica de seguridad crítica.


Gestión de volúmenes y persistencia de datos

Los volúmenes son la parte más crítica de una implementación de ERP Dockerizado. Pierde tus volúmenes y pierdes tus datos.

Tipos de volumen

TipoCaso de usoPersistenciaRendimiento
Volúmenes nombradosBase de datos, RedisSobrevive a la retirada del contenedorVelocidad del sistema de archivos nativo
Enlazar monturasArchivos de configuración, registrosVinculado al sistema de archivos hostVelocidad del sistema de archivos nativo
montajes tmpfsArchivos temporales, secretosSólo memoria, se pierde al reiniciarVelocidad de la memoria

Estrategia de copia de seguridad para volúmenes Docker

#!/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

Comprobaciones de estado y políticas de reinicio

Los contenedores de producción deben autoinformar su estado y recuperarse de fallas automáticamente.

Punto final de comprobación del estado de la aplicación

// 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;
    }
  }
}

Reiniciar selección de política

PolíticaComportamientoCaso de uso
CÓDIGO0Nunca reiniciarDesarrollo, tareas puntuales
CÓDIGO0Reiniciar solo al salir distinto de ceroTrabajadores, trabajos por lotes
CÓDIGO0Reiniciar siempre (incluso al reiniciar el demonio acoplable)Servicios de producción
CÓDIGO0Como always pero respeta las paradas manualesLa mayoría de los servicios de producción

Utilice unless-stopped para servicios de producción. Esto garantiza que los contenedores se reinicien después de que se reinicie el servidor o el demonio Docker, pero respeta los comandos manuales docker compose stop durante el mantenimiento.


Flujo de trabajo de implementación

Actualizaciones continuas con 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

Seguridad en la migración de bases de datos

Nunca ejecute migraciones dentro del inicio de la aplicación. En su lugar, ejecútelos como un paso independiente:

# Run migrations before deploying new containers
docker compose run --rm api npx drizzle-kit push

# Then deploy the new version
docker compose up -d

Este patrón garantiza que si falla una migración, la versión anterior continúa ejecutándose sin verse afectada.


Registro y depuración

Registro centralizado

# Add to docker-compose.yml
services:
  api:
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "5"
        labels: "service"
    labels:
      service: "ecosire-api"

Comandos de depuración comunes

# 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

Preguntas frecuentes

¿Cómo manejamos las migraciones de bases de datos en Docker?

Ejecute las migraciones como un paso independiente antes de implementar nuevos contenedores de aplicaciones. Utilice docker compose run --rm api npx drizzle-kit push (o el comando de migración de su ORM) como paso previo a la implementación. Nunca incorpore la ejecución de la migración en el comando de inicio del contenedor. Una migración fallida no debería impedir que la versión actual continúe ejecutándose.

¿Cuál es la sobrecarga de rendimiento de Docker?

En Linux, la sobrecarga de rendimiento de Docker es insignificante: normalmente menos del 2 % para cargas de trabajo vinculadas a la CPU y ninguna diferencia mensurable para cargas de trabajo vinculadas a E/S. En macOS y Windows, Docker se ejecuta dentro de una máquina virtual, lo que agrega entre un 5% y un 15% de sobrecarga. Para la producción (que debería ser Linux), Docker no representa un problema de rendimiento significativo.

¿Cómo gestionamos los secretos en Docker?

Nunca coloque secretos en archivos Dockerfiles o docker-compose.yml. Utilice archivos de variables de entorno (.env) excluidos del control de versiones, secretos de Docker (para el modo Swarm) o administradores de secretos externos (AWS Secrets Manager, HashiCorp Vault). Para Docker Compose, un archivo .env en la raíz del proyecto es el método más sencillo.

¿Deberíamos utilizar Docker Swarm o Kubernetes?

Para la mayoría de las implementaciones de ERP para PYMES, Docker Compose es suficiente. Docker Swarm agrega orquestación de múltiples hosts con una complejidad mínima. Kubernetes es apropiado cuando necesita escalamiento automático, políticas de red complejas o capacidades de malla de servicios. Consulte nuestra guía de escalado de Kubernetes y la guía de arquitectura de microservicios para conocer los marcos de decisión.

¿Cómo manejamos los módulos personalizados de Odoo en Docker?

Monte módulos personalizados como un volumen de montaje vinculado que apunte a su directorio de complementos. En Dockerfile, asegúrese de que la ruta de los complementos esté configurada en odoo.conf. Para CI/CD, cree una imagen de Docker personalizada que se integra en sus módulos, garantizando la coherencia de la versión. Consulte nuestra guía de implementación de Docker Odoo existente para conocer la configuración específica de Odoo.


¿Qué viene después?

Docker es la base para la implementación de ERP moderna. Una vez que su pila en contenedores esté estable, explore estrategias de implementación sin tiempo de inactividad, monitoreo de producción e infraestructura como código para crear un canal de operaciones totalmente automatizado.

Comuníquese con ECOSIRE para obtener consultoría sobre la implementación de Docker, o explore nuestros servicios de implementación de Odoo para una implementación de ERP en contenedores totalmente administrada.


Publicado por ECOSIRE: ayuda a las empresas a implementar software empresarial con confianza.

E

Escrito por

ECOSIRE Research and Development Team

Construyendo productos digitales de nivel empresarial en ECOSIRE. Compartiendo perspectivas sobre integraciones Odoo, automatización de eCommerce y soluciones empresariales impulsadas por IA.

Chatea en whatsapp