Architecture Alternatives — Research & Proposal¶
Date: 2026-07-05 Question: Is Ansible + Cloudflare Tunnels the right architecture for this fleet?
Revised 2026-07-05: two recommendations below were rejected after review — end users do need per-device Node-RED dashboards (to fine-tune parameters), and updates stay push-based Ansible with on-demand connect (no
ansible-pull). See19-NETBIRD-HEADSCALE-CLOUDFLARE-COMPARISON.mdfor the updated access-layer decision.
Requirements (restated)¶
- Fleet of ~100 remote Raspberry Pi devices connecting to MQTT (EMQX, mTLS).
- A way to connect back to devices for updates and management.
- Node-RED with two user levels: admins (full editor) and users (restricted, no access to all paths).
Current Architecture — Assessment¶
| Component | Current | Verdict |
|---|---|---|
| MQTT | Devices → EMQX outbound, mTLS client certs | ✅ Solid, keep as-is |
| Remote access | 1 Cloudflare Tunnel per device, 3 hostnames each (dashboard / editor / SSH) | ⚠️ Works but heavy: 300 DNS records, 100 cloudflared configs, Terraform churn per device |
| User auth | Cloudflare Access: Google IdP (users), GitHub IdP (admins) | ⚠️ Free tier caps at 50 seats; beyond that $7/user/mo for all users |
| Node-RED "RBAC" | settings.js middleware sniffs Host header to decide dashboard vs editor |
❌ Not real authorization. Anyone who reaches port 1880 with the right Host header gets the full editor. Security depends 100% on Cloudflare staying in front. |
| Updates | Ansible push over CF tunnel SSH | ⚠️ Requires devices online during push; offline devices never converge; slow at 100 devices |
Core insight: devices already maintain an outbound connection (MQTT). The management plane can also be outbound-only — the per-device inbound tunnel is only truly needed for interactive admin sessions (SSH, editor), not for updates or for end users.
Options Researched¶
Option A — Keep architecture, harden it (low effort, ~$0)¶
- Replace Host-header middleware with real Node-RED
adminAuth: admins get"*"permission, users get"read"or no editor account at all. Editor then requires login even if Cloudflare is bypassed. (Node-RED securing docs) - Switch Ansible push →
ansible-pullon a systemd timer. Devices clone the repo and converge themselves; offline devices catch up when they reconnect. Keep push mode for ad-hoc debugging. - The git-based Node-RED project deployment already implemented (see
NODERED-GIT-DEPLOYMENT-SUMMARY.md) becomes the flow-update channel — device pulls, restarts service, health-checks.
Fixes: real two-level auth, offline convergence. Keeps: tunnel sprawl, CF seat limits.
Option B — Mesh VPN for the management plane (Tailscale / Headscale / NetBird)¶
Replace 100 per-device tunnels with one mesh network; admins SSH / open editor over the mesh.
- Tailscale (SaaS): unlimited user devices, but tagged resources (servers/IoT) — 50 included, then $1/mo each → ~$50–100+/mo at 100 devices. Zero coordination-server maintenance. (pricing analysis)
- Headscale (self-hosted, free): official Tailscale clients, own coordination server. No per-device cost, but you operate the server.
- NetBird (self-hosted, free): fully open source stack incl. dashboard and IdP integration. (comparison)
Only solves the admin access path. End users still need something for dashboards — which leads to Option C.
Option C — Centralize user-facing dashboards (architectural fix)¶
Users never touch device-local Node-RED at all.
- Devices already publish state to EMQX. Build one central dashboard (central Node-RED instance or Grafana) on the VPS next to EMQX, subscribed to all device topics.
- End users get accounts on the central dashboard only. Grafana gives real RBAC (Viewer/Editor/Admin, per-folder permissions) for free; a central Node-RED + FlowFuse Dashboard 2.0 multi-user is an alternative.
- Device-local Node-RED editor becomes an admin-only tool — one hostname per device instead of three, or reachable only over the mesh VPN.
Eliminates: CF Access seat-count problem (users hit one app, not 100), the Host-header middleware entirely, and 2/3 of the DNS/ingress sprawl. Command/control back to devices flows through MQTT topics with EMQX ACLs.
Option D — FlowFuse (purpose-built Node-RED fleet platform)¶
The exact product category for this use case: Device Agent connects outbound (no tunnels needed), snapshot-based flow deployment to the whole fleet, remote editor access through the platform, audit logs, and true RBAC (viewer/member/owner roles).
- Cost is the blocker: Cloud Pro ≈ $3,700+/mo at 100 devices ($425 base + ~$35/extra instance). Self-hosted open source lacks RBAC and team features; Enterprise self-hosted is custom-priced. (pricing)
- Still needs a separate SSH path for OS-level work (mesh VPN or a few retained tunnels).
Right product, wrong price point for this fleet unless the business case supports it.
Option E — OS-level fleet platforms (balena / Mender)¶
- balena: balenaOS + containers, full device lifecycle, per-device pricing, hard vendor lock-in to balenaOS. (overview)
- Mender: open-source A/B OTA updates for the OS, works on Raspberry Pi OS via mender-convert; remote terminal is a paid add-on. (fleet management)
Neither addresses Node-RED user levels. Worth revisiting only if atomic OS updates with rollback become a hard requirement. (comparison)
Recommendation¶
Ansible + Cloudflare Tunnels is not wrong — but it is doing two jobs it shouldn't: serving end users per-device, and pushing updates. Restructure into three planes:
| Plane | Today | Proposed |
|---|---|---|
| Data | MQTT → EMQX (mTLS) | Unchanged |
| Users | CF Access → 100 device dashboards, Host-header middleware | One central dashboard (Grafana or central Node-RED) behind CF Access. Users exist only there. |
| Admin/updates | CF tunnel SSH + Ansible push | ansible-pull + git flow deployment (self-converging). Interactive access: keep CF tunnels admin-only (1 hostname/device) or migrate to Headscale/Tailscale. |
Phased migration¶
- Phase 1 (defense in depth, days): add
adminAuthto device Node-RED; admins"*", no user accounts. Editor safe even without Cloudflare. - Phase 2 (update plane, ~1 week): convert existing roles to
ansible-pull+ systemd timer; git-based flow deployment (already half-built) as the standard channel. Devices report deploy status over MQTT. - Phase 3 (user plane, 1–2 weeks): stand up central dashboard on the EMQX VPS; move end users there; delete per-device dashboard hostnames + middleware from
settings.js. - Phase 4 (optional): replace per-device tunnels with Headscale/Tailscale for admin SSH/editor; keep 2–3 CF tunnels as break-glass.
Cost comparison (100 devices)¶
| Option | Monthly cost | Effort |
|---|---|---|
| Current, >50 users | $350+ (CF Access seats) | — |
| Recommended (C + A) | ~$0 (CF free tier: users hit 1 app; VPS already exists) | 3–4 weeks phased |
| + Tailscale mesh | ~$50–100 | +1 week |
| FlowFuse Cloud | ~$3,700 | low |
| balena | ~$100–200+ (per-device) | high (OS migration) |