What it is
n8n is a node-based workflow automation tool — think Zapier or Make, but self-hosted and with a workflow language that actually exposes code nodes for when the visual editor isn't enough. Workflows are triggered by webhooks, schedules, or polling, and chain together HTTP requests, transformations, conditional logic, and integrations with hundreds of services.
Why I run it
I have one big use case for it right now: the Watchtower update advisor pipeline. Watchtower sends "container X has a new image" notifications to an n8n webhook, n8n parses the report, looks up the GitHub release notes, hands everything to a local LLM for risk analysis, and posts a structured Discord embed with SAFE / REVIEW / WAIT verdicts.
That workflow could have been a Python script. It's an n8n workflow instead because:
- The visual editor is genuinely useful for inspecting the data shape at each step, especially when something goes wrong.
- Webhook receivers, GitHub API calls, Discord embeds, and retries-with-backoff are all built-in nodes — I'd be reimplementing all of them in a script.
- I can iterate on prompt and parsing logic without redeploying anything.
Once n8n is in place for one workflow, the next is almost free — that's the bet.
How I use it
The Watchtower advisor is the only production workflow at the moment. It runs every time any Docker LXC's Watchtower poll finds an update, deduplicates by image hash with a 30-day cache, and only sends Discord notifications for SAFE or REVIEW verdicts (WAIT is silent — no point waking me up for a release the model itself thinks isn't worth acting on).
The LLM call is an HTTP request to LM Studio's OpenAI-compatible endpoint, with the model name swappable via a single field. The model gets a strict system prompt that forbids inventing release notes or recommending automatic updates. Output is a single JSON object the workflow parses for the Discord embed.
The trickiest plumbing detail was the LLM payload. Building it as a JSON string inline blew up — GitHub release notes contain literal newlines and quotes, and n8n's expression evaluator substituting those directly into a string produced invalid JSON. The fix is a Code node that constructs the payload as a real JavaScript object, then the next node JSON.stringifys it. Untrusted multiline text has to flow through JSON.stringify, not through string interpolation.
Setup notes
- Host: a dedicated LXC on the laptop node, native systemd service (not Docker). Pre-dates the Docker-LXC standardization and works fine where it is.
- Reverse proxy: yes, with websockets enabled in the proxy config — n8n's live workflow editor disconnects without them.
- Metrics: n8n's Prometheus endpoint is enabled, scraped by the observability stack.
- Update cadence: manual.
Runbook
- Healthy looks like: workflow editor loads over the proxied hostname, the Watchtower webhook returns 200 for synthetic test payloads, recent executions show no errors.
- Workflow editor disconnects randomly: websockets not enabled on the proxy host.
- Webhook returns 404: workflow is inactive (not published), or the caller is hitting
/webhook/while n8n is only listening on/webhook-test/. Different paths during testing vs production. - GitHub fetch returns
/repos//releases/latest: the parser produced an emptygithub_target. Either the image isn't in the parser's image-to-repo map, or the parser regex didn't match. Both are quick fixes. - No Discord alert for a repeated test payload: the dedup cache suppressed it. Use a fresh fake hash in test payloads each time.
- Where logs live: n8n's Execution history view for per-workflow detail;
journalctl -u n8non the host for the service itself.