Back to n8n: fixed workflows for the routine stuff, and an AI agent only for questions
n8n on a Raspberry Pi with no open ports: a daily Telegram digest, 3D printer alerts and a six-tool agent I can talk to from my phone.
A few weeks ago I wrote about why I removed n8n and an AI agent from my Raspberry Pi: the agent burned through the model’s free requests before lunch. The takeaway was that routine work doesn’t need AI. Well, n8n is back, but this time built around that lesson: fixed workflows for whatever is always the same, and a small agent with six tools only for when I want to ask something. All on an 8 GB Pi 5, with not a single port opened.
Here’s how I set it up and, more importantly, where I tripped, which is the part tutorials leave out.
1. Do you need another Pi?
That was my first question, because Raspberry Pis aren’t cheap anymore. The answer was to look before buying:
free -h
With Gitea, Vaultwarden, Uptime Kuma, Homepage, Portainer and my command center running, the Pi was using 1.3 GB out of 8. n8n takes a few hundred megs, and the AI runs over an API, not on the Pi. With everything in place I’m at 2.2 GB. No need to buy anything.
If you ever want local AI or a VM lab, a refurbished business mini PC gives you far more than another Pi for about the same money.
2. n8n in Docker, reachable only over Tailscale
n8n insists on HTTPS for its session cookie: over http://192.168.1.50:5678 it won’t even let you log in. Instead of fighting that, I did what I did with Vaultwarden: listen on localhost only and let Tailscale provide the certificate.
n8n:
image: docker.n8n.io/n8nio/n8n:latest
container_name: n8n
restart: unless-stopped
environment:
N8N_HOST: my-pi.your-net.ts.net
N8N_PROTOCOL: https
WEBHOOK_URL: "https://my-pi.your-net.ts.net:10000/"
GENERIC_TIMEZONE: Europe/Madrid
N8N_RUNNERS_ENABLED: "true"
EXECUTIONS_DATA_PRUNE: "true"
EXECUTIONS_DATA_MAX_AGE: "336" # 14 days of history
ports: ["127.0.0.1:5678:5678"]
volumes: [./n8n:/home/node/.n8n]
sudo tailscale serve --bg --https=10000 http://127.0.0.1:5678
A note for backups: n8n keeps everything in SQLite. Copying the live file can give you a corrupt backup, so I added a clean dump to my nightly backup to the NAS before the rsync:
sqlite3 /home/pi/servicios/n8n/database.sqlite ".backup /home/pi/servicios/n8n/backup-database.sqlite"
3. The routine stuff: two workflows, no AI
Daily digest at 8:30. A schedule trigger, five HTTP requests to the command center API (services, last backup, Pi status, coursework deadlines and to-dos), a Code node that builds the message, and the Telegram node. If something is wrong the message starts with ⚠️; if everything is fine, with ✅. For this I added a /api/sistema endpoint to the command center with disk, RAM and temperature, because n8n, inside its container, can’t see the Pi’s own stats.
3D printer alerts. Every minute it reads the printer status and compares it with the previous one, stored with $getWorkflowStaticData('global'). It pings me when a print starts, finishes, pauses or errors out. Two things I learned:
- Static data only persists in production executions (with the workflow published). When you test it by hand it looks like it remembers nothing.
- One run per minute is 1,440 executions a day. I set
saveDataSuccessExecution: noneso only failures are kept. In the database they briefly show up asrunningwith a deletion date and then vanish: that’s normal, they’re not stuck.
4. The agent: few tools, well described
For questions (“how’s the print going?”, “what’s left to hand in?”, “add ‘check the NAS’ to my list”) I do use AI. The workflow is an AI Agent node with:
- Model: Qwen 3.8 27B free via OpenRouter, with Gemma 4 31B as a fallback and three retries.
- Memory of the last 10 messages.
- Six tools, all HTTP requests to my command center: service status, backup, system, printer, deadlines and to-dos (list, create, mark done). None of them delete anything.
Each tool’s description is what the model reads to decide which one to call, so I wrote them as if for a new colleague: “Returns the result of the last nightly backup (status, date, text). No parameters.” To create to-dos, the body uses $fromAI(), so the model fills in the text and the area:
{{ JSON.stringify({
texto: $fromAI('texto', 'Task text, one sentence', 'string'),
area: $fromAI('area', 'Area: Casa, ASIR, Web…', 'string')
}) }}
What about the 50 requests a day that killed it last time? They’re still there, but now the agent only works when I ask, and each question costs two or three requests, not twenty. What I did hit was “Rate limit reached” mid-conversation: :free models share capacity with everyone. Hence the fallback model and the retries.
5. Talking to it from Telegram without opening ports
n8n’s Telegram Trigger node needs a public webhook, and I didn’t want to expose anything. Having n8n poll Telegram every few seconds wasn’t an option either: thousands of empty executions a day.
The fix was a small Python bridge, about 150 lines, running as a systemd service:
- It long-polls
getUpdateswithtimeout=50: the connection goes out from the Pi and stays open until a message arrives, so replies are instant. - It ignores any chat that isn’t mine.
- It forwards the message to a local n8n webhook protected by a header (
X-Relay-Token). Without the key, n8n returns 403. - That workflow calls the agent through Execute Workflow and returns the answer, which the bridge sends back to Telegram.
Only the bridge has the bot token, in a file with 600 permissions. The agent’s bot is separate from my Uptime Kuma alerts bot, and I kept it that way on purpose: if n8n goes down, I want to be told by a bot that doesn’t depend on n8n.
6. Where I tripped
- The credential I couldn’t use. I already had an OpenRouter credential for n8n’s built-in assistant. Using it in my workflow gave “does not have access to the credential”. That credential has
usageScope: instanceand is reserved for the assistant. You need a regular one; it can use the same key. - Importing unpublishes. I edit workflows as JSON and import them with
n8n import:workflow, but importing leaves the workflow unpublished. You have to publish it again (n8n publish:workflow --id=...) and restart the container, because CLI changes aren’t picked up live. And after a restart, webhooks take a few seconds longer than the healthcheck to be ready. - The model and dates. The agent told me a deadline was “today” when it was five days away. Fixed by putting the date in ISO format in the instructions (
2026-10-03) plus an explicit rule: “count the days left; only say ‘today’ if the date matches”. - Stale data. I asked about my courses and it only knew the codes, because the API didn’t have the full names. I fixed it at the source, not in the prompt. And while reviewing its answers I “corrected” some dates that were actually right, trusting an old copy. Before blaming the agent, check where it gets its data, and be clear about which source wins.
What not to do
- Hand the agent things that are always the same. A daily digest doesn’t need a language model.
- Give it tools that delete or change important things “just in case”.
- Open a port for a Telegram webhook when long polling does the same without exposing anything.
- Trust a free model with dates and arithmetic without serving it the data properly.
Summary
- Check RAM before buying hardware: an 8 GB Pi 5 has plenty of room for n8n.
- n8n on localhost plus HTTPS from Tailscale Serve: no open ports and no cookie headaches.
- Fixed workflows for the routine stuff (daily digest, printer alerts), no AI.
- An agent with few tools, well described and none that delete, for whatever changes every time.
- Telegram over long polling with a tiny bridge: instant replies, nothing exposed.
Last time I removed the agent because I was trying to make it do everything. This time it only answers questions, and that’s what makes it worth having.
Did it stick?
Three quick questions. Each right answer is worth 10 XP.