guides/n8n
Automation · Workflows · Self-hosting

n8n · Docker

Self-hosted workflow automation with a visual node editor. Connect APIs, databases and services into pipelines without hosting it with a third party. This guide runs n8n as a Docker Compose stack with PostgreSQL behind it.

Fair-code license Docker / Linux n8n GmbH
PostgreSQLrecommended backend
Webhooksrequire a public URL
Queuemode for scaling

Before you start

n8n can run with an internal SQLite file, but for anything serious you should use PostgreSQL — it's more robust with many workflows and concurrent runs. Prerequisite: a working Docker installation.

Prerequisite: Follow the Docker guide first. For webhooks (e.g. your phishing analyzer or RSS triggers) n8n needs a public URL via a reverse proxy — see Reverse proxy.

Compose stack

PostgreSQL

A complete stack with n8n + Postgres on an isolated network. Put it in a folder, e.g. ~/n8n/.

compose.yaml
services:
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - pg_data:/var/lib/postgresql/data
    networks: [ internal ]
    healthcheck:
      test: [ "CMD-SHELL", "pg_isready -U $${POSTGRES_USER}" ]
      interval: 10s
      retries: 5

  n8n:
    image: docker.n8n.io/n8nio/n8n:latest
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_DATABASE: ${POSTGRES_DB}
      DB_POSTGRESDB_USER: ${POSTGRES_USER}
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
      N8N_HOST: ${N8N_HOST}
      N8N_PORT: 5678
      N8N_PROTOCOL: https
      WEBHOOK_URL: https://${N8N_HOST}/
      GENERIC_TIMEZONE: Europe/Copenhagen
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
    ports:
      - "127.0.0.1:5678:5678"
    volumes:
      - n8n_data:/home/node/.n8n
    networks: [ internal, web ]

volumes:
  pg_data:
  n8n_data:

networks:
  internal:
    internal: true
  web:
Postgres sits on internal (no internet), n8n on both internal and web. The port is bound to 127.0.0.1 — reachable only via the reverse proxy.

Configuration (.env)

Keep secrets in a .env file next to compose.yaml — never in the YAML itself.

.env
POSTGRES_USER=n8n
POSTGRES_PASSWORD=# generate: openssl rand -base64 24
POSTGRES_DB=n8n
N8N_HOST=n8n.defencia.dk
N8N_ENCRYPTION_KEY=# generate: openssl rand -hex 32
Critical — encryption key: N8N_ENCRYPTION_KEY is used to encrypt all stored credentials. If it isn't set explicitly, it's auto-generated in the volume — and if you lose the volume without having the key, all credentials are irrecoverably lost. Store the key in Bitwarden alongside your other secrets.
Important env variableFunction
N8N_ENCRYPTION_KEYEncrypts stored credentials (save it!)
WEBHOOK_URLPublic base URL n8n builds webhook addresses from
N8N_HOST / N8N_PROTOCOLHostname and protocol behind the proxy
GENERIC_TIMEZONETime zone for cron/schedule nodes
N8N_SECURE_COOKIESet true behind HTTPS (default)
EXECUTIONS_DATA_PRUNEAuto-clear old execution logs
N8N_RUNNERS_ENABLEDEnable task runners (isolated code execution)

Reverse proxy (Nginx)

n8n uses websockets for the live editor — the proxy block must handle upgrade headers.

/etc/nginx/sites-available/n8n.defencia.dk
server {
    listen 443 ssl;
    server_name n8n.defencia.dk;

    # ssl_certificate ... (Certbot inserts)

    location / {
        proxy_pass http://127.0.0.1:5678;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 3600s;
    }
}
The long read_timeout prevents long-running workflows from being cut off by the proxy.
Cert: sudo certbot --nginx -d n8n.defencia.dk — or use your existing wildcard for *.defencia.dk.

Scaling — queue mode

advanced

For many concurrent or heavy workflows, n8n can run in queue mode: a main instance handles the UI/webhooks, and separate worker containers run the jobs via Redis.

Components: Add a redis service, set EXECUTIONS_MODE=queue and QUEUE_BULL_REDIS_HOST=redis on both main and workers, and start n workers with the command n8n worker. Each worker pulls jobs from the queue — scale by increasing the number of worker containers.
When: For your current use (RSS screening, reports, WOD generator) single-instance is fine. Queue mode only becomes relevant if you hit execution bottlenecks or want to isolate heavy AI calls from UI responsiveness.

Operation

Start & follow logs
docker compose up -d
docker compose logs -f n8n
Update to latest
docker compose pull
docker compose up -d
Always read the release notes — n8n can have breaking changes between majors.
Backup database
docker compose exec postgres \
  pg_dump -U n8n n8n > n8n_$(date +%F).sql
Export workflows to a file
docker compose exec n8n \
  n8n export:workflow --all --output=/home/node/.n8n/backup.json
For your backup stack: Take both a pg_dump (data + credentials, encrypted with the encryption key) and a workflow export (portable JSON). Send both to restic/pCloud. Remember: without the encryption key, the credentials in the dump are useless.

Hardening

ControlRecommendation
ExposureBind to 127.0.0.1, only via Nginx + HTTPS
DatabasePostgres on the internal network, no host port
Encryption keySet explicitly, store in Bitwarden, back up separately from the volume
LoginEnable user management/2FA; Fail2ban jail on the login endpoint
WebhooksUse webhook auth/tokens; validate payloads in the workflow
Task runnersEnable to isolate Code-node execution
Execution dataPrune old logs so they don't accumulate sensitive data
AccessConsider VPN-only access to the editor; webhooks separately
Remember the Docker+UFW trap: Bind n8n to 127.0.0.1:5678 — not 0.0.0.0 — otherwise the port can be exposed to the internet past UFW. See the Docker guide for details.