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.
- Set
N8N_ENCRYPTION_KEYin production.env(store in Bitwarden). - Never run the main
n8ncontainer as root — use the image default user (node). Execute Command runs in that container as UID 1000. - Do not recreate the
n8n_datavolume 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-stoppedonn8nand 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
n8nto be healthy before starting - sandbox-api healthcheck:
GET http://127.0.0.1:8080/healthzinside 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”:
- Set on runner containers (not only main
n8n): N8N_RUNNERS_MAX_OLD_SPACE_SIZE=24576(default; override via env) — used by the launcher for the JS runner processNODE_OPTIONS=--max-old-space-size=${N8N_RUNNERS_MAX_OLD_SPACE_SIZE}— same heap for the child process- Keep runner
mem_limitabove that heap (compose default28g). - Main
n8n: heap default12288MB (N8N_NODE_MAX_OLD_SPACE_SIZE),mem_limitdefault16g. - 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.