Self-Hosting n8n on Docker
n8n is powerful, but relying on someone else’s cloud for your automations isn’t always ideal. Self-hosting gives you control, privacy, and no usage limits.
Here’s how to run n8n on your own infrastructure.
Quick Start with Docker
The fastest way to get started:
docker run -it --rm \
--name n8n \
-p 5678:5678 \
-v ~/.n8n:/home/node/.n8n \
n8nio/n8n
Open http://localhost:5678 and you’re running.
Production Setup with Docker Compose
For production, you’ll want persistence, environment configuration, and proper networking:
# docker-compose.yml
version: '3.8'
services:
n8n:
image: n8nio/n8n
restart: always
ports:
- "5678:5678"
environment:
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=admin
- N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
- N8N_HOST=${N8N_HOST}
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://${N8N_HOST}/
- GENERIC_TIMEZONE=UTC
volumes:
- n8n_data:/home/node/.n8n
volumes:
n8n_data:
Create a .env file:
N8N_PASSWORD=your-secure-password
N8N_HOST=n8n.yourdomain.com
Using PostgreSQL for Persistence
SQLite works for small deployments, but PostgreSQL is better for production:
version: '3.8'
services:
postgres:
image: postgres:13
restart: always
environment:
POSTGRES_DB: n8n
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
n8n:
image: n8nio/n8n
restart: always
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=${POSTGRES_PASSWORD}
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=admin
- N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
depends_on:
- postgres
volumes:
- n8n_data:/home/node/.n8n
volumes:
postgres_data:
n8n_data:
Adding HTTPS with Traefik
Never run n8n without HTTPS in production:
version: '3.8'
services:
traefik:
image: traefik:v2.5
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.email=you@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- letsencrypt:/letsencrypt
n8n:
image: n8nio/n8n
restart: always
environment:
- N8N_HOST=n8n.yourdomain.com
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.yourdomain.com/
# ... other env vars
labels:
- "traefik.enable=true"
- "traefik.http.routers.n8n.rule=Host(`n8n.yourdomain.com`)"
- "traefik.http.routers.n8n.entrypoints=websecure"
- "traefik.http.routers.n8n.tls.certresolver=letsencrypt"
volumes:
letsencrypt:
Environment Variables Reference
Essential configuration:
# Authentication
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=secure-password
# Host configuration
N8N_HOST=n8n.yourdomain.com
N8N_PORT=5678
N8N_PROTOCOL=https
WEBHOOK_URL=https://n8n.yourdomain.com/
# Database (PostgreSQL)
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n
DB_POSTGRESDB_PASSWORD=db-password
# Timezone
GENERIC_TIMEZONE=Europe/Berlin
# Execution settings
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168 # 7 days in hours
Backups
Database Backup
docker exec postgres pg_dump -U n8n n8n > backup.sql
Volume Backup
docker run --rm \
-v n8n_data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/n8n-backup.tar.gz /data
Automated Backup Script
#!/bin/bash
DATE=$(date +%Y%m%d)
docker exec postgres pg_dump -U n8n n8n | gzip > /backups/n8n-$DATE.sql.gz
find /backups -name "n8n-*.sql.gz" -mtime +30 -delete
Scaling for High Volume
For many concurrent workflows:
services:
n8n:
image: n8nio/n8n
deploy:
replicas: 3
environment:
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
n8n-worker:
image: n8nio/n8n
command: n8n worker
deploy:
replicas: 5
environment:
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
redis:
image: redis:6
Security Checklist
- HTTPS enabled
- Strong basic auth password
- PostgreSQL password not default
- Firewall allows only 80/443
- Regular backups configured
- Execution history pruning enabled
- Environment variables not in docker-compose.yml
Monitoring
Add health checks:
n8n:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5678/healthz"]
interval: 30s
timeout: 10s
retries: 3
Monitor with your existing stack (Prometheus, Grafana).
Final Thoughts
Self-hosting n8n gives you complete control over your automation infrastructure. The setup is straightforward with Docker Compose, and you can scale as your needs grow.
Start simple, add components as needed, and always prioritize backups. Your automations will thank you.
Own your infrastructure.