How this site is served

The journal is a static Astro build: no login, no admin page, nothing a visitor can change. Caddy serves it on loopback inside CT 410, one unprivileged Ubuntu 24.04 LXC in VLAN 50 (DMZ), and cloudflared, as a separate service with its own user, carries public requests from Cloudflare. Each release is a folder named after its commit, so switching back is one symlink change.

Public site
  1. Public readerany browser · Example
  2. Cloudflare WAFWAF + tunnel · In use
  3. WANISP, behind CGNAT · Configured
  4. Laptop uplinkonboard NIC · Configured
  5. WAN bridgevmbr0 · Configured
  6. Sophos FirewallVM 102 · Running
  7. VLAN trunkvmbr2 · VLAN-aware · Configured
  8. DMZ · journalCT 410 · Running
Lab map · addresses and hostnames left out The public path, separate from private access: Cloudflare's WAF and the named tunnel hand requests to cloudflared, which reaches Caddy on 127.0.0.1:8080. No WAN port opens, and the DMZ container cannot start connections into the other roles. Open the full lab console

The request path

  1. A reader opens portfolio.sirkits.tech. Cloudflare answers for the sirkits.tech zone and applies the WAF rules scoped to that hostname.
  2. Cloudflare sends the request down a named, remotely managed tunnel.
  3. cloudflared, running as a systemd service in the DMZ LXC, receives it over its outbound connection on port 7844.
  4. It forwards to http://127.0.0.1:8080 in the same container, where Caddy serves the current release.

Two services, each with the least it needs

  • Caddy runs as its own user, listens on 127.0.0.1:8080 only, reads the release folder read-only, and keeps its admin API on a local socket instead of a network port.
  • cloudflared runs as a different user, listens on nothing, holds the tunnel token in a file only it can read, and cannot read the site files at all.
  • The tunnel has one route: portfolio.sirkits.tech to Caddy. Every other hostname gets a 404, and no route leads into the lab.
  • Caddy refuses anything but GET and HEAD and sends a strict Content-Security-Policy: scripts only from this site, no frames, no forms, no outside requests.

Boundaries, and what I have checked

  • Outbound, the Sophos rule DMZ_SITE_UPDATES lets the DMZ reach the WAN on HTTP, HTTPS and a custom Cloudflare service (TCP and UDP 7844), which the tunnel needs. Its source is the DMZ network object, not this one container, and HTTP and HTTPS go to any WAN destination, so it is wider than "Cloudflare only".
  • On the Sophos zone settings, the DMZ zone's device access lists DNS, User Portal and SNMP. That is what the screen shows; I have not yet narrowed it, and it is not the hardened state I would want.
  • No rule I have recorded proves that the DMZ cannot reach my other networks. The rule order and the drop rules are described on the lab page; a test from inside CT 410 is still on my list.
  • Port 8080 listens on loopback only, and the origin is unreachable from any other guest.
  • The public release contains only approved files: no private DNS records, draft thesis material or addresses.

Serving a release

bashDMZ LXC · deploy userExample output · substituted names and addresses

Before reloading Caddy I check that the configuration parses, so a typo never takes the site down.

sudo caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
Show output
Valid configuration
Example output. This is the check that runs on the origin after each release switch.

Each release unpacks into its own folder under /srv/homelab-journal/releases/, and a current symlink points at the live one. Rolling back means pointing the symlink at the previous release.

References: Cloudflare Tunnel · Caddy static files · Proxmox VE administration