Table of Contents
Overview
n8n is a workflow automation tool useful in AI Engineering for orchestrating LLM calls, data pipelines, webhooks, and scheduled tasks. This guide shows how to self-host n8n reliably, from a minimal single-container setup to a production-ready stack with PostgreSQL, Redis, and workers.
Collection: Self-Hosting AI Models & Tools
Quickstart (Minimal, Single Container)
This gets you running locally or on a single VPS fast. It uses SQLite (embedded) and is ideal for testing or small personal setups.
Minimal working example
docker-compose.yml
version: "3.8"
services:
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    ports:
      - "5678:5678"
    environment:
      - NODE_ENV=production
      - GENERIC_TIMEZONE=UTC
      - N8N_PORT=5678
      - N8N_PROTOCOL=http
      - N8N_HOST=localhost
      - WEBHOOK_URL=http://localhost:5678/
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=admin
      - N8N_BASIC_AUTH_PASSWORD=change-me
      - N8N_ENCRYPTION_KEY=change-this-to-a-long-random-string
    volumes:
      - n8n_data:/home/node/.n8n
    restart: unless-stopped
volumes:
  n8n_data:
Steps:
- Save the file as docker-compose.yml.
 - Start: docker compose up -d
 - Open http://localhost:5678, log in with admin / change-me, and complete the initial setup.
 
Notes:
- This uses SQLite stored under the n8n_data volume.
 - Use strong credentials and a strong 32+ character N8N_ENCRYPTION_KEY.
 
A tiny “Hello n8n” workflow (importable)
Import this JSON into n8n (Menu → Import from File) and click Execute to see a message.
{
  "name": "Hello n8n",
  "nodes": [
    {
      "parameters": {},
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [240, 300]
    },
    {
      "parameters": {
        "functionCode": "return [{ json: { message: 'Hello from self-hosted n8n' } }];"
      },
      "name": "Function",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [480, 300]
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          { "node": "Function", "type": "main", "index": 0 }
        ]
      ]
    }
  }
}
Production Setup (PostgreSQL + Redis + Workers)
For multi-user, higher reliability, and scaling concurrent executions, use Postgres for the database and Redis for queue-based execution.
docker-compose.yml
version: "3.8"
services:
  postgres:
    image: postgres:15-alpine
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=change-me
      - POSTGRES_DB=n8n
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped
  redis:
    image: redis:7-alpine
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redis_data:/data
    restart: unless-stopped
  n8n-web:
    image: n8nio/n8n:latest
    depends_on:
      - postgres
      - redis
    environment:
      - NODE_ENV=production
      - GENERIC_TIMEZONE=UTC
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - N8N_HOST=automation.example.com
      - WEBHOOK_URL=https://automation.example.com/
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=admin
      - N8N_BASIC_AUTH_PASSWORD=change-me
      - N8N_ENCRYPTION_KEY=change-this-to-a-long-random-string
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=change-me
      - EXECUTIONS_MODE=queue
      - QUEUE_BULL_REDIS_HOST=redis
      - QUEUE_BULL_REDIS_PORT=6379
    ports:
      - "5678:5678"
    volumes:
      - n8n_data:/home/node/.n8n
    restart: unless-stopped
  n8n-worker:
    image: n8nio/n8n:latest
    depends_on:
      - postgres
      - redis
    environment:
      - NODE_ENV=production
      - GENERIC_TIMEZONE=UTC
      - N8N_ENCRYPTION_KEY=change-this-to-a-long-random-string
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=change-me
      - EXECUTIONS_MODE=queue
      - QUEUE_BULL_REDIS_HOST=redis
      - QUEUE_BULL_REDIS_PORT=6379
      - N8N_ENABLE_EDITOR_UI=false
    command: n8n worker
    restart: unless-stopped
volumes:
  n8n_data:
  postgres_data:
  redis_data:
Numbered steps to launch:
- Set a DNS A/AAAA record for automation.example.com to your server.
 - Replace credentials, N8N_ENCRYPTION_KEY, and domain in the compose file.
 - Start: docker compose up -d
 - Put n8n behind your HTTPS reverse proxy and ensure it forwards Host and X-Forwarded-* headers.
 - Open https://automation.example.com and finish setup.
 
Key environment variables (essentials)
- N8N_HOST: Public hostname (no scheme).
 - N8N_PORT/N8N_PROTOCOL: Port and scheme n8n listens on.
 - WEBHOOK_URL: Public base URL used in webhooks (must match your reverse proxy URL).
 - N8N_ENCRYPTION_KEY: Strong secret for credential encryption.
 - DB_TYPE: postgresdb for production; default is SQLite if omitted.
 - EXECUTIONS_MODE: queue for horizontal scaling with workers.
 - QUEUE_BULL_REDIS_HOST/PORT: Redis connection for queue mode.
 
Operating tips
- Upgrades: docker compose pull && docker compose up -d (always back up first).
 - Backups: dump Postgres regularly and snapshot the n8n_data volume.
 - Logs: docker compose logs -f n8n-web n8n-worker
 - Scale workers: docker compose up -d --scale n8n-worker=3
 
Performance notes
- Prefer Postgres over SQLite for reliability and concurrency.
 - Use EXECUTIONS_MODE=queue with Redis to run workflows on separate workers and scale horizontally.
 - For heavy tasks, run multiple workers and tune CPU/RAM accordingly; monitor DB and Redis.
 - Consider isolating executions into their own processes (set EXECUTIONS_PROCESS=own) if workflows are memory-heavy; this reduces impact on the main UI/webhook process.
 - Reduce logging in production (e.g., set N8N_LOG_LEVEL=warn) to lower I/O overhead.
 - Place n8n near your AI/LLM endpoints (same region) to reduce latency.
 
Common pitfalls
- Missing WEBHOOK_URL or mismatched protocol/host breaks external triggers.
 - Using SQLite with multiple replicas or workers risks corruption—use Postgres.
 - No persistent volumes means losing credentials and workflows on container removal.
 - Weak or reused N8N_ENCRYPTION_KEY compromises stored credentials; rotate safely.
 - Reverse proxies that buffer or alter headers can delay or break webhooks; forward Host and X-Forwarded-Proto.
 - Large uploads may be blocked by your proxy; raise its request size limit if needed.
 
Minimal security checklist
- Enable basic auth (already shown) and use strong passwords.
 - Restrict access to the admin UI to trusted networks if possible.
 - Keep Docker and images updated; patch Postgres and Redis.
 - Use HTTPS end-to-end; ensure reverse proxy enforces TLS.
 
Tiny FAQ
- Can I use an external Postgres/Redis? Yes—point DB_POSTGRESDB_HOST and QUEUE_BULL_REDIS_HOST to those services.
 - Do I need Redis if I’m not scaling? No. For single-instance setups, Redis and queue mode are optional.
 - Will upgrades cause downtime? A brief restart occurs. Use multiple workers and a reverse proxy for minimal disruption.
 - Can n8n run offline? Yes, but nodes that call external APIs (e.g., LLMs) require internet access.
 - How do I change the port? Update N8N_PORT and the compose port mapping.
 
Next steps
- Add authentication for webhooks where appropriate.
 - Create environment-specific credentials and use separate databases for staging/production.
 - Monitor with container metrics and Postgres/Redis health checks to anticipate bottlenecks.