VCode home

Reach it from a phone

Agents: claude, codex, opencode · On: phone

Two ways to open VCode on a phone: over a Tailscale tailnet, or over a Cloudflare tunnel with Cloudflare Access in front. The server itself always binds loopback under the unit; neither route changes that.

Why you would use it

The whole point of the app is driving agents from a phone. Loopback on the machine gets you nowhere from outside it, and neither option asks you to open a port on your router.

How to use it

Over Tailscale

  1. Enable HTTPS certificates for the tailnet at https://login.tailscale.com/admin/dns, "HTTPS Certificates" → Enable. It is a tailnet-wide setting; tailscale status --json shows CertDomains: null until it is on.
  2. sudo tailscale set --operator=$USER once, so serve and cert stop needing sudo.
  3. tailscale serve --bg --https=443 http://127.0.0.1:3444
  4. tailscale serve status prints the URL, https://<host>.<tailnet>.ts.net/.
  5. Keep BIND_HOST=127.0.0.1 so the plain HTTP port stays loopback and every remote hit goes through the HTTPS proxy.
  6. Open the ts.net URL on the phone. Reverse it with tailscale serve --https=443 off.

Over a Cloudflare tunnel with Access

Do Access before DNS, so the hostname is never reachable unauthenticated, even for a few seconds.

  1. Zero Trust → Access → Applications → Add an application → Self-hosted. Name it, set the session duration, set the application domain to your hostname, and attach your existing allow policy rather than duplicating it.
  2. Zero Trust → Networks → Tunnels → your tunnel → Public Hostnames → Add a public hostname. Subdomain and domain, empty path, service type HTTP, URL localhost:3445. This one action inserts the ingress rule and creates the proxied DNS CNAME.
  3. Put the tunnel's token into TUNNEL_TOKEN in ~/.config/v-code/env.
  4. Run bash install.sh update. The tunnel unit is enabled and started now that the token is there.
  5. Open the hostname on the phone. Access asks for your identity first, then the app asks for the token.

What you see

Over the tunnel you meet two gates in order. Cloudflare Access redirects you to your identity provider. Only after that does the app's own token sheet appear — see log-in-with-the-token.

When an Access session runs out while the app is open, public/store.js sees HTML from a route that only ever speaks JSON and marks the session stale. The app is meant to turn the offline banner into Access session expired — tap to sign in again, whose tap reloads the page so Access can run its redirect flow — EventSource and fetch cannot follow that dance themselves. Today that banner never becomes visible. See the gap below; what you actually get is an app that stops updating, and a reload by hand puts you back through the Access login.

Options and settings

Option Default What it changes
TUNNEL_TOKEN (empty) cloudflared's token. Empty leaves v-code-tunnel.service untouched
BIND_HOST 127.0.0.1 Set to a tailnet IP only when the tunnel connector runs on another machine
VCODE_URL (empty) This machine's public URL, shown in the UI and used by the node switcher
VCODE_PEERS (empty) Sibling machines as name|url, comma separated

Limits and known gaps