Self-hosting the full stack
The repositories ship a compose deployment targeting any VM with Docker — developed and
running on Oracle Cloud's Always Free tier (Ampere ARM). The same compose file runs the
edge alone (default) or the full cloud stack (gateway + console + TLS proxy) behind the
cloud profile.
127.0.0.1; the only
public surface is the TLS reverse proxy. Nothing is published to 0.0.0.0 —
even the proxy uses host networking with loopback upstreams.
What you need
-
A VM with Docker + compose v2 and two sibling checkouts:
free-llm(public edge) andzerofare-cloud(gateway + console). - Node 24 with corepack pnpm (the images build in-container, but local tooling helps for bootstrap).
- Ports 80/443 open for ACME if you want automatic TLS via a real hostname.
Bring up the stack
-
Provision secrets
Create
deploy/oracle-free/secrets/with a vault passphrase, a gateway pepper (random, ≥32 bytes), and an (initially empty) seed file. All owned by UID 65532, mode 0600:cd zerofare-cloud/deploy/oracle-free mkdir -p secrets state umask 077 head -c 48 /dev/urandom | base64 > secrets/gateway_pepper printf 'your-vault-passphrase\n' > secrets/vault_passphrase echo '{"version":1,"tenants":{}}' > secrets/control-seed.json -
Build the images
Images build from the sibling checkouts (node image pinned by digest; ARM and x86 both covered):
docker compose build # edge docker compose --profile cloud build # gateway + console -
Bootstrap a tenant
The bootstrap tool provisions a tenant, generates both secrets, and merges digest-only records into the shared seed file. Plaintext is printed once — capture it:
docker compose --profile cloud run --rm \ -v ./secrets:/run/zerofare-seed-out control-plane \ node tools/bootstrap-tenant.mjs --tenant-id ten_myco --tenant-name "My Co"Re-running is idempotent: it merges new keys without invalidating existing ones.
-
Start everything
docker compose up -d docker compose --profile cloud up -dVerify from the VM — fail-closed first:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8788/management/v1/sessions # 401 curl -s http://127.0.0.1:8789/management/v1/healthz # 200 -
Add TLS
Point a DNS A record at your VM (or use an
sslip.iohostname while evaluating), edit the two hostnames incaddy/Caddyfile, and restart the proxy. Caddy obtains and renews Let's Encrypt certificates automatically:docker compose up -d caddyOrdering matters: open firewall ports before first starting Caddy, or ACME validation fails and backs off. If that happens,docker compose restart caddyafter opening ports.
Console & management surface
-
Console:
https://your-host/— paste the management credential from bootstrap. -
Management API:
https://your-host/management/v1/…— same origin, same credential. -
Gateway API:
https://your-host:8443/…— for customer API keys and SDKbaseUrl.
Upgrades & state
Service state is in-memory by design (Phase 1); the durable artifacts are the seed file
and pepper, both digest-only. Restarting containers re-hydrates credentials from the
seed — verified by tests and by the live deployment. Container images are rebuilt from
source with in-image typecheck, so a
git pull && docker compose --profile cloud build && docker compose --profile cloud up
-d
is the whole upgrade path.
Operations runbook
The repository's deploy/oracle-free/README.md is the detailed runbook:
secret staging, bootstrap procedure, two-layer firewall rules, public verification
checks, and troubleshooting (including the ACME ordering gotcha).