6674) over a public HTTPS URL, so
Flare’s TEE infrastructure can reach it. Use instead of ngrok. Compose file:
docker-compose.cloudflared.yaml.
Only testnets need this. On --chain local, nothing is started because a local devnet reaches
the proxy on localhost.
The scripts drive it
start-services.sh syncs the tunnel before every other container, then writes the URL
into .env as EXT_PROXY_URL. full-setup.sh calls it in Phase 2, and post-build.sh /
test.sh re-read .env, so a whole run picks the URL up by itself:
stop-services.sh leaves the tunnel up unless you pass --tunnel — see below.
One tunnel, shared by every extension
The tunnel does not belong to this extension. It runs in a compose project calledtunnel, set by the name: line at the top of docker-compose.cloudflared.yaml.
Compose identifies a container by project name + service name and nothing else — not
the file’s path, not the folder it sits in. So every extension’s copy of this file
addresses the same container: the first up -d creates it, every later one finds it
running and inherits its URL. That works because every extension here publishes ext-proxy
on host 6674, so only one stack is ever behind the tunnel; it is handed from extension to
extension, keeping one URL across the switch.
That is also why stop-services.sh keeps it alive by default: tearing it down rotates the
URL for every other extension and strands their EXT_PROXY_URL.
Copy docker-compose.cloudflared.yaml verbatim. Compose compares the resolved service
config to decide reuse-vs-recreate. Comments and file paths are not part of that
comparison, but command: is — so a differing TUNNEL_TARGET recreates the container and
mints a new URL for everyone. Setting TUNNEL_TARGET by hand warns you for this reason.
Two cases that legitimately need their own tunnel, and get their own project:
Manual
From the repo root, in Git Bash:.env as EXT_PROXY_URL=<url>, then start the containers.
start-services.sh blocks on $EXT_PROXY_URL/info, so a stale value there is what makes
it fail. Stop with docker compose -f docker-compose.cloudflared.yaml down.
Skipping the copy-paste — does step 2 and writes the result straight into .env:
EXT_PROXY_URL= line only — if the echo comes back empty, add the
line once by hand and this works from then on.
Already registered on-chain? Repoint the machine record too
EXT_PROXY_URL in .env only feeds the local scripts. Flare’s TEE infra delivers
instructions to the URL stored in the MachineManager, written once at registration —
post-build.sh will not rewrite it for an already-registered machine. A rotated URL
therefore looks like every on-chain op timing out at pollAction even though the chain leg
succeeded.
MachineManager.updateTeeMachineSettings directly — no attestation, one transaction,
ledger untouched. A full re-registration would trip ChallengeExpired on a long-running
TEE. This is the strongest reason to want a stable hostname.
Making the URL stable
Quick tunnels always rotate and no flag pins them. Cheapest fix first: Don’t restart the tunnel. It has no dependency on the main stack, sodown && up on the
app containers does not touch it. Only restarting cloudflared itself rotates the URL.
Named tunnel — permanently fixed hostname. Needs a free Cloudflare account with a domain
on it:
- Zero Trust → Networks → Tunnels → Create a tunnel → Cloudflared. Copy the
eyJ...token. - Add a public hostname pointing at
http://host.docker.internal:6674. - In
.env: docker compose -f docker-compose.cloudflared.yaml up -d— same command, named mode now.
EXT_PROXY_URL and the on-chain machine record then survive every restart, and
restart: unless-stopped becomes safe to add to the service.