Running n8n with Docker
A Compose file that actually persists data, the four things that trip people up in production, and where the line is between "fun weekend project" and "unpaid part-time job."
A docker-compose.yml That Actually Persists Data
n8n plus Postgres, with the encryption key, webhook URL, and volumes set up correctly from the start. Replace the placeholder values before running it.
services:
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: change-this-password
POSTGRES_DB: n8n
volumes:
- postgres_data:/var/lib/postgresql/data
n8n:
image: n8nio/n8n
restart: unless-stopped
ports:
- "5678:5678"
environment:
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_DATABASE: n8n
DB_POSTGRESDB_USER: n8n
DB_POSTGRESDB_PASSWORD: change-this-password
N8N_ENCRYPTION_KEY: replace-with-a-long-random-string
N8N_HOST: n8n.yourdomain.com
WEBHOOK_URL: https://n8n.yourdomain.com/
GENERIC_TIMEZONE: UTC
volumes:
- n8n_data:/home/node/.n8n
depends_on:
- postgres
volumes:
postgres_data:
n8n_data:The Four Things That Actually Bite People
Every one of these works fine until the day it doesn't.
Lose N8N_ENCRYPTION_KEY, lose every saved credential
n8n encrypts stored credentials with this key. If your container gets recreated without it set explicitly (or you never wrote it down), every saved credential becomes unreadable — not just hard to find, permanently unreadable. Set it once, save it somewhere outside the container, and never let it regenerate.
SQLite works until it doesn't
n8n defaults to SQLite if you don't configure a database. It's fine for testing, but concurrent workflow executions against a single SQLite file cause lock contention once you're running anything real. Point DB_TYPE at Postgres from the start — retrofitting it later means a manual data migration.
WEBHOOK_URL has to match what the internet actually sees
If n8n sits behind a reverse proxy (Caddy, Nginx, Traefik) terminating SSL, WEBHOOK_URL needs to be the public https:// URL, not localhost or the internal container address — otherwise webhook nodes generate URLs nothing external can reach.
Bind-mount /home/node/.n8n, or restarts erase it
Without a persistent volume on /home/node/.n8n, recreating the container (a routine `docker compose up -d` after pulling a new image) wipes local config and any binary data n8n cached outside the database. The compose file above already handles this — but it's the single most common thing skipped.
Scaling past a single container
Once one instance can't keep up, n8n's queue mode adds a Redis container plus one or more worker containers that pull jobs off a queue instead of racing to execute everything in the main process. It's the right move for high-volume workflows — it's also three more containers to keep healthy, patched, and monitored, on top of the two you already have.
When Docker Becomes a Part-Time Job
None of the above is hard on its own. It adds up: security patches on the host OS, pulling new n8n images and testing them before they hit production, watching disk usage on the Postgres volume, and being the one who gets paged when a container doesn't restart cleanly.
If that's not how you want to spend your evenings, a managed dedicated server gets you the same n8n — same Community Edition, same node library — with the update, backup, and monitoring work already handled.
Or Get n8n Running Without the Compose File
Same n8n, on a dedicated server we patch, back up, and monitor. From $7/mo, 10-day free trial.
Frequently Asked Questions
Is docker run enough, or do I need Docker Compose?
A single docker run command works for a quick test, but you'll want Postgres as a separate container almost immediately — which means Compose (or an equivalent orchestration) becomes necessary anyway. Starting with Compose from the beginning avoids a migration later.
Why does n8n need Postgres instead of the default SQLite?
SQLite is a single file with limited concurrent-write support. n8n's default install uses it for zero-config testing, but production workloads — multiple workflows executing at once, queue mode, high webhook volume — need a real database that handles concurrency properly. Postgres is what n8n itself recommends and what queue mode requires.
How do I update n8n running in Docker without losing data?
Pull the new image tag, then `docker compose up -d` — as long as your volumes are set up correctly (see the persistent-volume gotcha above), workflows and credentials survive the restart. Always back up the Postgres volume before a major version bump; n8n's release notes call out breaking changes when they happen.
Can I run n8n in Docker without exposing it to the internet?
Yes, for testing — bind it to localhost or a private network and access it over SSH tunnel or VPN. But webhook-triggered workflows need a publicly reachable WEBHOOK_URL to actually receive anything, so a production instance almost always needs to be internet-facing behind a reverse proxy with SSL.
Does Docker support n8n's queue mode for scaling?
Yes — queue mode adds a Redis container and one or more separate worker containers that pull execution jobs off a queue instead of running everything in the main process. It's more moving parts to keep healthy, which is usually where self-managed Docker setups start feeling like real infrastructure work rather than a weekend project.
Skip the Compose File
Get n8n running on a dedicated server in minutes, with updates and backups handled for you. Free for 10 days.
Already self-hosting? Our migration tool moves your workflows over automatically.