The whole stack runs behind one tunnel
No public ports, two Cloudflare Tunnels, a Tailscale fallback, and the duplicate-connector bug that took an afternoon to find.
The rule for this server is simple: nothing listens on the public internet. Everything a visitor can reach goes out through a Cloudflare Tunnel, and everything I need goes in over Tailscale.
The shape of it
Internet
│
▼
Cloudflare DNS
├── v3classes.com ──▶ systemd cloudflared (tunnel 0e66aa20) ──▶ nginx :8081
└── *.varzil.com ──▶ docker cloudflared-varzil (tunnel e565193f) ──▶ containersTwo tunnels, deliberately separated by domain. Mixing a varzil.com hostname into the v3classes.com tunnel — or vice versa — is the kind of thing that produces a 404 that looks exactly like a DNS problem.
Adding a subdomain
Three steps, in this order:
Add the ingress rule to
/opt/stacks/cloudflared-varzil/data/config.yml:- hostname: mindmate.varzil.com service: http://172.18.0.1:8501Point the DNS record at the tunnel:
ZONE="762ac37f09b19e633b051756f8f3b6e3" TARGET="e565193f-0629-4d3d-b1a9-e7f39988876d.cfargotunnel.com" curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ "https://api.cloudflare.com/client/v4/zones/$ZONE/dns_records" \ -d "{\"type\":\"CNAME\",\"name\":\"mindmate.varzil.com\",\"content\":\"$TARGET\",\"proxied\":true}"Restart the connector:
cd /opt/stacks/cloudflared-varzil && sg docker -c "docker compose restart"
The bug worth remembering
For a few days v3classes.com was intermittently 502-ing. The site was fine, nginx was fine, the HTML even rendered when it worked. The problem was two connectors registered against the same tunnel ID:
| Connector | Where | Result |
|---|---|---|
systemd cloudflared.service | host, /etc/cloudflared/config.yml | worked |
docker cloudflared | /opt/stacks/cloudflared/ | timed out on 172.18.0.1:8081 |
Cloudflare load-balanced between them, so roughly half of all requests hit the broken connector. The fix was to stop the duplicate:
sg docker -c "docker stop cloudflared"The general rule
One tunnel ID, one connector. If a site is randomly 502-ing and the origin looks fine, check for a second process claiming the same tunnel before you touch anything else.
Memory pressure
Eight gigabytes of RAM is not a lot once you add Postgres, Redis and MinIO. Docker memory limits are doing real work here:
services:
pcopy:
mem_limit: 128m
iacommenter-db:
mem_limit: 512mSet a limit on anything that is not the workload you actually care about. It is the cheapest way to keep a rebuild from OOM-killing the whole box.