Skip to content
MoveWorkflows

Self-hosting n8n with Docker Compose

Updated 2026-10-21 · 5 min read

Self-hosting n8n with Docker gives you everything n8n Cloud restricts: npm packages in Code nodes, Python with libraries, no per-execution pricing, and data on your own server. In exchange you run the database, the runners, backups and upgrades. This guide goes from the one-line test container to a production Compose file with Postgres and the task runner sidecar that n8n 2.x needs for Code nodes and Python.

n8n itself says self-hosting is recommended for expert users (Docker docs). If you've never operated a database in production, read n8n Cloud vs self-hosted first.

Step 0: try it in one command

docker volume create n8n_data
docker run -it --rm --name n8n -p 5678:5678 \
  -e GENERIC_TIMEZONE="Europe/Berlin" -e TZ="Europe/Berlin" \
  -e N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true \
  -v n8n_data:/home/node/.n8n \
  n8nio/n8n

Open http://localhost:5678 and create the owner account. Data, including the SQLite database and the encryption key, lives in the n8n_data volume. Good for evaluation, not for production: no Postgres, no external runners, no TLS.

What a production setup needs

Piece Why
Postgres n8n recommends it over SQLite for real workloads
n8nio/runners sidecar External-mode task runners: real isolation for Code nodes, and required for Python
Fixed N8N_ENCRYPTION_KEY Credentials stay readable across container recreation
Timezone Self-hosted default is America/New_York
Reverse proxy with TLS HTTPS, and correct public webhook URLs
Backups Database dump plus the key

The Compose file

Create a folder with .env and compose.yml.

.env (keep it out of version control):

N8N_VERSION=2.44.4
POSTGRES_USER=n8n
POSTGRES_PASSWORD=change-me
POSTGRES_DB=n8n
N8N_ENCRYPTION_KEY=change-me-long-random-string
N8N_RUNNERS_AUTH_TOKEN=change-me-another-random-string

Pin N8N_VERSION to a current stable release; the runners image must use the same version as n8n (task runners).

compose.yml:

services:
  postgres:
    image: postgres:18
    restart: always
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
      PGDATA: /var/lib/postgresql/data
    volumes:
      - db-storage:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 10

  n8n:
    image: n8nio/n8n:${N8N_VERSION}
    restart: always
    ports:
      - "127.0.0.1:5678:5678"
    environment:
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: "5432"
      DB_POSTGRESDB_DATABASE: ${POSTGRES_DB}
      DB_POSTGRESDB_USER: ${POSTGRES_USER}
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      GENERIC_TIMEZONE: Europe/Berlin
      TZ: Europe/Berlin
      N8N_HOST: n8n.example.com
      N8N_PROTOCOL: https
      N8N_WEBHOOK_URL: https://n8n.example.com/
      N8N_PROXY_HOPS: "1"
      N8N_RUNNERS_MODE: external
      N8N_RUNNERS_BROKER_LISTEN_ADDRESS: 0.0.0.0
      N8N_RUNNERS_AUTH_TOKEN: ${N8N_RUNNERS_AUTH_TOKEN}
    volumes:
      - n8n-data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy

  task-runners:
    image: n8nio/runners:${N8N_VERSION}
    restart: always
    environment:
      N8N_RUNNERS_TASK_BROKER_URI: http://n8n:5679
      N8N_RUNNERS_AUTH_TOKEN: ${N8N_RUNNERS_AUTH_TOKEN}
    depends_on:
      - n8n

volumes:
  db-storage:
  n8n-data:

Start it:

docker compose up -d
docker compose logs -f n8n

Notes on the choices:

  • Postgres 18 with PGDATA set: n8n's Compose guide uses this so the data path matches earlier Postgres versions.
  • Port bound to 127.0.0.1: only the reverse proxy on the same host can reach n8n directly.
  • N8N_WEBHOOK_URL (n8n 2.30.0+; older versions use WEBHOOK_URL): without it, webhook URLs in the editor show localhost:5678 and senders can't reach them.
  • N8N_RUNNERS_BROKER_LISTEN_ADDRESS=0.0.0.0: the broker listens only on localhost by default; the sidecar needs to connect from another container.
  • N8N_RUNNERS_ENABLED isn't needed: it's deprecated from 2.0, where task runners are on by default.

The n8n Compose guide also bundles a sandbox stack for the n8n Assistant (AI code execution). It's optional and needs more resources (at least 4 GB RAM and 2 vCPUs per that guide); the file above leaves it out.

Reverse proxy

Put Caddy, Traefik or nginx in front for TLS. With Caddy the whole config is:

n8n.example.com {
  reverse_proxy 127.0.0.1:5678
}

Keep N8N_PROXY_HOPS equal to the number of proxies in front of n8n, or IP allowlists on webhooks see the proxy's address.

npm packages and Python libraries

Packages for Code nodes are installed in the runners image and allow-listed in its launcher config, not in the main n8n container:

FROM n8nio/runners:2.44.4
USER root
RUN cd /opt/runners/task-runner-javascript && pnpm add lodash dayjs
RUN cd /opt/runners/task-runner-python && uv pip install pandas
COPY n8n-task-runners.json /etc/n8n-task-runners.json
USER runner

In n8n-task-runners.json, set NODE_FUNCTION_ALLOW_EXTERNAL (JavaScript) and N8N_RUNNERS_EXTERNAL_ALLOW (Python) under each runner's env-overrides. Build the image and use it instead of n8nio/runners in the Compose file. Step-by-step with the full config: npm packages in n8n and Python Code node.

Backups

  • Database: docker compose exec postgres pg_dump -U n8n n8n > n8n-$(date +%F).sql, daily, copied off the server.
  • Encryption key: stored in your .env, and in the n8n-data volume. Without it, restored credentials are unreadable.
  • Workflows as files (optional): n8n export:workflow --backup --output=backups/ inside the container gives you JSON for Git.

Test a restore once on a second machine before you rely on it.

Upgrades

  1. Read the release notes for breaking changes (2.0 changed Code node runners, Python, env access and publishing).
  2. Back up the database.
  3. Change N8N_VERSION in .env; both stock images follow it. If you built a custom runners image, rebuild it from the same version first.
  4. docker compose pull && docker compose up -d. n8n runs database migrations on start.

Free vs licensed features

The self-hosted Community edition is free and includes almost all of n8n. It lacks, among others, Variables, environments, external secrets, log streaming, SSO, credential sharing, projects and Git source control; registering your email adds folders, debug in editor and custom execution data (Community edition features). Business and Enterprise licenses unlock the rest.

When to scale

One n8n container handles a lot. When executions queue up or the editor gets slow during heavy runs, move to queue mode: Redis plus worker containers, each with its own runners sidecar.

Self-hosting as a Pipedream replacement

Teams leaving Pipedream often self-host because Pipedream code steps imported any npm package, and n8n Cloud allows only crypto and moment in the Code node. Before you choose, count how many of your steps actually import packages: our converter report lists every import per workflow, and the code step checker does it for one step. The report and 1 workflow are free. Full export: $3 per workflow, $19 minimum — coming soon. Then follow migrate Pipedream to n8n with the instance you just set up.

Not affiliated with Pipedream or n8n.

FAQ

What is the quickest way to run n8n in Docker?
docker volume create n8n_data, then docker run -it --rm --name n8n -p 5678:5678 -v n8n_data:/home/node/.n8n n8nio/n8n. Open http://localhost:5678. Use Docker Compose with Postgres for anything long-lived.
Do I need the n8nio/runners image?
For Python Code nodes and for isolating JavaScript Code nodes properly, yes. External-mode task runners run in an n8nio/runners sidecar whose version must match n8nio/n8n; since 2.0 the main image doesn't include the external-mode runner.
Should n8n use SQLite or Postgres?
SQLite is the default and fine for testing. n8n's Compose guide recommends Postgres for production instances with more than a handful of users or workflows running around the clock.
How much RAM does self-hosted n8n need?
n8n's own Docker page doesn't state minimum hardware for n8n alone. Its full Compose guide, which includes the n8n Assistant sandbox stack, asks for at least 4 GB RAM and 2 vCPUs.
What must I back up?
The database (or the n8n_data volume if you use SQLite) and the encryption key. Credentials can't be decrypted without the key.

Related

Sources