How to install n8n self-hosted with Docker
Table of contents
- Key takeaways
- What n8n does and when self-hosting makes sense
- Which n8n version to install in September 2026
- Installing with Docker Compose and PostgreSQL
- Database choice
- Queue mode for serious loads
- Critical environment variables
- Traefik or Nginx in front
- Typical friction points
- Frequently asked questions
- Is n8n free to use for myself?
- When should I switch on queue mode?
- What happens if I lose the encryption key?
- Can I use MySQL or MariaDB as the n8n database?
- My take
- Conclusion
- Sources
Tested with n8n 2.39.6 · PostgreSQL 18.6 · Redis 8.10 · Docker Compose 2.40 · verified
Updated: 2026-09-16
n8n is the low-code automation project that has best adapted to self-hosting. A walk through the real install with Docker Compose, the database and queue decisions to make, and the points where most people trip up the first time.
n8n is a low-code automation platform with about 204,600 stars on GitHub as of 16 September 2026. It offers a serious alternative to Zapier and Make for organizations wanting to control their own flows without paying for each execution. This article installs n8n 2.39.6, the stable release on that date, with Docker Compose and PostgreSQL 18. It also covers the architecture decisions to make at start and the points where you will stumble the first time; we ran every command that same day.
Key takeaways
-
Pin the image version:
n8nio/n8n:2.39.6has been the stable release since 16 September 2026, while 2.40.1 is still in beta. -
Start with PostgreSQL 17 or 18: n8n 2.0 dropped MySQL and MariaDB as storage backends, and queue mode on SQLite isn’t supported.
-
Code node scripts run on task runners; in production, move them to an
n8nio/runnerscontainer of the same version. -
N8N_ENCRYPTION_KEYis the most critical variable: generate it once, store it with your database backups, and never change it. -
Behind a proxy, public webhooks need
N8N_WEBHOOK_URLset to the real domain;WEBHOOK_URLhas been deprecated since n8n 2.35.0.
What n8n does and when self-hosting makes sense
n8n lets you connect tools via visual flows where each node is an action, a trigger, or a transformation. There are nodes for hundreds of common services: Google Workspace, Slack, databases, generic HTTP APIs, message queues, WhatsApp, email, object storage. When a node doesn’t exist, a code node allows writing JavaScript or Python for almost anything.
The project’s source is available from its main GitHub repository[1], with the official image published on Docker Hub[2]. n8n created the Sustainable Use License in 2022 as a fair-code model. It lets you use and modify the code for internal or non-commercial use, but restricts reselling it as a competing service (license details[3]). Files with .ee. in their name require an Enterprise license.
Self-hosting makes sense for three main reasons:
-
Sensitive data control: if your flows move customer data, contracts, or financial data, having them bouncing between external SaaS and internal systems is an exposure not all organizations accept.
-
Cost: the self-hosted version doesn’t charge per flow execution, which for high volumes is a substantial saving.
-
Flexibility: integrating with internal systems, private APIs, or local databases is much easier when n8n runs inside the same network.
Where it doesn’t make sense is when flows are few and simple, with few external integrations and low volume. In that scenario, the operational friction of maintaining another piece of infrastructure outweighs cost savings.
Which n8n version to install in September 2026
Install the stable 2.x line and pin the exact number. Version 2.0.0[4] shipped on 8 December 2025. On 16 September 2026, GitHub marks 2.39.6[5] as Latest and 2.40.1 as Pre-release. Since 2.0 the release channels are called stable and beta, and n8n recommends pinning the version (n8n 2.0 breaking changes[6]).
n8n plans to ship n8n 3.0 in October 2026[7], and it will only support Docker-based self-hosted installs. If you already run a 2.x instance, follow the guide to prepare your self-hosted n8n for n8n 3.0 before you upgrade.
Installing with Docker Compose and PostgreSQL
The install is three containers: PostgreSQL, n8n, and the task runners, and we tested it with Docker Engine 29.5.2 and Compose 2.40.3 on linux/arm64. It builds on the withPostgres example in the n8n-hosting repository[8], because the official Docker Compose guide[9] uses SQLite and adds the n8n Assistant sandbox.
First, create the directory and the .env file with the secrets, generated with openssl:
mkdir -p ~/n8n && cd ~/n8n
cat > .env <<EOF
N8N_VERSION=2.39.6
N8N_DOMAIN=n8n.example.com
POSTGRES_PASSWORD=$(openssl rand -hex 24)
N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)
N8N_RUNNERS_AUTH_TOKEN=$(openssl rand -hex 32)
EOF
chmod 600 .env
Replace n8n.example.com with your subdomain and keep a copy of .env off the server. Then create compose.yaml in the same directory:
services:
postgres:
image: postgres:18.6
restart: unless-stopped
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: n8n
volumes:
- db_data:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"]
interval: 5s
timeout: 5s
retries: 10
n8n:
image: n8nio/n8n:${N8N_VERSION}
restart: unless-stopped
ports:
- "127.0.0.1:5678:5678"
environment:
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_USER: n8n
DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
N8N_HOST: ${N8N_DOMAIN}
N8N_PROTOCOL: https
N8N_WEBHOOK_URL: https://${N8N_DOMAIN}/
N8N_PROXY_HOPS: 1
GENERIC_TIMEZONE: Europe/Madrid
TZ: Europe/Madrid
N8N_RUNNERS_MODE: external
N8N_RUNNERS_AUTH_TOKEN: ${N8N_RUNNERS_AUTH_TOKEN}
N8N_RUNNERS_BROKER_LISTEN_ADDRESS: 0.0.0.0
volumes:
- n8n_data:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
n8n-runner:
image: n8nio/runners:${N8N_VERSION}
restart: unless-stopped
environment:
N8N_RUNNERS_AUTH_TOKEN: ${N8N_RUNNERS_AUTH_TOKEN}
N8N_RUNNERS_TASK_BROKER_URI: http://n8n:5679
depends_on:
- n8n
volumes:
db_data:
n8n_data:
PostgreSQL 18 stores its data in /var/lib/postgresql/18/docker, so the volume is mounted at /var/lib/postgresql, as the official image documentation[10] says. Port 5678 is only published on the host’s 127.0.0.1, because the reverse proxy will expose n8n. The n8n-runner service runs Code node scripts outside the n8n process, on the same version.
Start the stack and check its state:
docker compose up -d
docker compose ps --format 'table {{.Service}}\t{{.Image}}\t{{.Status}}'
SERVICE IMAGE STATUS
n8n n8nio/n8n:2.39.6 Up 7 seconds
n8n-runner n8nio/runners:2.39.6 Up 7 seconds
postgres postgres:18.6 Up 13 seconds (healthy)
On first start, n8n applies 263 migrations. The editor was ready 3.5 s after the container started with a load average of 4 on 18 cores, and 6.9 s with a load of 37. The logs confirm the version, the public URL, and the JavaScript and Python runners:
docker compose logs --no-log-prefix n8n | grep -E -A1 'Version:|Editor is now'
docker compose logs --no-log-prefix n8n | grep 'Registered runner'
docker compose exec n8n wget -qO- http://127.0.0.1:5678/healthz/readiness
Version: 2.39.6
Building workflow dependency index...
--
Editor is now accessible via:
https://n8n.example.com
Registered runner "launcher-javascript" (d991bbc56f76e9b3)
Registered runner "launcher-python" (984bc6c61fb5eeea)
{"status":"ok"}
Once the proxy is in place, open https://n8n.example.com and create the owner account. The test had no domain, so we created that account through the local REST API rather than the browser form.
Database choice
The database stores flows, credentials, execution history, and users. Since n8n 2.0 only SQLite, the default, and PostgreSQL remain, because that release dropped MySQL and MariaDB.
The recommendation, for almost any deployment with more than one flow or user, is to start directly with PostgreSQL. The n8n database documentation[11] supports PostgreSQL 17 and 18, plus 16 for compatibility. Moving from SQLite to PostgreSQL later is possible with n8n export:entities and import:entities, but awkward, and PostgreSQL advantages appear even at small scales: better backups, clearer introspection, and the ability to enable queue mode later without remigrating.
If you already run a shared PostgreSQL instance, you can use it by creating a dedicated database for n8n, which cuts down the number of containers and simplifies backups. Anyone already managing that Postgres with Docker Compose can lean on the Docker Compose install guide for Ubuntu 20.04 to define both services in the same stack.
Watch the volume path on PostgreSQL 18. The n8n example mounts it at /var/lib/postgresql/data and sets PGDATA to that path; without that variable, the database lands outside the volume and a recreated container starts empty. The PostgreSQL with Docker guide covers the other options.
Queue mode for serious loads
By default, n8n executes flows in the same process serving the web interface. This mode is fine for low volumes, but with long flows or high concurrency, problems appear: the interface slows down, executions can block, and horizontal scaling is impossible.
The solution is queue mode, described in the official queue-mode guide[12]. The main process puts each execution on a Redis queue, and a worker picks it up, runs it, and stores the result in PostgreSQL. Each worker accepts 10 concurrent jobs by default. To enable it, add compose.queue.yaml next to the first file:
services:
redis:
image: redis:8.10.1
restart: unless-stopped
n8n:
environment: &queue
EXECUTIONS_MODE: queue
QUEUE_BULL_REDIS_HOST: redis
OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS: "true"
depends_on: [redis]
n8n-worker:
extends: {file: compose.yaml, service: n8n}
command: worker
environment: *queue
ports: !reset []
volumes: !reset []
depends_on: [redis]
n8n-worker-runner:
extends: {file: compose.yaml, service: n8n-runner}
environment:
N8N_RUNNERS_TASK_BROKER_URI: http://n8n-worker:5679
depends_on: [n8n-worker]
The worker inherits the n8n configuration through extends, including the same N8N_ENCRYPTION_KEY. The !reset tag (Compose merge rules[13]) removes its port and volume, so it doesn’t share the event log with the main process. Each worker also needs its own runners container. Start both files together and check that the worker is ready:
export COMPOSE_FILE=compose.yaml:compose.queue.yaml
docker compose up -d
docker compose logs --no-log-prefix n8n-worker | grep -A4 'worker is now ready'
n8n worker is now ready
* Version: 2.39.6
* Concurrency: 10
* Pool: (default)
* Queue: jobs
With a published test flow, the worker logged Worker started execution 1 (job 1) and Worker finished execution 1 (job 1). OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS=true also sends manual executions to the workers, which n8n 3.0 will always do. Binary data can’t live on the filesystem in queue mode: use the database or S3.
Critical environment variables
There are a handful of environment variables worth configuring correctly from the start:
-
N8N_ENCRYPTION_KEY: the key used to encrypt credentials in the database. If this key changes, all stored credentials become unusable. Generate it once withopenssl rand -hex 32, save it safely, and never change it. -
N8N_HOST,N8N_WEBHOOK_URLandN8N_PROXY_HOPS: tell n8n its domain, the URL for public webhooks, and how many proxies sit in front of it (reverse proxy configuration[14]). The oldWEBHOOK_URLhas been deprecated since 2.35.0, and in the test the log warned:Use N8N_WEBHOOK_URL instead. The typical mistake is forgetting the public URL behind Traefik, leaving webhooks pointing at localhost. -
N8N_RUNNERS_MODEandN8N_RUNNERS_AUTH_TOKEN: since n8n 2.0 task runners are on by default, and the 2.39.6 log asks you to removeN8N_RUNNERS_ENABLED. The log also marks internal mode, which launches runners as child processes of n8n, as deprecated, and the task runner documentation[15] advises against it in production. WithN8N_RUNNERS_MODE=external, then8nio/runnerscontainer connects to port 5679 with the shared token.
Traefik or Nginx in front
In most self-hosted installs, n8n runs behind a proxy like Traefik or Nginx to handle HTTPS and to serve more than one service on the same server. The proxy config is standard: a router pointing at port 5678 on the n8n container, with automatic TLS and the X-Forwarded-* headers. Anyone who hasn’t set up the proxy yet can follow the Traefik with Docker Compose install guide, which uses the same Docker Compose syntax[16] as this article.
One detail worth watching is the proxy timeout. Some flows can take minutes to run, especially if they call slow external APIs or process large files. Nginx’s proxy_read_timeout[17] defaults to 60 s and can cut these executions short, producing 504 Gateway Timeout errors. Raise it to minutes, not seconds, for the flow execution routes.
For public webhooks, pay attention to the public URL being reachable from the internet. A common mistake is running n8n on an internal network behind a VPN, adding a webhook to a flow, and not understanding why it never arrives. The external service can’t reach the URL n8n generates.
Typical friction points
Four problems almost everyone runs into the first time:
-
Data persistence: the n8n container stores data at
/home/node/.n8n, which must go on a persistent volume. More than one team has lost flows and credentials by runningdocker compose down -v, which removes volumes. The recommendation is to use named volumes and keep regular database backups. -
Credential handling: n8n encrypts credentials with
N8N_ENCRYPTION_KEY, but the backup process must include both the database and the encryption key. If you restore the database on another server with a different key, all credentials become useless. Always keep both pieces together. -
Publishing instead of activating: n8n 2.0 replaced the active/inactive toggle with Publish and Unpublish buttons, and changes stay in draft until you publish (save and publish workflows[18]).
n8n publish:workflowneeds an n8n restart, and in the testn8n import:workflowunpublished the flow it overwrote. Review your published flows after a bulk import. -
Code that stops working in the Code node: runners isolate that code, and in the test
process.versionfailed withprocess is not defined. Since n8n 2.0, the node also can’t read environment variables unless you setN8N_BLOCK_ENV_ACCESS_IN_NODE=false.
For backups, these two commands worked in the test. The first dumps PostgreSQL and the second exports each flow to a JSON file inside the n8n volume:
docker compose exec -T postgres pg_dump -U n8n -Fc n8n > n8n-$(date +%F).dump
docker compose exec -u node n8n \
n8n export:workflow --backup --output=/home/node/.n8n/backups/
Frequently asked questions
Is n8n free to use for myself?
Yes. Under the Sustainable Use License you can use, modify, and self-host n8n at no cost for internal or personal use; what the license restricts is reselling it as a competing SaaS service.
When should I switch on queue mode?
When executions pile up or the editor slows down under load. In the test, the worker used between 252 and 458 MiB of RAM and Redis between 9 and 43 MiB, a small price compared with migrating later with production data.
What happens if I lose the encryption key?
Every credential stored in the database becomes unusable and has to be re-entered one by one. That’s why N8N_ENCRYPTION_KEY must be backed up alongside the database, never separately.
Can I use MySQL or MariaDB as the n8n database?
Not since n8n 2.0, which only supports SQLite and PostgreSQL. Migrate your data before upgrading; the MySQL node for querying your own databases is still available.
My take
n8n delivers on its promise: complex automations without writing much code, with control over your data and no per-execution fees. For organizations that chain internal systems together and want to keep their data in-house, the self-hosted version is competitive against any commercial alternative.
The concrete recommendation is that if you’re evaluating Zapier or Make for serious use in your organization, look at self-hosted n8n before deciding. The requirements are modest: in the test, n8n ranged between 264 and 872 MiB of RAM and PostgreSQL between 35 and 45 MiB. If you are torn between self-hosted platforms, compare it with Activepieces and Windmill first.
Where it’s worth stopping to think is the opposite case. If your internal operations don’t pile up integrations or sensitive data, and you only need to glue together three common external APIs, the commercial SaaS version is still easier to manage. Self-hosting infrastructure always carries an operational cost, and that cost needs to be justified by real benefit. n8n is no exception to that general rule.
Conclusion
The decision to self-host n8n comes down to one question: does control over data and savings on executions justify the operational cost of maintaining another piece of infrastructure? For medium volumes and sensitive data, yes.
Sources
- main GitHub repository
- Docker Hub
- license details
- Version 2.0.0
- 2.39.6
- n8n 2.0 breaking changes
- n8n 3.0 in October 2026
- n8n-hosting repository
- official Docker Compose guide
- official image documentation
- n8n database documentation
- official queue-mode guide
- Compose merge rules
- reverse proxy configuration
- task runner documentation
- Docker Compose syntax
- proxy_read_timeout
- save and publish workflows