Eval sandbox deployment (Coolify)
Eval sandbox deployment (Coolify)
Section titled “Eval sandbox deployment (Coolify)”The eval sandbox is a minimal vox mcp HTTP gateway that exposes /v1/eval for the static docs playground. It does not mount a workspace or provider keys. Production URL: https://eval.vox-lang.org. The documentation site (https://vox-lang.org) is separate (GitHub Pages / mdBook), not this stack.
Compose and container image
Section titled “Compose and container image”- SSOT compose:
vox-eval.compose.ymlat the repository root (docker/vox-eval.compose.ymlis a mirror for compose invocations rooted indocker/). - Image:
ghcr.io/vox-foundation/vox-eval:latest, built by.github/workflows/docker-eval.ymlwithVOX_CLI_FEATURES=mcp-server(matchescommand: ["vox", "mcp"]). - Health: Docker
HEALTHCHECKand Composehealthcheckprobehttp://localhost:3921/healthviacurl(the runtime image installscurl). This aligns Traefik with an actually reachable backend. vox doctor --probe: Can be used interactively for diagnostics; it is not the canonical signal for Docker/Coolify scheduling because it exercises a broader CLI surface than the gateway-onlymcp-serverimage. PreferGET /healthfor orchestration probes.
DNS (outside Coolify)
Section titled “DNS (outside Coolify)”Point eval.vox-lang.org to the same edge IP (or CNAME) where Coolify’s Traefik terminates TLS for your VPS. Coolify cannot create this record; configure it at your DNS provider (for example Cloudflare or your registrar). Confirm with dig / nslookup before debugging TLS.
Coolify configuration
Section titled “Coolify configuration”- Create or select a Docker Compose application in Coolify.
- Set FQDN / domains to
https://eval.vox-lang.org(or equivalent field for your Coolify version). - Inject
VOX_MCP_HTTP_BEARER_TOKEN(and any other env vars from compose) via Coolify project/environment secrets. - If the GHCR package is private, configure registry credentials in Coolify so
pull_policy: alwayscan fetchghcr.io/vox-foundation/vox-eval. - Align Traefik / TLS with Coolify’s documented certificate workflow. Raw Compose labels use
tls.certresolver=letsencrypt; your Coolify install may prefer UI-managed certs—avoid duplicate conflicting resolvers.
API discovery and provisioning (no SSH)
Section titled “API discovery and provisioning (no SSH)”Secrets resolve through vox-secrets from canonical env vars (deploy-contract.md):
| Purpose | Env vars |
|---|---|
| Coolify base URL | COOLIFY_BASE_URL |
| Write / deploy | COOLIFY_TOKEN |
Read-only GET (optional) | COOLIFY_READ_TOKEN (falls back to COOLIFY_TOKEN) |
| Target app | COOLIFY_APP_UUID |
Discover (list apps and optional version probe):
vox ci coolify-eval discoverSync compose from the repo (updates docker_compose_raw, optional domains, then GET /api/v1/deploy?uuid= unless --no-deploy):
vox ci coolify-eval sync-compose \ --compose vox-eval.compose.yml \ --domains https://eval.vox-lang.orgVerify COOLIFY_APP_UUID matches the eval compose application (vox ci coolify-eval discover prints uuid and fqdn). deploy-hetzner.yml Gate 3 assumes this UUID backs https://eval.vox-lang.org/health unless COOLIFY_PUBLIC_EVAL_HEALTH_URL overrides it. Gate 1 is only cargo build -p vox-cli --locked on ubuntu-latest; merge-quality gates live in .github/workflows/ci.yml.
GitHub Actions
Section titled “GitHub Actions”Manual .github/workflows/coolify-eval-sync.yml (workflow_dispatch) runs discover and optionally sync-compose using repository secrets. Exact Coolify PATCH shapes may differ by major version; treat failures as a signal to confirm API docs for your install.
deploy-hetzner.yml Gate 3: The public curl probe must still run when Gate 1 is skipped (workflow_dispatch + skip_tests: true). The health-check job should needs: [smoke-ci, deploy-coolify] with if: ${{ always() && needs.deploy-coolify.result == 'success' }} so GitHub Actions does not skip the HTTPS verifier just because smoke CI did not run.
Recovery loop (everything except Cloudflare)
Section titled “Recovery loop (everything except Cloudflare)”Use this repeat-until-green procedure when curl -fsS https://eval.vox-lang.org/health fails (TLS errors, 503 no available server, etc.). Symptom → cause mapping: deploy-contract Gate 3 cheatsheet.
Step 1 — Identity (COOLIFY_APP_UUID vs eval app)
Section titled “Step 1 — Identity (COOLIFY_APP_UUID vs eval app)”Prerequisites: COOLIFY_BASE_URL and a read-capable bearer (COOLIFY_READ_TOKEN or COOLIFY_TOKEN) in the environment, or resolve the same values via vox-secrets locally.
vox ci coolify-eval discoverConfirm exactly one application is the eval stack: docker_compose_raw is present (compose app), fqdn / name matches eval.vox-lang.org. Align GitHub repository secret COOLIFY_APP_UUID with that row’s uuid so deploy-hetzner.yml Gate 3 targets the correct resource.
Step 2 — Compose sync and secrets (Coolify + GHCR)
Section titled “Step 2 — Compose sync and secrets (Coolify + GHCR)”Push SSOT compose from this repo (writes Coolify application state; requires write/deploy token):
vox ci coolify-eval sync-compose \ --compose vox-eval.compose.yml \ --domains https://eval.vox-lang.orgOr run .github/workflows/coolify-eval-sync.yml with sync_compose: true.
In Coolify, set VOX_MCP_HTTP_BEARER_TOKEN and any other env vars referenced by vox-eval.compose.yml. If ghcr.io/vox-foundation/vox-eval is private, add registry credentials in Coolify. Wait until the deployment finishes and the service is running and healthy (Compose healthcheck uses curl → http://localhost:3921/health).
Step 3 — TLS and Traefik router
Section titled “Step 3 — TLS and Traefik router”Choose one TLS strategy: Coolify UI FQDN / Generate TLS certificates for eval.vox-lang.org, or Traefik tls.certresolver=letsencrypt in labels — avoid duplicate conflicting resolvers (see §Coolify configuration above). Confirm the Traefik Host rule matches eval.vox-lang.org (same as vox-eval.compose.yml). For HTTP-01 ACME, port 80 must reach Traefik on the origin.
Step 4 — Backend (503)
Section titled “Step 4 — Backend (503)”If TLS verifies but you still see 503 / no available server, inspect Coolify/Docker logs for the eval container. Inside the container, GET /health on port 3921 must return 200. Typical fixes: wrong COOLIFY_APP_UUID, missing VOX_MCP_HTTP_*, failed image pull, or Host mismatch.
Step 5 — CI alignment (optional)
Section titled “Step 5 — CI alignment (optional)”After the public URL is green, push main or run Deploy Hetzner (Coolify) so .github/workflows/deploy-hetzner.yml Gate 3 passes.
Cloudflare (operator-only; not automated here)
Section titled “Cloudflare (operator-only; not automated here)”Do this at your DNS provider when eval.vox-lang.org uses Cloudflare:
- DNS:
eval→A/AAAA(orCNAME) to the Coolify/VPS public target Cloudflare documents for your setup. Confirm withdig eval.vox-lang.org. - Proxy: DNS only (grey) simplifies origin TLS/Let’s Encrypt while debugging. Proxied (orange) visitors see Cloudflare’s certificate; set SSL/TLS to Full or Full (strict) when the origin presents a valid cert. Avoid Flexible if you rely on
VOX_MCP_HTTP_REQUIRE_FORWARDED_HTTPS=1(see compose). - ACME: For HTTP-01, ensure
http://eval.vox-lang.org/.well-known/acme-challenge/is not blocked by rules. Prefer DNS-01 in Coolify if HTTP-01 is impractical; that may require a Cloudflare API token (DNS:Edit) configured in Coolify, not in this repository. - Wrong origin: Ensure
evalis not a CNAME to GitHub Pages or another unrelated host.
Verification
Section titled “Verification”Loop exit: from a machine with a normal CA store (no -k):
curl -fsS https://eval.vox-lang.org/healthExpect HTTP 200. Repeat until success after each Coolify/DNS change.
Periodic check (every ~3s, PowerShell):
while ($true) { curl.exe -fsS "https://eval.vox-lang.org/health" 2>$null if ($LASTEXITCODE -eq 0) { break } Start-Sleep -Seconds 3}Periodic check (bash):
until curl -fsS https://eval.vox-lang.org/health; do sleep 3; done