Three workflows that connect the services from episodes 1 to 7: a Forgejo push becomes a phone notification, an Uptime Kuma outage writes and closes its own incident page in Outline, and a nightly schedule writes one ops digest. The point of the episode is the split between webhooks and the editor. Caddy publishes only the webhook path prefixes under the n8n host and answers an empty 404 for everything else, so the editor stays reachable over the mesh from episode 5 and never gets a login page on the open internet. Also in here: - n8n pinned to a version instead of :latest, after episode 7 showed that every restore is otherwise an upgrade - the 2.x environment that actually matters, including the file access restriction pointed at one directory rather than the root - the Forgejo allowed host list, without which a webhook to a private address is silently dropped - fixed subnets for both docker networks, after a restore handed them out in the other order and quietly broke the mesh route - a note on the encryption key: it decrypts every stored credential, it lives in the n8n volume, and a backup without it restores a hub that cannot log in Claude-Session: https://claude.ai/code/session_01Md8yhBKp5e7xMmCyxoEgdm
5.6 KiB
Automation (episode 8)
Three workflows that wire the other services together. They are the reason n8n sits in this compose file, and they are all built from HTTP requests, so nothing here depends on a vendor connector that might change next quarter.
| File | Trigger | What it does |
|---|---|---|
a-deploy-notifier.json |
Forgejo push webhook |
Turns a push into one push notification: repo, branch, author, commit subject, link to the commit. |
b-incident-responder.json |
Uptime Kuma webhook notification | On DOWN: opens an incident document in the wiki and sends a high priority alert with a link to it. On UP: finds that document, appends the downtime, renames it to "Resolved", sends the all clear. |
c-nightly-ops-digest.json |
Schedule, 07:00 daily | Reads commits of the last 24 hours from Forgejo, availability from the Uptime Kuma status page, and the last restic snapshot from the episode 7 backup log. Writes one wiki document and sends a one line summary. |
The point of this episode
Automation needs webhooks. Webhooks look like they need a public address. They do not, at least not for everything:
- Senders that live on the box (Forgejo, Uptime Kuma) call
http://n8n:5678/webhook/...straight over the docker network. That traffic never touches the internet, needs no certificate and no port. - Senders out on the internet need one public path, and exactly one. The
Caddyfile publishes
/webhook/*,/webhook-test/*and/webhook-waiting/*undern8n.<DOMAIN>and answers an empty 404 for everything else. The editor, the REST API and the login page are not reachable from outside at all. - You reach the editor over the mesh from episode 5, on the container address, on plain HTTP inside the tunnel.
Two different 404s tell you which layer answered: an empty body is Caddy refusing the path, a JSON body is n8n saying the webhook is not registered.
Importing them
- Create the three credentials in n8n first. Their names in these files are
ntfy (n8n user),Outline APIandForgejo API; keep the names and n8n will attach them on import.ntfy (n8n user): Basic Auth, an ntfy user with write access to the topics.docker compose exec -e NTFY_PASSWORD='...' ntfy ntfy user add --role=user n8nthenntfy access n8n alerts write-onlyand the same fordeploys.Outline API: Header Auth, nameAuthorization, valueBearer ol_api_...(Outline: Settings, API tokens).Forgejo API: Header Auth, nameAuthorization, valuetoken ...(a read-only token is enough:read:repository).
- Import each JSON, then replace the placeholders:
REPLACE_WITH_YOUR_CREDENTIAL_IDis filled in when you pick the credential in the node.REPLACE_WITH_YOUR_OUTLINE_COLLECTION_IDis the collection the incident and digest documents are written into. Create a collection, open it, the id is incollections.infoor in the URL of the collection settings.REPLACE_ON_IMPORTis the webhook id; n8n generates a fresh one.ops.example.comappears in the two links that go into push messages.
- Activate all three.
Wiring the senders
Forgejo, per repository: Settings, Webhooks, Forgejo, URL
http://n8n:5678/webhook/forgejo-push, content type application/json,
event Push.
Forgejo refuses to deliver to a private address unless you tell it not to. That
is the FORGEJO__webhook__ALLOWED_HOST_LIST: "external,n8n" line in
compose.yml. Without it the push is accepted, the workflow never runs, and the
only trace is one line in the Forgejo log:
webhook can only call allowed HTTP servers (check your webhook.ALLOWED_HOST_LIST setting), deny 'n8n(172.18.0.3:5678)'
Uptime Kuma: Settings, Notifications, add a Webhook notification, URL
http://n8n:5678/webhook/kuma-incident, content type application/json, then
attach it to the monitors you want incident documents for. Kuma sends one POST
per state change, with heartbeat.status 0 for down and 1 for up.
The one file that matters for backups
/home/node/.n8n/config holds encryptionKey. Every credential in the database
is encrypted with it. The database alone is worth nothing:
Credentials could not be decrypted. The likely reason is that a different
"encryptionKey" was used to encrypt the data.
The workflows survive, the credentials do not, and n8n quietly writes a fresh
key on first start when the file is missing, so nothing looks broken until a
workflow runs. The episode 7 backup covers the whole n8n_data volume, so the
key is in the snapshot. Verify it rather than assume it:
restic ls latest | grep n8n_data/_data/config
If you set N8N_ENCRYPTION_KEY in the environment instead, that value wins and
the file is never written; then the key belongs in your password manager, and it
has to survive independently of the box.
Notes
c-nightly-ops-digest.jsonreads/backup/backup.log, whichcompose.ymlbind-mounts read-only from episode 7. Without that file the digest reports "no backup log found" and still writes the document.N8N_RESTRICT_FILE_ACCESS_TO: /backupis what allows that read. Without the variable, 2.x answers "Access to the file is not allowed." for every path. Do not widen it to/: a workflow could then read the encryption key.- The digest matches commit responses to repositories by position. That holds because the HTTP node returns exactly one item per input item when the response format is text and errors continue.
- These workflows use plain HTTP Request nodes on purpose. A Forgejo node, an Outline node and an ntfy node either do not exist or would add a dependency for three URL calls.