Skip to content

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.json for runner configuration
  • Custom config REPLACES (not merges) the image default config
  • Minimal config with only runner-type, health-check-server-port, env-overrides causes crash: failed to chdir into configured dir (): chdir : no such file or directory
  • Root cause: Missing workdir, command, args fields → 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/runner workdir → permission denied
  • Unlike n8n main container, runners execute isolated code and need full filesystem access

Python Stdlib Allow List

  • N8N_RUNNERS_STDLIB_ALLOW controls 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-overrides within the config JSON (not in allowed-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_KEY env 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