Self-hosted (Docker)
Full control with a single container.
Run n8n yourself with Docker Compose on any VPS or bare-metal box. Full data ownership, unlimited executions, and complete control of the stack.
Pros
- Full data ownership
- Unlimited executions
- Install community & custom nodes freely
- Cheap for high-volume workflows
Cons
- You own upgrades, backups, TLS, monitoring
- Needs Docker + reverse-proxy know-how
- Queue mode requires Redis + workers for scale
Prerequisites
- A VPS with Ubuntu 22.04+ (min 2 vCPU / 2GB RAM)
- A domain name pointed to the server's IP
- Docker Engine + Docker Compose plugin
- Basic command-line comfort
Step-by-step guide
Step 1
Provision the server
Spin up a VPS (Hetzner, DO, Linode…), point your domain's A record to its IP.
ssh root@your-server-ipStep 2
Install Docker
Use the official convenience script.
curl -fsSL https://get.docker.com | shStep 3
Write docker-compose.yml
Define Postgres, n8n, and Caddy services with named volumes.
services: postgres: image: postgres:16 restart: unless-stopped environment: POSTGRES_USER: n8n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: n8n volumes: [ pgdata:/var/lib/postgresql/data ] n8n: image: n8nio/n8n:latest restart: unless-stopped depends_on: [ postgres ] ports: [ "5678:5678" ] environment: DB_TYPE: postgresdb DB_POSTGRESDB_HOST: postgres DB_POSTGRESDB_DATABASE: n8n DB_POSTGRESDB_USER: n8n DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD} N8N_HOST: ${N8N_HOST} N8N_PROTOCOL: https N8N_PORT: 5678 WEBHOOK_URL: https://${N8N_HOST}/ N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY} GENERIC_TIMEZONE: Europe/Berlin volumes: [ n8n_data:/home/node/.n8n ] caddy: image: caddy:2 restart: unless-stopped ports: [ "80:80", "443:443" ] volumes: - ./Caddyfile:/etc/caddy/Caddyfile - caddy_data:/data - caddy_config:/config volumes: { pgdata: {}, n8n_data: {}, caddy_data: {}, caddy_config: {} }Step 4
Configure the Caddyfile
Point your domain at the n8n container; Caddy issues Let's Encrypt certs automatically.
your-domain.com { reverse_proxy n8n:5678 }Step 5
Set environment variables & launch
Create a .env with POSTGRES_PASSWORD, N8N_HOST, N8N_ENCRYPTION_KEY, then start the stack.
docker compose up -d
Scaling & queue mode
For higher throughput, switch to queue mode: set EXECUTIONS_MODE=queue, add a Redis instance, and run one or more separate `n8n worker` containers alongside the main process. The main instance handles the editor UI and webhooks while workers pull jobs from Redis. Scale worker replicas horizontally as load grows, and use a managed Postgres (with read replicas if needed) once execution volume is high.
Backup & upgrade
Back up the Postgres database (pg_dump on a cron schedule, or your provider's managed snapshot feature) and the n8n_data volume (contains binary data & config). Before upgrading, snapshot both, read the release notes for breaking changes, pull the new image tag, then `docker compose up -d`. Roll back by restoring the previous image tag + database snapshot if anything fails.
Security checklist
- Always run behind HTTPS (Caddy/Traefik/Nginx + Let's Encrypt) — never expose port 5678 directly.
- Set a strong, permanent N8N_ENCRYPTION_KEY and back it up (losing it breaks stored credentials).
- Use Postgres, not the default SQLite, for anything beyond local testing.
- Enable N8N_BASIC_AUTH or an SSO/proxy layer if the instance is reachable from the internet.
- Restrict inbound firewall rules to 80/443 (and SSH from your IP only).
- Keep the n8n image pinned and update on a schedule instead of always using :latest in production.
Cost breakdown
- Small VPS (2GB RAM)$5–12/mo
- Domain name~$10/yr
- TLS certs (Let's Encrypt)Free
Troubleshooting & FAQ
+Can I use SQLite instead of Postgres?
n8n defaults to SQLite, which works for testing but isn't recommended for production: no concurrent writers, harder backups, and risk of corruption under load. Use Postgres for anything real.
+My webhooks return 404 / don't trigger — why?
WEBHOOK_URL must match the public HTTPS URL exactly (including trailing slash), and the workflow must be activated (not just saved) for production webhook URLs to work.
+How do I move from Self-hosted Docker to another host later?
Export workflows/credentials with the n8n CLI (`n8n export:workflow --all` / `export:credentials --all`) or the REST API, restore the Postgres dump on the new host, then import.
+Do I lose data if the container restarts?
No, as long as n8n_data and the Postgres volume are mounted as named Docker volumes (not ephemeral container storage), your data persists across restarts and image updates.