Lessons: n8n Stack¶
Patterns and pitfalls specific to n8n deployment.
Task Runners Config Must Include All Default Fields¶
- n8n task-runners sidecar reads
/etc/n8n-task-runners.jsonfor runner configuration - Custom config REPLACES (not merges) the image default config
- Minimal config with only
runner-type,health-check-server-port,env-overridescauses crash:failed to chdir into configured dir (): chdir : no such file or directory - Root cause: Missing
workdir,command,argsfields → runner can't locate its own binary
Image Default Config (n8nio/runners)¶
Extract via: docker run --rm --entrypoint cat n8nio/runners:latest /etc/n8n-task-runners.json
Key fields per runner:
- workdir: /home/runner (working directory for code execution)
- command: binary path (/usr/local/bin/node for JS, /opt/runners/task-runner-python/.venv/bin/python for Python)
- args: startup arguments
- allowed-env: whitelist of env vars passed to child process
- env-overrides: additional env vars for the runner
Pattern: Override Only What You Need¶
# CORRECT: Full config with custom overrides
echo '{"task-runners":[
{"runner-type":"javascript","workdir":"/home/runner","command":"/usr/local/bin/node","args":["--disallow-code-generation-from-strings","--disable-proto=delete","/opt/runners/task-runner-javascript/dist/start.js"],"health-check-server-port":"5681","allowed-env":["PATH","GENERIC_TIMEZONE","NODE_OPTIONS","NODE_PATH","N8N_RUNNERS_AUTO_SHUTDOWN_TIMEOUT","N8N_RUNNERS_TASK_TIMEOUT","N8N_RUNNERS_MAX_CONCURRENCY","N8N_SENTRY_DSN","N8N_VERSION","ENVIRONMENT","DEPLOYMENT_NAME","HOME"],"env-overrides":{"NODE_FUNCTION_ALLOW_BUILTIN":"crypto","NODE_FUNCTION_ALLOW_EXTERNAL":"moment","N8N_RUNNERS_HEALTH_CHECK_SERVER_HOST":"0.0.0.0"}},
{"runner-type":"python","workdir":"/home/runner","command":"/opt/runners/task-runner-python/.venv/bin/python","args":["-I","-B","-X","disable_remote_debug","-m","src.main"],"health-check-server-port":"5682","allowed-env":["PATH","N8N_RUNNERS_LAUNCHER_LOG_LEVEL","N8N_RUNNERS_AUTO_SHUTDOWN_TIMEOUT","N8N_RUNNERS_TASK_TIMEOUT","N8N_RUNNERS_MAX_CONCURRENCY","N8N_SENTRY_DSN","N8N_VERSION","ENVIRONMENT","DEPLOYMENT_NAME"],"env-overrides":{"N8N_RUNNERS_STDLIB_ALLOW":"json,os,re,datetime,collections,math,urllib,pathlib,typing,csv,io","N8N_RUNNERS_EXTERNAL_ALLOW":""}}
]}' > /mnt/user/appdata/n8n/task-runners.json
# WRONG: Minimal config missing critical fields
echo '{"task-runners":[
{"runner-type":"javascript","health-check-server-port":"5681"},
{"runner-type":"python","health-check-server-port":"5682","env-overrides":{"N8N_RUNNERS_STDLIB_ALLOW":"json,os"}}
]}' > /mnt/user/appdata/n8n/task-runners.json # → CRASH LOOP
Task Runner Should NOT Run as Non-Root¶
- n8nio/runners image expects to run as root (default)
- Setting
user: "99:100"prevents creating/home/runnerworkdir → permission denied - Unlike n8n main container, runners execute isolated code and need full filesystem access
Python Stdlib Allow List¶
N8N_RUNNERS_STDLIB_ALLOWcontrols which Python stdlib modules are available in Code node- Default is empty (only builtins) — must explicitly add:
json,os,re,datetime,collections,math,urllib,pathlib,typing,csv,io - This is NOT an env var — must be in
env-overrideswithin the config JSON (not inallowed-env)
n8n Public API Key Management¶
- API key is a JWT created in n8n UI only (Settings → n8n API → Create API Key)
- No
N8N_PUBLIC_API_KEYenv var exists — n8n does not support programmatic key creation - Store in:
atuin dotfiles var set N8N_PUBLIC_API_KEY '<jwt>' - Retrieve:
atuin dotfiles var list | grep N8N_PUBLIC_API_KEY | sed 's/export N8N_PUBLIC_API_KEY=//' - See [[secret-store-boundary]] for classification rules