Running n8n in production requires reliable deployment, scaling, and maintenance strategies. Docker containers provide the ideal platform for deploying n8n with consistency across environments, easy scaling, and simplified operations. This guide covers Docker deployment patterns for n8n, from basic single-container setups to production-grade multi-container architectures with external databases, reverse proxies, and monitoring solutions.

Why Docker for n8n Production Deployments?

Docker offers several advantages for n8n production deployments:

  • Consistency across environments — Same container runs in dev, staging, production
  • Isolation and security — n8n runs in isolated container with limited host access
  • Easy scaling — Scale containers horizontally with orchestration tools
  • Simplified updates — Update n8n version by pulling new container image
  • Resource efficiency — Share host kernel, lower overhead than VMs
  • Portability — Run on any Docker-compatible platform (cloud, on-prem, local)

Basic n8n Docker Deployment

Simple Docker Run Command

# Basic n8n Docker run command
docker run -d \
  --name n8n \
  -p 5678:5678 \
  -v n8n_data:/home/node/.n8n \
  -e N8N_BASIC_AUTH_ACTIVE=true \
  -e N8N_BASIC_AUTH_USER=admin \
  -e N8N_BASIC_AUTH_PASSWORD=securepassword \
  n8nio/n8n

Docker Compose for Development

# docker-compose.yml for n8n with PostgreSQL
version: '3.8'

services:
  n8n:
    image: n8nio/n8n
    container_name: n8n
    restart: unless-stopped
    ports:
      - "5678:5678"
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=n8n_password
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=admin
      - N8N_BASIC_AUTH_PASSWORD=securepassword
      - N8N_HOST=localhost
      - N8N_PORT=5678
      - N8N_PROTOCOL=http
      - WEBHOOK_URL=http://localhost:5678/
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      - postgres
    networks:
      - n8n_network

  postgres:
    image: postgres:15
    container_name: n8n_postgres
    restart: unless-stopped
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=n8n_password
      - POSTGRES_DB=n8n
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - n8n_network

volumes:
  n8n_data:
  postgres_data:

networks:
  n8n_network:
    driver: bridge

Production n8n Docker Architecture

Multi-Container Production Setup

# Production docker-compose.yml with Traefik, Redis, and monitoring
version: '3.8'

services:
  traefik:
    image: traefik:v3.0
    container_name: traefik
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./letsencrypt:/letsencrypt"
    networks:
      - n8n_network

  n8n:
    image: n8nio/n8n
    container_name: n8n
    restart: unless-stopped
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=${N8N_USER}
      - N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
      - N8N_HOST=n8n.example.com
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://n8n.example.com/
      - EXECUTIONS_DATA_PRUNE=true
      - EXECUTIONS_DATA_MAX_AGE=168
      - QUEUE_BULL_REDIS_HOST=redis
      - QUEUE_BULL_REDIS_PORT=6379
      - EXECUTIONS_MODE=queue
    volumes:
      - n8n_data:/home/node/.n8n
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.n8n.rule=Host(`n8n.example.com`)"
      - "traefik.http.routers.n8n.entrypoints=websecure"
      - "traefik.http.routers.n8n.tls.certresolver=letsencrypt"
      - "traefik.http.services.n8n.loadbalancer.server.port=5678"
    depends_on:
      - postgres
      - redis
    networks:
      - n8n_network

  postgres:
    image: postgres:15
    container_name: n8n_postgres
    restart: unless-stopped
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=n8n
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./backup:/backup
    command: >
      postgres -c max_connections=200
               -c shared_buffers=256MB
               -c effective_cache_size=1GB
    networks:
      - n8n_network

  redis:
    image: redis:7-alpine
    container_name: n8n_redis
    restart: unless-stopped
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data
    networks:
      - n8n_network

  pgadmin:
    image: dpage/pgadmin4
    container_name: pgadmin
    restart: unless-stopped
    environment:
      - PGADMIN_DEFAULT_EMAIL=admin@example.com
      - PGADMIN_DEFAULT_PASSWORD=${PGADMIN_PASSWORD}
    volumes:
      - pgadmin_data:/var/lib/pgadmin
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.pgadmin.rule=Host(`pgadmin.example.com`)"
      - "traefik.http.routers.pgadmin.entrypoints=websecure"
      - "traefik.http.routers.pgadmin.tls.certresolver=letsencrypt"
      - "traefik.http.services.pgadmin.loadbalancer.server.port=80"
    networks:
      - n8n_network

volumes:
  n8n_data:
  postgres_data:
  redis_data:
  pgadmin_data:

networks:
  n8n_network:
    driver: bridge

n8n Docker Configuration Best Practices

Environment Variables Configuration

# .env file for n8n Docker deployment
# Database Configuration
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n
DB_POSTGRESDB_PASSWORD=secure_db_password_here

# Authentication
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=secure_n8n_password_here

# Server Configuration
N8N_HOST=n8n.yourdomain.com
N8N_PORT=5678
N8N_PROTOCOL=https
WEBHOOK_URL=https://n8n.yourdomain.com/

# Execution Settings
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168  # Keep execution data for 7 days
EXECUTIONS_DATA_PRUNE_MAX_COUNT=10000

# Queue Mode (for scaling)
EXECUTIONS_MODE=queue
QUEUE_BULL_REDIS_HOST=redis
QUEUE_BULL_REDIS_PORT=6379
QUEUE_BULL_REDIS_PASSWORD=secure_redis_password_here

# Security
N8N_ENCRYPTION_KEY=your-32-character-encryption-key-here
N8N_DIAGNOSTICS_ENABLED=false

# External hooks
EXTERNAL_HOOK_FILES=/home/node/.n8n/external-hooks.js

Volume Management for Data Persistence

# Volume configuration for data persistence
docker volume create n8n_data
docker volume create postgres_data
docker volume create redis_data

# Backup script for n8n data
#!/bin/bash
# backup-n8n.sh
BACKUP_DIR="/backup/n8n"
DATE=$(date +%Y%m%d_%H%M%S)

# Backup PostgreSQL database
docker exec n8n_postgres pg_dump -U n8n n8n > ${BACKUP_DIR}/n8n_db_${DATE}.sql

# Backup n8n configuration and workflows
docker run --rm --volumes-from n8n -v ${BACKUP_DIR}:/backup alpine \
  tar czf /backup/n8n_data_${DATE}.tar.gz /home/node/.n8n

# Backup Redis data
docker exec n8n_redis redis-cli --rdb /data/dump.rdb
docker cp n8n_redis:/data/dump.rdb ${BACKUP_DIR}/redis_${DATE}.rdb

# Rotate old backups (keep 30 days)
find ${BACKUP_DIR} -name "*.sql" -mtime +30 -delete
find ${BACKUP_DIR} -name "*.tar.gz" -mtime +30 -delete
find ${BACKUP_DIR} -name "*.rdb" -mtime +30 -delete

Scaling n8n with Docker

Queue Mode for Horizontal Scaling

# docker-compose.scale.yml for multiple n8n workers
version: '3.8'

services:
  n8n_web:
    image: n8nio/n8n
    container_name: n8n_web
    restart: unless-stopped
    environment:
      - EXECUTIONS_MODE=queue
      - N8N_PROTOCOL=https
      - N8N_HOST=n8n.example.com
      - WEBHOOK_URL=https://n8n.example.com/
      # ... other environment variables
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.n8n.rule=Host(`n8n.example.com`)"
      - "traefik.http.services.n8n.loadbalancer.server.port=5678"
    networks:
      - n8n_network

  n8n_worker_1:
    image: n8nio/n8n
    container_name: n8n_worker_1
    restart: unless-stopped
    environment:
      - EXECUTIONS_MODE=queue
      - N8N_PROTOCOL=https
      - N8N_HOST=n8n.example.com
      - WEBHOOK_URL=https://n8n.example.com/
      # ... other environment variables
    command: n8n worker
    networks:
      - n8n_network

  n8n_worker_2:
    image: n8nio/n8n
    container_name: n8n_worker_2
    restart: unless-stopped
    environment:
      - EXECUTIONS_MODE=queue
      - N8N_PROTOCOL=https
      - N8N_HOST=n8n.example.com
      - WEBHOOK_URL=https://n8n.example.com/
      # ... other environment variables
    command: n8n worker
    networks:
      - n8n_network

  # Add more workers as needed
  n8n_worker_3:
    image: n8nio/n8n
    container_name: n8n_worker_3
    restart: unless-stopped
    environment:
      - EXECUTIONS_MODE=queue
      - N8N_PROTOCOL=https
      - N8N_HOST=n8n.example.com
      - WEBHOOK_URL=https://n8n.example.com/
      # ... other environment variables
    command: n8n worker
    networks:
      - n8n_network

Monitoring and Maintenance

Docker Health Checks

# Health check configuration for n8n
services:
  n8n:
    image: n8nio/n8n
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5678/healthz"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    # ... other configuration

Logging Configuration

# Docker logging configuration
services:
  n8n:
    image: n8nio/n8n
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
        tag: "n8n"
    # ... other configuration

# View logs
docker logs n8n --tail 100 --follow

# Log aggregation with Loki
docker run --name loki -d -p 3100:3100 grafana/loki

Security Considerations

Docker Security Best Practices

  • Non-root user — Run n8n as non-root user (n8n image does this by default)
  • Read-only root filesystem — Mount volumes for writable directories
  • Resource limits — Set CPU and memory limits
  • Network segmentation — Use Docker networks to isolate services
  • Regular updates — Keep Docker, n8n, and base images updated

Getting Started with n8n Docker Deployment

  1. Install Docker and Docker Compose — On your production server
  2. Create project directory — Organize configuration files
  3. Set up environment variables — Create .env file with secure values
  4. Configure docker-compose.yml — Start with basic setup, then add production features
  5. Initialize volumes — Create Docker volumes for data persistence
  6. Start services — docker-compose up -d
  7. Configure reverse proxy — Set up Traefik or Nginx for HTTPS
  8. Implement monitoring — Set up health checks and logging
  9. Create backup strategy — Regular backups of database and n8n data
  10. Test failover — Ensure services restart properly

Docker provides the ideal platform for running n8n in production, offering consistency, scalability, and operational simplicity. By following container best practices and implementing proper architecture patterns, you can deploy n8n with confidence, knowing that your workflow automation platform is reliable, secure, and maintainable.