Reference deploys — Docker, Fly.io, Render
read as.md If you followed the OSS Quick Start, you have ggui serve running on localhost. This page puts that same server behind a public URL so your phone, teammates, or collaborators can reach it. Three drop-in manifests — generic Docker, Fly.io, Render.com — are inlined below; copy them straight into your project root.
None of these deploys phone home. Your code runs on your infrastructure. Viewer clients pair with the public URL via the flow in Self-hosted pairing.
What’s in the box
Section titled “What’s in the box”| File | Purpose |
|---|---|
Dockerfile | Multi-stage Node 22 image that boots ggui serve on $PORT. |
fly.toml | Fly.io manifest — scales to zero, healthchecks /ggui/health. |
render.yaml | Render.com Blueprint — Docker runtime, healthchecks /ggui/health. |
All three are self-contained — there is no separate templates package to install.
Option 1 — Generic Docker
Section titled “Option 1 — Generic Docker”Works anywhere Docker runs: EC2, your homelab, a Raspberry Pi, a Coolify VPS. Save this as Dockerfile in your project root (next to ggui.json):
# syntax=docker/dockerfile:1# Boots `ggui serve` on $PORT. Assumes your project root has# package.json + ggui.json (+ agent entry and blueprints).FROM node:22-slim AS depsWORKDIR /appCOPY package.json package-lock.json* ./RUN npm install --omit=dev
FROM node:22-slimWORKDIR /appENV NODE_ENV=productionCOPY --from=deps /app/node_modules ./node_modulesCOPY . .EXPOSE 6781CMD ["sh", "-c", "npx -y @ggui-ai/cli serve --host 0.0.0.0 --port ${PORT:-6781}"]docker build -t my-ggui .docker run --rm -p 6781:6781 -e PORT=6781 -e ANTHROPIC_API_KEY=sk-... my-gguiThe image exposes /ggui/health, /mcp (MCP channel), /ws (WebSocket channel for ActionEnvelope / StreamEnvelope), /pair, /admin/pair/init, /admin/pair/:pairingId/revoke, and / (operator console) on $PORT. Once running, point a viewer client at http://<docker-host>:6781 and follow the Pairing guide.
ggui serve binds plaintext HTTP. Put a TLS-terminating reverse proxy in front (Caddy, nginx, Cloudflare Tunnel) for anything beyond a LAN. The WebSocket channel rides the same :6781 port — make sure your proxy upgrades /ws correctly. Minimal Caddy example (Caddy handles WS upgrades automatically):
my-ggui.example.com { reverse_proxy 127.0.0.1:6781}Option 2 — Fly.io
Section titled “Option 2 — Fly.io”Fly gives you a free-tier region, HTTPS by default, and scale-to-zero. Reuse the Dockerfile from Option 1 and save this as fly.toml:
# fly.toml — `ggui serve` reference deployapp = "my-ggui" # change meprimary_region = "iad" # change me
[build] dockerfile = "Dockerfile"
[env] PORT = "6781"
[http_service] internal_port = 6781 force_https = true auto_stop_machines = true auto_start_machines = true min_machines_running = 0
[[http_service.checks]] interval = "30s" timeout = "5s" grace_period = "10s" method = "GET" path = "/ggui/health"fly launch --no-deploy # picks up the fly.toml + Dockerfile abovefly secrets set ANTHROPIC_API_KEY=sk-...fly deployThe [http_service] block sets force_https = true, so the URL you hand a viewer client is https://<app>.fly.dev — no proxy setup needed. The WS channel rides the same TLS endpoint as wss://<app>.fly.dev/ws.
auto_stop_machines = true + min_machines_running = 0 freezes the machine when idle and thaws it on the next request (~1–2 s cold start). Fine for a personal server. Disable it if you need zero-latency persistence — e.g., a long-running session where a cold start mid-conversation would drop the WS connection.
Option 3 — Render.com
Section titled “Option 3 — Render.com”Render is the simplest click-to-deploy story. Commit the manifest + Dockerfile, point Render at your repo, and it redeploys on every push to main. Reuse the Dockerfile from Option 1 and save this as render.yaml:
# render.yaml — `ggui serve` reference deployservices: - type: web name: my-ggui runtime: docker plan: starter healthCheckPath: /ggui/health envVars: - key: ANTHROPIC_API_KEY sync: false # set the value in the Render dashboardgit add Dockerfile render.yamlgit commit -m "chore: add ggui serve reference deploy"git pushIn Render’s dashboard: New → Blueprint → connect your repo. Render reads render.yaml, provisions the service, and gives you a https://<name>.onrender.com URL (TLS + WS upgrade handled).
Swap plan: starter for standard if your agent does heavier in-process work — embeddings, large prompt assembly, or holding many concurrent WebSocket sessions.
Environment variables
Section titled “Environment variables”ggui serve honors a handful of env vars across all three deploys. Set them via the PaaS’s secret store (Fly secrets, Render environment groups, docker run -e):
| Variable | Effect |
|---|---|
PORT | Bind port. Injected by Fly + Render automatically; pass -e PORT=… for raw Docker. The Dockerfile’s CMD forwards it via --port ${PORT:-6781}. |
| LLM provider key | Set one of ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_API_KEY / OPENROUTER_API_KEY via the PaaS secret store, matching ggui.json#generation.model. Without one, every render falls back to a Connect-a-key card pointing at /settings. See ggui serve → Generation. |
GGUI_PERSISTENT_DIR | Override the .ggui/persistent/ bundle location (HMAC secrets, sessions, vectors, short-codes, paired bearers). Defaults to project-local; point at a mounted volume for durability. |
GGUI_MCP_INSTRUCTIONS | Server-level MCP instructions preset (default / aggressive / always / minimal / off). CLI flag --mcp-instructions wins if both are set. |
Pin --admin-token via the CMD (e.g. ggui serve --admin-token "$GGUI_ADMIN_TOKEN" --host 0.0.0.0 --port $PORT) when you want the admin bearer to survive restarts; otherwise the server mints a fresh one each boot.
Before you leave localhost
Section titled “Before you leave localhost”The default ggui serve posture is strict-auth: /mcp only accepts pair-minted bearers, and /admin/pair/init requires the per-boot ggui_admin_* token (or whatever you pin via --admin-token). That’s already safe to expose. The hardening below covers the remaining edges before handing out a public URL to paired clients:
- Pin the admin token. Without
--admin-token, the server mints a freshggui_admin_*per boot — fine for local use, awkward when you redeploy. Pin a stable value via a secret (e.g. Fly secrets, Render environment groups) and pass it as--admin-token "$GGUI_ADMIN_TOKEN"in the CMD. - (Optional) swap in a real
AuthAdapter. If you want OIDC / Cognito / SSO instead of pair-minted bearers, wrap your agent entrypoint withcreateGguiServer({ auth })that gates/mcpand/ws. See theAuthAdapterinterface in@ggui-ai/mcp-server-core. - Firewall
/admin/*./admin/pair/initand/admin/pair/:pairingId/revokeare the only admin surfaces today, both bearer-gated by the admin token. Restricting the/admin/prefix to an allow-list IP or VPN at the reverse-proxy layer adds defense-in-depth (and covers any new/admin/*routes added later). - NEVER pass
--dev-allow-allor--public-demohere. Those flags relax/mcpto accept any bearer — or none at all (the second one adds a rate limiter and a “operator pays” banner). They exist for local-dev and demo audiences, not for a deployed server.--public-base-urlis for ad-hoc tunnel testing, not for these manifests — the PaaS already gives you the public URL. - Rotate pairings. Pair-minted bearers have no built-in expiry — they live until revoked. Revoke stale paired devices from the operator console at
/(orPOST /admin/pair/:pairingId/revokedirectly).
Skip these and a “deployed ggui serve” still authenticates correctly, but you’ll be churning admin tokens on every redeploy and can’t selectively block admin-only routes at the edge.
What this does NOT include
Section titled “What this does NOT include”- Managed deploys. Reference assets only — no auto-updates, no managed rollouts, no dashboard beyond what the PaaS ships. Render’s built-in logs/metrics gets closest to “push to main → zero-downtime rollout with observability”; full production SRE is out of scope.
- Multi-region replication. Each deploy is a single instance.
ggui serveis stateful-per-process today; replicas won’t share session state, blueprint cache, or paired-device records without a shared store.storage.rendersinggui.jsonsupports sqlite for a single node; durable multi-instance storage is roadmap. - Managed hosting. A managed lane — builds, logs, quotas, BYOK, billing, run by us — is coming soon. These self-hosted manifests are the available-now path.
What’s next
Section titled “What’s next”- Self-hosted pairing — pair viewer clients to your deployed server.
- OSS Quick Start — the local-first walkthrough (run on localhost before you deploy).