Skip to content

n8n

n8n is used for workflow automation and integration tasks.

  • Service Name: n8n
  • Default Port: 5678
  • URL: https://n8n.lage-beauty.de/
  • Credentials: Stored in Bitwarden (or configured via environment variables)
  • Cursor MCP: n8n MCP + Cursor — instance-level MCP for workflow search/build/trigger from the IDE

Encryption key (required)

n8n encrypts stored credentials with N8N_ENCRYPTION_KEY. If the key changes, every credential fails with bad decrypt.

  1. Set N8N_ENCRYPTION_KEY in production .env (store in Bitwarden).
  2. Never run the main n8n container as root — use the image default user (node). Execute Command runs in that container as UID 1000.
  3. Do not recreate the n8n_data volume without the same key.

Shared invoices volume permissions are fixed on service-invoice startup (root:1000, 2775) so n8n can mkdir under /home/node/.n8n-files/invoices/.

Restart & health

Compose (docker-compose.yml / docker-compose-portainer.yml) sets:

  • restart: unless-stopped on n8n and task runners
  • n8n healthcheck: GET http://127.0.0.1:5678/healthz
  • runners healthcheck: GET http://127.0.0.1:5680/healthz (launcher; idle runners wait for task offers — that is normal)
  • runners wait for n8n to be healthy before starting
  • sandbox-api healthcheck: GET http://127.0.0.1:8080/healthz inside the container ({"status":"ok"}). n8n does not wait for it.

n8n Assistant (self-hosted sandbox)

n8n Assistant needs a model key, a sandbox, and optionally web search. We host n8n’s own sandbox (n8n-sandbox), not Daytona. Official setup: Set up n8n Assistant.

This is not the Code-node task runner (n8n-runner / n8nio/runners). Assistant code runs in sandbox-runner-1 (privileged Docker-in-Docker).

Services (internal only)

Service Role
sandbox-certs One-shot mTLS into volume sandbox-tls, then exits
sandbox-api Control plane; n8n talks HTTP on :8080
sandbox-runner-1 Privileged DinD; keep this Compose service name (cert SAN)
searxng Optional web search for the assistant. Settings are written inside the container (JSON enabled). Do not bind-mount settings.yml — a missing host file becomes a directory and SearXNG crash-loops.

Never publish sandbox 8080/9090 or SearXNG, and do not put them behind nginx / NPM. Unlike n8n or docs, nothing in the browser talks to the sandbox — the n8n container calls http://sandbox-api:8080 on app-network. Prometheus already uses host 9090; sandbox gRPC stays on the Compose network.

Secrets (Portainer / .env / Bitwarden — never commit)

The model is configured in n8n Settings → AI Assistant. Compose already sets N8N_SANDBOX_SERVICE_URL=http://sandbox-api:8080 — it is not an .env or nginx URL. n8n starts without waiting for the sandbox.

# n8n ↔ sandbox-api (must match SANDBOX_API_KEYS inside the API container)
N8N_SANDBOX_SERVICE_API_KEY=<long random, 32+ chars>

# sandbox-api ↔ sandbox-runner-1 (must be a different secret)
SANDBOX_API_RUNNER_REGISTRATION_TOKEN=<long random>
SANDBOX_API_RUNNER_API_KEY=<different long random>

# SearXNG (optional web search; attach in the AI Assistant UI)
SEARXNG_SECRET=<long random>

Generate secrets with openssl rand -hex 32. N8N_SANDBOX_SERVICE_API_KEY and SANDBOX_API_RUNNER_API_KEY must not be the same.

To enable agents later: N8N_ENABLED_MODULES=instance-ai,agents (Preview; knowledge base on self-hosted wants Daytona — skip that unless you switch).

Verify after deploy

From another container on app-network:

wget -qO- http://sandbox-api:8080/healthz
# {"status":"ok"}

Open the n8n editor and confirm n8n Assistant appears and responds. The DinD runner pulls ghcr.io/n8n-io/n8n-sandbox-service-sandbox on first use — first reply can be slow.

Disable

N8N_DISABLED_MODULES=instance-ai, or turn the assistant off in Settings → AI Assistant.

Task runners (Code nodes)

Code / Python nodes run in external runners (n8nio/runners). The launcher container stays up; the JS child process is what actually runs Code nodes.

Idle shutdown → “Task request timed out”

Default N8N_RUNNERS_AUTO_SHUTDOWN_TIMEOUT=15 kills the JS process ~15s after the last Code task. Logs look like:

Waiting for launcher's task offer to be accepted...
ERROR [runner:js] ExperimentalWarning: localStorage is not available...
Runner process exited on idle timeout

The localStorage line is harmless Node noise (often logged as ERROR). The idle exit is the problem: with a large heap (N8N_RUNNERS_MAX_OLD_SPACE_SIZE), cold-starting the child is slow, and if both runners are busy or still starting, main n8n fails with Task request timed out (Code node was not matched to a runner).

Compose defaults:

Variable Where Default Purpose
N8N_RUNNERS_AUTO_SHUTDOWN_TIMEOUT runners 0 Keep JS runner warm (0 = never idle-exit)
N8N_RUNNERS_MAX_CONCURRENCY runners 5 Concurrent Code tasks per runner container
N8N_RUNNERS_TASK_REQUEST_TIMEOUT main n8n 600 Seconds to wait for a free runner offer

Redeploy both n8n and n8n-runner / n8n-runner-2. After restart you should not see repeated exited on idle timeout between Code runs. If timeouts persist under load, add another runner replica or space heavy Code workflows.

Memory / OOM

If a node fails with “Increase the memory available to the task runner with N8N_RUNNERS_MAX_OLD_SPACE_SIZE”:

  1. Set on runner containers (not only main n8n):
  2. N8N_RUNNERS_MAX_OLD_SPACE_SIZE=24576 (default; override via env) — used by the launcher for the JS runner process
  3. NODE_OPTIONS=--max-old-space-size=${N8N_RUNNERS_MAX_OLD_SPACE_SIZE} — same heap for the child process
  4. Keep runner mem_limit above that heap (compose default 28g).
  5. Main n8n: heap default 12288 MB (N8N_NODE_MAX_OLD_SPACE_SIZE), mem_limit default 16g.
  6. Redeploy / recreate the runner containers (not only main n8n), then retry. For huge item batches, also split with a Loop node.

Portainer will show unhealthy and Docker will restart exited containers. Pair with Uptime Kuma on https://n8n.lage-beauty.de/healthz for alerts.

Recover the key from the volume (if still intact):

docker exec n8n cat /home/node/.n8n/config

Copy encryptionKey into .env as N8N_ENCRYPTION_KEY, redeploy n8n only (not a fresh n8n_data volume).

If the config was overwritten and you have no backup, re-enter credentials in the n8n UI manually.