DEV Community

Pirate Prentice
Pirate Prentice

Posted on

n8n Docker Setup: Self-Host n8n in 10 Minutes (2026)

n8n Docker Setup: Self-Host n8n in 10 Minutes (2026)

Tags: n8n, docker, selfhosted, automation


Running n8n locally is great for testing, but for production you want a persistent, self-hosted instance that runs 24/7. Docker is the fastest path. This guide gets you from zero to a running n8n instance with persistent storage and basic security in under 10 minutes.


What You Need

  • A server (VPS, cloud instance, or home server) running Linux
  • Docker and Docker Compose installed
  • A domain name (optional but recommended for HTTPS)
  • 1 GB RAM minimum; 2 GB recommended

For most use cases, a $6/mo DigitalOcean Droplet or $4/mo Hetzner CX11 is plenty.


Quick Start: Single Container

The fastest way to spin up n8n:

docker run -it --rm \
  --name n8n \
  -p 5678:5678 \
  -v ~/.n8n:/home/node/.n8n \
  docker.n8n.io/n8nio/n8n
Enter fullscreen mode Exit fullscreen mode

This runs n8n at http://localhost:5678 with your data persisted to ~/.n8n. Fine for local testing — but not for production because it stops when you close the terminal.


Production Setup: Docker Compose

For a persistent production instance, use Docker Compose with a tunnel or reverse proxy for external access.

Option A: n8n + ngrok (quickest external access)

# docker-compose.yml
version: '3.8'
services:
  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: always
    ports:
      - "5678:5678"
    environment:
      - N8N_HOST=${SUBDOMAIN}.ngrok-free.app
      - WEBHOOK_URL=https://${SUBDOMAIN}.ngrok-free.app/
      - N8N_PROTOCOL=https
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - GENERIC_TIMEZONE=America/Chicago
    volumes:
      - n8n_data:/home/node/.n8n

volumes:
  n8n_data:
Enter fullscreen mode Exit fullscreen mode

Option B: n8n + Traefik + Let's Encrypt (recommended for production)

# docker-compose.yml
version: '3.8'
services:
  traefik:
    image: traefik:v3
    restart: always
    command:
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.email=${SSL_EMAIL}"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik_data:/letsencrypt

  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: always
    environment:
      - N8N_HOST=${DOMAIN}
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://${DOMAIN}/
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - GENERIC_TIMEZONE=America/Chicago
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=${N8N_USER}
      - N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.n8n.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.n8n.entrypoints=websecure"
      - "traefik.http.routers.n8n.tls.certresolver=letsencrypt"
    volumes:
      - n8n_data:/home/node/.n8n

volumes:
  n8n_data:
  traefik_data:
Enter fullscreen mode Exit fullscreen mode

Create your .env file:

DOMAIN=n8n.yourdomain.com
SSL_EMAIL=you@yourdomain.com
N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)
N8N_USER=admin
N8N_PASSWORD=your-strong-password
Enter fullscreen mode Exit fullscreen mode

Start everything:

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

n8n will be live at https://n8n.yourdomain.com with a valid SSL cert within 60 seconds.


Adding a Database (SQLite → PostgreSQL)

By default n8n stores everything in SQLite — fine for low volume. For production with concurrent workflows or high execution counts, switch to PostgreSQL:

  postgres:
    image: postgres:16
    restart: always
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=n8n
    volumes:
      - postgres_data:/var/lib/postgresql/data

  n8n:
    # ... same as above, add:
    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}
    depends_on:
      - postgres
Enter fullscreen mode Exit fullscreen mode

Enabling Queue Mode (for high-volume workflows)

Queue mode separates the main n8n process from worker processes, allowing parallel workflow execution:

  n8n-worker:
    image: docker.n8n.io/n8nio/n8n
    restart: always
    command: worker
    environment:
      - EXECUTIONS_MODE=queue
      - QUEUE_BULL_REDIS_HOST=redis
      # ... same DB config as main n8n
    depends_on:
      - redis
      - n8n

  redis:
    image: redis:7
    restart: always
    volumes:
      - redis_data:/data
Enter fullscreen mode Exit fullscreen mode

Updating n8n

docker compose pull
docker compose up -d
Enter fullscreen mode Exit fullscreen mode

Docker Compose will pull the latest image and restart the container with zero downtime (with Traefik handling the proxy).


Backup Strategy

Your n8n data is in the n8n_data volume. Back it up:

# Backup
docker run --rm -v n8n_data:/data -v $(pwd):/backup alpine \
  tar czf /backup/n8n-backup-$(date +%Y%m%d).tar.gz /data

# Restore
docker run --rm -v n8n_data:/data -v $(pwd):/backup alpine \
  tar xzf /backup/n8n-backup-20260717.tar.gz -C /
Enter fullscreen mode Exit fullscreen mode

For PostgreSQL, use pg_dump:

docker exec n8n-postgres-1 pg_dump -U n8n n8n > n8n-db-backup.sql
Enter fullscreen mode Exit fullscreen mode

Automate backups via n8n itself with a Schedule Trigger → Execute Command node running the backup script → Upload to S3.


Common Gotchas

Webhook URLs not working externally: Set WEBHOOK_URL to your public domain. Without this, n8n generates localhost webhook URLs that external services can't reach.

Encryption key mismatch: The N8N_ENCRYPTION_KEY encrypts credentials. If you lose it or change it, all stored credentials become unreadable. Back it up immediately and never rotate it without migrating credentials first.

Container restarts losing data: Always use named volumes (n8n_data:) not bind mounts for production. Bind mounts have permission issues; named volumes don't.

Timezone in cron expressions: Set GENERIC_TIMEZONE or cron triggers run in UTC. Unexpected times in cron expressions often trace back to this.


Free n8n Workflow JSON

Here's a self-monitoring workflow that pings your n8n health endpoint every 5 minutes and sends a Slack alert if it fails:

{
  "name": "n8n Self-Monitor",
  "nodes": [
    {
      "parameters": { "rule": { "interval": [{ "field": "minutes", "minutesInterval": 5 }] } },
      "name": "Every 5 Min",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [250, 300]
    },
    {
      "parameters": { "url": "http://localhost:5678/healthz", "options": {} },
      "name": "Health Check",
      "type": "n8n-nodes-base.httpRequest",
      "position": [450, 300]
    },
    {
      "parameters": {
        "conditions": { "string": [{ "value1": "={{ $json.status }}", "operation": "notEqual", "value2": "ok" }] }
      },
      "name": "Is Down?",
      "type": "n8n-nodes-base.if",
      "position": [650, 300]
    },
    {
      "parameters": {
        "webhookUri": "YOUR_SLACK_WEBHOOK_URL",
        "text": "⚠️ n8n health check failed at {{ $now.toISO() }}"
      },
      "name": "Slack Alert",
      "type": "n8n-nodes-base.slack",
      "position": [850, 200]
    }
  ],
  "connections": {
    "Every 5 Min": { "main": [[{ "node": "Health Check", "type": "main", "index": 0 }]] },
    "Health Check": { "main": [[{ "node": "Is Down?", "type": "main", "index": 0 }]] },
    "Is Down?": { "main": [[{ "node": "Slack Alert", "type": "main", "index": 0 }], []] }
  }
}
Enter fullscreen mode Exit fullscreen mode

Want This Set Up For You?

If you'd rather skip the server setup entirely: Done-For-You n8n Workflow Service — describe your automation need, get a working n8n workflow (and Docker setup help if needed) delivered in 48 hours. $99 flat.

Or grab the n8n Workflow Pack for 5 plug-and-play workflow JSONs ($29 one-time).


Related Articles

Top comments (0)