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
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:
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:
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
Start everything:
docker compose up -d
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
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
Updating n8n
docker compose pull
docker compose up -d
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 /
For PostgreSQL, use pg_dump:
docker exec n8n-postgres-1 pg_dump -U n8n n8n > n8n-db-backup.sql
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 }], []] }
}
}
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).
Top comments (0)