Before the first docker run, line up these prerequisites so the n8n docker setup does not stall halfway:
- A Linux host (Ubuntu 22.04 LTS or Debian 12 is a safe default) with at least 1 vCPU and 2 GB RAM. n8n is light at rest but spikes during heavy executions.
- Docker Engine and the Docker Compose plugin installed.
- A domain or subdomain (for example
n8n.yourcompany.com) with a DNS A record pointing at the server's public IP. You need this for valid HTTPS. - Ports 80 and 443 open in your firewall and reachable from the internet.
Install Docker quickly on Ubuntu:
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# log out and back in so the group change takes effect
docker --version
Confirm the Compose plugin is present with docker compose version. If it prints a version, you are ready.
The single most important rule when you run n8n in Docker is: persist /home/node/.n8n. That directory holds the SQLite database, the encryption key, and saved binary data. Lose it and you lose every credential.
- Create a named Docker volume so the data lives outside the container lifecycle:
``bash docker volume create n8n_data ``
- Start the container with the volume attached and the host details set:
``bash docker run -d --restart unless-stopped \ --name n8n \ -p 5678:5678 \ -e N8N_HOST=n8n.yourcompany.com \ -e N8N_PROTOCOL=https \ -e WEBHOOK_URL=https://n8n.yourcompany.com/ \ -e GENERIC_TIMEZONE=America/New_York \ -v n8n_data:/home/node/.n8n \ docker.n8n.io/n8nio/n8n ``
- The
--restart unless-stopped flag means n8n comes back automatically after a host reboot. The WEBHOOK_URL must be your public HTTPS address, or webhook-triggered workflows will register the wrong callback URL and silently fail.
Back up the volume regularly. A cron job that runs docker run --rm -v n8n_data:/data -v $(pwd):/backup alpine tar czf /backup/n8n-$(date +%F).tgz /data gives you a restorable snapshot.
For anything beyond a quick test, define the stack declaratively. A Compose file makes the n8n self hosted deployment reproducible and version-controllable. We pair n8n with PostgreSQL because SQLite struggles once you have many concurrent executions.
services:
postgres:
image: postgres:16
restart: unless-stopped
environment:
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=change_this_password
- POSTGRES_DB=n8n
volumes:
- pg_data:/var/lib/postgresql/data
n8n:
image: docker.n8n.io/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_HOST=n8n.yourcompany.com
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.yourcompany.com/
- N8N_ENCRYPTION_KEY=set_a_long_random_string_here
- GENERIC_TIMEZONE=America/New_York
volumes:
- n8n_data:/home/node/.n8n
depends_on:
- postgres
volumes:
pg_data:
n8n_data:
Bring it up with docker compose up -d. The N8N_ENCRYPTION_KEY is critical: set it explicitly so you can move the stack between hosts without orphaning your encrypted credentials. We go much deeper on the Postgres pairing and scaling options in our dedicated n8n Docker Compose guide.
Never expose n8n on plain HTTP. Put a reverse proxy in front to terminate TLS. Caddy is the lightest option because it provisions and renews Let's Encrypt certificates automatically.
A minimal Caddyfile:
n8n.yourcompany.com {
reverse_proxy localhost:5678
}
Run Caddy as a second container or system service on the same host. With a valid DNS record pointing at your server, Caddy fetches a certificate on first request and your instance is live on https://n8n.yourcompany.com.
Beyond TLS, harden the instance:
- Set owner credentials immediately. n8n's first-run setup creates an owner account; never leave it open.
- Restrict the editor. If only your team needs the UI, firewall port 5678 to internal IPs and let webhooks flow through the proxy only.
- Rotate the encryption key carefully. Changing
N8N_ENCRYPTION_KEY after credentials exist makes them unreadable. Set it once, store it in a secrets manager.
Updating is one of the real advantages of the n8n docker approach — it is a pull-and-restart, not a migration project.
- Pull the newer image:
``bash docker compose pull n8n ``
- Recreate the container with the new image while keeping volumes intact:
``bash docker compose up -d ``
- Verify the version in the n8n UI footer, then confirm a couple of critical workflows still execute.
Pin to a specific tag (for example n8nio/n8n:1.65.0) in production rather than tracking latest. That way an update is a deliberate, tested act instead of a surprise the next time Docker pulls a fresh image. Always snapshot your Postgres database and the n8n_data volume before a major version jump, in case a breaking change requires a rollback.
A handful of issues account for most support questions we see around the n8n docker setup.
If a webhook node registers http://localhost:5678 instead of your domain, you forgot to set WEBHOOK_URL (and usually N8N_HOST/N8N_PROTOCOL). Set all three to your public HTTPS address and restart.
This is the encryption-key problem. When you migrate the n8n_data volume to a new host but n8n generates a new encryption key, every saved credential becomes undecryptable. Always carry over the same N8N_ENCRYPTION_KEY.
The official image runs as the node user (UID 1000). If you bind-mount a host directory instead of using a named volume, fix ownership with sudo chown -R 1000:1000 ./n8n-data. Named volumes avoid this class of problem entirely, which is why we prefer them.
Check docker logs n8n. The usual culprit is a Postgres connection that is not yet ready; depends_on starts Postgres first but does not wait for it to accept connections, so a restart: unless-stopped policy lets n8n retry until the database is up.
Once your instance is live, the value comes from what you automate. A self-hosted box removes execution limits, so high-volume jobs that would blow a managed quota run freely.
- Lead-capture pipelines that route form submissions into a CRM and fire instant notifications to Slack or WhatsApp.
- Scheduled data syncs between SaaS tools that lack a native integration — for example pushing rows from an Airtable base into a Postgres reporting database every hour.
- Internal back-office jobs like nightly invoice exports, report generation, and cleanup tasks that nobody wants to run by hand.
- AI-augmented workflows that call an LLM to classify, summarize, or draft — the foundation for connecting n8n to the Model Context Protocol via our n8n MCP integration walkthrough.
Because the instance is yours, you can also install community nodes that extend n8n with integrations the managed product does not ship, and run long, heavy executions that a metered plan would throttle. That headroom is the whole point of self-hosting: the engine scales with your hardware, not your invoice.
If you would rather start from proven patterns than a blank canvas, browse our library of ready-made n8n templates and import one directly into your new instance. New to the tool entirely? Our primer on what n8n is and how it works covers the fundamentals before you wire up anything complex.
Self-hosting n8n in Docker is genuinely accessible — the steps above are not exotic. But "accessible" and "worth your time" are different questions.
If you run a handful of internal automations and someone on your team is comfortable with Docker, self-hosting is a great call: low cost, full control, and total privacy. The maintenance burden is real but modest — occasional updates, backups, and certificate renewal that Caddy mostly handles for you.
If automation is business-critical and downtime costs you revenue, the calculus shifts. You now need monitoring, alerting, a backup-restore drill you have actually tested, and someone on call when a workflow breaks at 2 a.m. That is an ops function, not a weekend project.
Self-hosting n8n in Docker hands you a private, unmetered automation engine for the price of a small server — and the setup is a one-evening job, not a migration epic. The discipline that separates a hobby instance from a dependable one is unglamorous: persist the data volume, pin the encryption key, run behind real TLS, and back up before every update. Get those four things right and your self-hosted n8n will quietly run thousands of executions a month without ever asking for your attention.