Skip to content
All posts

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.

2 min read

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) ──▶ containers

Two 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:

  1. Add the ingress rule to /opt/stacks/cloudflared-varzil/data/config.yml:

    - hostname: mindmate.varzil.com
      service: http://172.18.0.1:8501
  2. Point 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}"
  3. 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:

ConnectorWhereResult
systemd cloudflared.servicehost, /etc/cloudflared/config.ymlworked
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: 512m

Set 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.