Self-Hosting n8n: Docker Compose Setup That Survives Production
How to run n8n self-hosted with Docker: a Postgres and Redis docker-compose file, queue mode, backups, upgrades, security, and when not to self-host.
Getting n8n self-hosted takes five minutes. Keeping a self-hosted n8n instance healthy for a year is a different job. The one-line docker run from the quickstart uses SQLite, stores everything in a single volume, and has no backups, no TLS and no plan for upgrades. It works perfectly until the day it does not, and that day usually involves a corrupted database file or a lost encryption key.
This guide is the setup we would hand to an ops engineer who has to run n8n in Docker for real workloads: a docker-compose file with Postgres and Redis, the environment variables that matter, queue mode, backups, upgrades, security, and an honest section on when you should not self-host at all.
Configuration details below follow the n8n Docker docs and queue mode docs as of October 2026. n8n moves quickly, so check the docs for your version.
Decide your architecture first
There are two sensible shapes for n8n self-hosted:
- Single instance + Postgres. One n8n container runs the editor, receives webhooks and executes workflows. Good for up to moderate volume and for teams that want the simplest thing that is still production-grade.
- Queue mode. A main instance handles the editor and triggers, pushes executions onto a Redis queue, and one or more worker containers pull and run them. Optional webhook processors take incoming webhook load. This is how you scale horizontally and stop one heavy workflow from freezing the UI.
Start with option 1 unless you already know you have heavy or bursty load. Moving to queue mode later is a configuration change, provided you started on Postgres. Moving off SQLite later is the painful migration — so skip SQLite entirely for anything you care about.
Docker Compose: n8n with Postgres and Redis
Here is a compose file for queue mode with one worker. Remove the redis and n8n-worker services and the EXECUTIONS_MODE/QUEUE_* variables for a single-instance setup.
services:
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: n8n
volumes:
- pg_data:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U n8n']
interval: 10s
retries: 5
redis:
image: redis:7
restart: unless-stopped
volumes:
- redis_data:/data
n8n:
image: n8nio/n8n:${N8N_VERSION}
restart: unless-stopped
ports:
- '127.0.0.1:5678:5678'
env_file: .env
volumes:
- n8n_data:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
n8n-worker:
image: n8nio/n8n:${N8N_VERSION}
restart: unless-stopped
command: worker --concurrency=5
env_file: .env
volumes:
- n8n_data:/home/node/.n8n
depends_on:
- n8n
volumes:
pg_data:
redis_data:
n8n_data:
And the .env file (never commit it):
N8N_VERSION=<pin an exact version tag>
N8N_HOST=n8n.example.com
N8N_PROTOCOL=https
WEBHOOK_URL=https://n8n.example.com/
GENERIC_TIMEZONE=Europe/Helsinki
TZ=Europe/Helsinki
N8N_ENCRYPTION_KEY=<long random string, stored in your password manager>
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n
DB_POSTGRESDB_PASSWORD=<same as POSTGRES_PASSWORD>
POSTGRES_PASSWORD=<same value>
EXECUTIONS_MODE=queue
QUEUE_BULL_REDIS_HOST=redis
QUEUE_BULL_REDIS_PORT=6379
Why each of these matters
- Pin
N8N_VERSION. Usinglatestmeans a routinedocker compose pullcan jump a major version. Pin a tag and upgrade on purpose. - Set
N8N_ENCRYPTION_KEYyourself. n8n encrypts stored credentials with this key. If you let it auto-generate inside the volume and later lose the volume, every credential becomes unreadable. In queue mode the main instance, workers and webhook processors must all share the same key. WEBHOOK_URL. Behind a reverse proxy, n8n needs to know its public URL or it will show localhost webhook URLs in the editor.GENERIC_TIMEZONEandTZ. The first controls schedule triggers, the second the container clock. Mismatches cause the classic “my 9am job runs at 7am” bug.- Keep the
.n8nvolume even on Postgres. It still holds the encryption key and other instance data. - Bind to
127.0.0.1. Put a reverse proxy (Caddy, Traefik, nginx) in front for TLS. Never expose port 5678 directly.
Queue mode in practice
Queue mode is worth it when any of these is true: workflows regularly take more than a few seconds, you have bursty webhook traffic, or editor responsiveness suffers during heavy runs.
A few operational notes from running it:
- Start with one worker at concurrency 5. The n8n docs list a default concurrency of 10 and recommend 5 or higher. Lower concurrency per worker plus more workers is easier to reason about than one huge worker.
- Scale workers, not the main instance.
docker compose up -d --scale n8n-worker=3is the whole scaling story for most teams. - Watch Redis memory. If workers fall behind, the queue grows in Redis. Alert on queue length, not just CPU.
- Graceful shutdown. Workers wait for running executions on shutdown (
N8N_GRACEFUL_SHUTDOWN_TIMEOUT, 30 seconds by default). Long workflows can be cut off during deploys; raise it if you have them.
Note that n8n’s pricing page lists queue mode among the self-hosted Business plan features, while the docs describe configuring it through environment variables. Check which features your edition and version support before you design around it.
Backups that you have actually restored
A backup you have never restored is a hope, not a backup. For n8n self-hosted you need three things:
-
Postgres dump, nightly at minimum:
docker compose exec -T postgres pg_dump -U n8n -Fc n8n > n8n-$(date +%F).dump -
The encryption key, stored outside the server (password manager or secrets manager). Without it the database dump restores workflows but not credentials.
-
Workflow exports in Git. Use the n8n CLI (
n8n export:workflow --all --separate --output=<dir>) on a schedule and commit the JSON. This gives you diffs and history even on editions without built-in Git version control.
Do a restore drill once a quarter on a throwaway VM: restore the dump, set the same encryption key, start n8n, open a workflow and test a credential. It takes 30 minutes and is the only proof your backup works.
Upgrading without drama
n8n releases frequently, and major versions do deprecate settings (for example, the docs note that N8N_RUNNERS_ENABLED is deprecated from 2.0). Our upgrade routine:
- Read the release notes and breaking-changes list for every version you are skipping.
- Take a fresh Postgres dump.
- Bump
N8N_VERSIONin.envon a staging copy first, run your five most critical workflows manually. - In production:
docker compose pull && docker compose down && docker compose up -d. - Watch the executions list for 30 minutes. Roll back by restoring the dump and the previous tag if something breaks.
Upgrade monthly-ish. Teams that upgrade once a year end up crossing several breaking changes at once, which is where most painful n8n migrations come from.
Security checklist
A self-hosted automation server holds tokens for your CRM, email, billing and data warehouse. Treat it like a production database.
- TLS via reverse proxy; no direct exposure of 5678.
- Restrict the editor to SSO, VPN or an IP allowlist if you can. Webhook paths can stay public; the editor should not.
- Strong owner account password and 2FA where available.
- Least-privilege credentials: create a dedicated integration user in each connected system rather than using a personal admin token.
- Patch the host OS and Docker regularly.
- Keep secrets out of workflow JSON (use credentials, not hard-coded headers). We cover patterns in workflow secrets management.
- Verify signatures on inbound webhooks; see the webhook handshake pattern.
What most people get wrong
- Staying on SQLite “for now.” The n8n docs state that SQLite with queue mode is not recommended and a distributed setup over SQLite is not supported. “For now” becomes a migration project.
- Losing the encryption key. The most common unrecoverable failure. Store it on day one.
- Treating executions data as free. Execution history grows in Postgres. Configure execution data pruning or your database will grow until disk alerts fire.
- No alerting on failed workflows. Self-hosted means nobody else notices. Add an error workflow that posts to Slack, and an external uptime check on the editor URL.
When not to self-host n8n
Self-hosting is the right choice when you have a data-residency or compliance reason, high execution volume, custom nodes, or an existing platform team that already runs Postgres and Docker. It is usually the wrong choice when:
- Nobody on the team owns infrastructure, and “the person who set it up” is the only person who can fix it.
- Your volume fits comfortably in a cloud plan. Once you price maintenance hours, cloud is usually cheaper at low volume; we run the numbers in n8n pricing explained.
- You need guaranteed uptime for revenue-critical flows and have no on-call rotation.
We wrote more broadly about this trade-off in why self-hosting makes sense for some teams.
If you want the control without running the box yourself, compare managed options before committing. We keep honest comparisons at Automatite vs n8n, and our own error handling and observability pages describe what we think a production automation platform should give you out of the box.
FAQ
Can I run n8n self-hosted for free?
The Community Edition has no license fee. You pay for the server, database and your own time. It is licensed under n8n’s Sustainable Use License, which allows internal business use.
Do I need Postgres for n8n in Docker?
Not for testing — n8n defaults to SQLite. For production, and especially queue mode, use Postgres.
What is the n8n Docker image name?
n8nio/n8n. Pin a specific version tag rather than relying on latest.
How do I upgrade n8n running in Docker Compose?
Back up Postgres, bump the image tag, then run docker compose pull, docker compose down and docker compose up -d. Test on staging first when crossing major versions.
When should I use n8n queue mode?
When workflows are long-running, webhook traffic is bursty, or the editor slows down during heavy runs. Queue mode needs Redis and should run on Postgres.
Stay in the loop
Get workflow automation playbooks, integration recipes, and product updates delivered to your inbox.
No spam. Unsubscribe anytime.