Forecaddie

Publish apps through Caddy over a NetBird mesh — approval-gated, no LAN exposure, no hand-edited upstreams.

View the Project on GitHub Forecaddie/forecaddie

Forecaddie — Requirements

Purpose

The coordinator lets any app on any Proxmox host be published through Caddy without the host being reachable on the LAN, without Caddy knowing the host’s address, and with every published app approved and auditable. It replaces per-host Caddy upstream entries with a registry that drives NetBird policies and Caddy configuration from one place.

Four problems with the current setup motivate it:

  1. Every LXC or VM must expose its app on the LAN so Caddy can reach it, so every host is a public-facing service on a flat network.
  2. Caddy’s config names each upstream by LAN hostname or IP, so a network move, re-addressing or host migration means editing Caddy by hand.
  3. Hosts get little or no firewall protection from each other; a compromised app can reach every other app.
  4. Apps rarely speak TLS on the LAN, so traffic between Caddy and the app is plaintext.

Scope

Project name: Forecaddie — the caddie who walks ahead and reports where everything landed. The product is the coordination layer only; NetBird is the transport and Caddy is the edge, both unmodified.

In scope

Out of scope

Glossary

Term Meaning
Peer A host enrolled in NetBird. Has a stable WireGuard key and overlay IP. One peer per LXC or VM.
Peer group A NetBird group. Every enrolled app host gets its own group, assigned by its setup key.
App One service on a peer: protocol + port + public hostname. A peer may host many apps (Docker-host VM).
Binding The approved relationship app → peer → Caddy node(s). Moving an app means destroying one binding and creating another.
Caddy node One Caddy instance (in the reference deployment, a single Caddy LXC). Also a NetBird peer, in group caddy.
Fragment The one Caddy config file per node that the coordinator owns and Caddy imports.
Policy A NetBird access-control rule: source group → destination group, protocol, ports. The coordinator owns all policies touching caddy.
Reconciler The per-Caddy-node process that turns registry state into a fragment and policies and applies them.
Management The NetBird management server (self-hosted) and its API.
IdP The external OIDC provider (Pocket ID, Authentik, Keycloak, or similar).

Actors

Actor Role in the system
Admin Full control: approve, destroy, manage Caddy nodes, manage roles.
Operator Register apps, enable/disable approved apps, view everything. Cannot approve or destroy. Intended for agents.
Viewer Read-only access to status and audit.
Automation agents Machine clients of the API. Act as Operator by default; Admin only by explicit grant. Authenticate as OIDC clients, never with shared passwords.
App host A peer. Runs the app and the NetBird client. Has no coordinator software unless the optional enrollment helper is used.
Caddy node Runs Caddy and the reconciler. Reads registry state, writes its fragment, reloads Caddy.
NetBird management Source of truth for peers, groups, policies and peer status. The coordinator writes groups and policies through its API.
IdP Sole source of human and agent identity for the coordinator.

Functional requirements

An approved app is reachable only from its Caddy node(s), only on its declared port, and only while approved. Everything below serves that sentence.

Enrollment

App registry

Approval lifecycle

Caddy reconciliation

L4 passthrough

Health and status

Multiple Caddy nodes and sites

Management surface

API for agents

Audit

Non-functional requirements

Security

Availability and degraded modes

Performance

Operability

Observability

Portability

Constraints and assumptions

Rejected alternatives

Alternative Why not
Pangolin Closest existing match: outbound WireGuard tunnels (Newt), dashboard, OIDC, Integration API, clustering. But it is built on Traefik and brings its own request-auth layer (Badger), which would duplicate caddy-security and the external IdP and require abandoning the existing Caddy build. Dual AGPLv3 / commercial license.
frp Solid transport with client IDs, OIDC client-credentials auth and a dashboard, but it is a tunnel, not a mesh: no per-peer firewall policy, no group model, and a coordinator would still have to be written on top. NetBird gives identity, policy and enrollment for free.
Gopher Caddy + rathole in one binary with a web UI and host agent. Pre-1.0, no OIDC, owns Caddy rather than cooperating with an existing build.
Cloudflare Tunnel Previously evaluated. Hands TLS termination and access control to a third party; the point here is self-hosting.
Split-horizon DNS to app hosts Removes hairpinning but bypasses Caddy, so no central TLS, no auth gate, and apps back on the LAN. Rejected per the purpose section.
NetBird subnet routes as inter-site glue The configuration that caused trouble before. Out of scope; the existing site-to-site VPN keeps that role.
Building the tunnel and agent from scratch Most control, most work, and reproduces what NetBird already does well.

Open questions