cloudflared/README.md
Travis Herbranson 2f4fc25543 docs: write operator README
Repo had a 0-byte README. New one covers:
- Single-service inventory with the load-bearing cloudflared-mcp
  container name (vault-mcp's legacy network aliases depend on it)
- Token-mode + dashboard-managed ingress contract
- .env contract (TUNNEL_TOKEN)
- Token rotation flow + how to map a new public hostname
- Cross-reference to homelab-ansible's roles/cloudflared README for
  the redirect-loop gotcha on Traefik backends
- No-ports / one-tunnel / :latest gotchas

Working tree has an in-flight docker-compose.yml → compose.yml rename
(D docker-compose.yml + ?? compose.yml). Intentionally not part of
this commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 07:50:25 -04:00

3.0 KiB

cloudflared

Token-mode Cloudflare tunnel that fronts the homelab's MCP services and admin UIs. Outbound-only from the host — no inbound ports.

Routes *.herbylab.dev hostnames (e.g. vault.herbylab.dev, auth.herbylab.dev, trellis.herbylab.dev) to in-network services on the shared homelab Docker network. Ingress rules live in the Cloudflare Zero Trust dashboard, not in this repo.

Service

Service Image Container name
cloudflared cloudflare/cloudflared:latest cloudflared-mcp

The cloudflared-mcp container name is load-bearing: it's the same hostname the legacy network aliases on vault-mcp (homelab-mcp, herbydev-mcp) expect, so renaming the container breaks dashboard ingress until the tunnel config is repointed.

Run it

docker compose up -d
docker compose logs -f cloudflared

Configuration

File Purpose
compose.yml Single-service definition + external homelab network
.env TUNNEL_TOKEN (from Cloudflare Zero Trust dashboard) — gitignored

To create / rotate the tunnel:

  1. Cloudflare Zero Trust dashboard → Networks → Tunnels.
  2. Create a new tunnel (or rotate the token on the existing one).
  3. Paste the connector token into .env as TUNNEL_TOKEN=....
  4. Restart: docker compose up -d.

To map a new public hostname to a backend:

  1. Cloudflare dashboard → tunnel → Public Hostnames → Add a public hostname.
  2. Origin = http://<container>:<port> (the backend must be on the homelab Docker network so this cloudflared-mcp container can reach it by service name).
  3. No restart needed — the connector reloads dashboard config on its own.

For HTTPS-only backends (Traefik et al.), use https://<container>:443 with "No TLS Verify" — see the roles/cloudflared README in homelab-ansible for the redirect-loop gotcha when targeting :80.

Gotchas

  • Token-mode = no ports: block. This is outbound-only by construction. If you find yourself adding a ports: entry, you're misconfiguring — backends are reached outbound from the connector, not inbound.
  • External homelab network must exist before this stack starts. Backends must also be on the homelab network (or a network shared with it) for cloudflared-mcp to resolve them by container name.
  • One container, one tunnel. Multiple tunnels = multiple compose stacks (or a parameterized invocation pattern — see the roles/cloudflared Ansible role in homelab-ansible for that approach).
  • :latest image tag. No pinning. If a connector upgrade breaks ingress, pin to the last-known-good tag and re-deploy.
  • The repo also contains some leftover uv-init artifacts (main.py, pyproject.toml, uv.lock, .python-version). These are not part of the deployment.