From 2f4fc25543ac7c488d735c2c5211d6247d56bcea Mon Sep 17 00:00:00 2001 From: Travis Herbranson Date: Wed, 20 May 2026 07:50:25 -0400 Subject: [PATCH] docs: write operator README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- README.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/README.md b/README.md index e69de29..9fd24fe 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,74 @@ +# 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 + +```bash +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://:` (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://: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.