Expose a Compose install with Cloudflare Tunnel

Route a stable hostname to your local Stackweaver installation without opening inbound ports. This guide uses a locally managed tunnel so its ingress rules live in YAML. Cloudflare also offers remotely managed tunnels, including API and Terraform management; see its tunnel management options.

Before you start

A named tunnel's <UUID>.cfargotunnel.com address is its DNS target, not a free public application hostname. Use your own domain. A quick tunnel gives a temporary trycloudflare.com address and is unsuitable as a stable OAuth callback or webhook URL.

1. Create the tunnel

Run these commands as the account that will own the tunnel files:

cloudflared tunnel login
cloudflared tunnel create stackweaver
cloudflared tunnel list

Record the tunnel UUID. The creation command writes its credentials JSON in your .cloudflared directory; keep that file private.

2. Route the application and identity hostnames

Create ~/.cloudflared/config.yml. Replace the UUID, username and domains:

tunnel: "<tunnel-uuid>"
credentials-file: /home/your-user/.cloudflared/<tunnel-uuid>.json

ingress:
  - hostname: app.example.com
    service: http://localhost:5173
  - hostname: auth.example.com
    service: http://localhost:8080
  - service: http_status:404

The frontend's supplied nginx configuration forwards API, discovery and login-proxy paths to the API while serving public pages and application routes itself. Routing the entire app hostname to port 5173 therefore preserves the same-origin browser setup. The identity hostname goes to Zitadel. The final catch-all rule must stay last.

Diagram definition
flowchart LR
    A["Browser or API client"] --> C["Cloudflare Tunnel"]
    C -->|"app.example.com"| F["Frontend :5173"]
    F -->|"API and auth proxy paths"| API["API :8022"]
    C -->|"auth.example.com"| Z["Zitadel :8080"]

If cloudflared runs in a container, localhost is that container. Use reachable service names or host addresses instead of copying these host-run origins unchanged.

Register both DNS records, validate the file and run in the foreground:

cloudflared tunnel route dns stackweaver app.example.com
cloudflared tunnel route dns stackweaver auth.example.com
cloudflared tunnel ingress validate
cloudflared tunnel ingress rule https://app.example.com/
cloudflared tunnel run stackweaver

Open the app hostname, sign in and return to the app. Check the discovery document and send a test delivery from your VCS provider if webhooks are enabled. A tunnel exposes traffic; it does not update Stackweaver's issuer, redirect registrations or public URLs. Configure your own canonical address and indexing policy with Public pages and search indexing.

3. Persist the tunnel on Linux

Copy the files into a root-owned directory. Replace TUNNEL_UUID before running:

sudo install -d -m 700 /etc/cloudflared
sudo install -m 600 ~/.cloudflared/config.yml /etc/cloudflared/config.yml
sudo install -m 600 ~/.cloudflared/TUNNEL_UUID.json /etc/cloudflared/TUNNEL_UUID.json

In /etc/cloudflared/config.yml, change only the credential path to /etc/cloudflared/TUNNEL_UUID.json, using the actual UUID in both places. Then install with an explicit config path so sudo does not look in /root/.cloudflared:

sudo cloudflared --config /etc/cloudflared/config.yml tunnel ingress validate
sudo cloudflared --config /etc/cloudflared/config.yml service install
sudo systemctl enable --now cloudflared
sudo systemctl status cloudflared
sudo journalctl -u cloudflared -f

Add a hostname or diagnose a failure

Add new hostname rules before the catch-all, create the matching DNS record with cloudflared tunnel route dns, validate, then run sudo systemctl restart cloudflared. Use sudo systemctl stop cloudflared to stop the service without deleting its tunnel.

SymptomCheck
Cloudflare reports an unreachable originThe local service is running and reachable from the cloudflared process.
A hostname returns the catch-all 404The hostname, DNS route and first matching ingress rule agree.
Sign-in redirects to localhost or the wrong domainReconcile the persistent identity and application URL settings, not just tunnel DNS.
Webhook deliveries are blockedThe webhook URL reaches the API and any Cloudflare access policy permits the provider's requests.

For temporary origin testing, create a quick tunnel:

cloudflared tunnel --url http://localhost:5173

Keep registered callbacks and production webhooks on the named hostnames.