Domains & TLS
Serve the API, preview URLs and the console over HTTPS with Caddy.
Caddy on the control plane serves the API, preview URLs and the console over HTTPS, with certificates from Let's Encrypt.
Create DNS records
Point every name at the control plane, never at a sandbox host.
| Record | Type | For |
|---|---|---|
api.example.com | A | The API, SDKs, CLI and the host installer |
*.sbx.example.com | A | Preview URLs, if you turn them on |
app.example.com | A | The console, if you turn it on |
A preview hostname is <port>-<sandbox>.sbx.example.com: one label under the domain, so one wildcard record covers every sandbox.
dig +short api.example.com # the control plane's address
dig +short 8080-test.sbx.example.com # the same addressPorts 80 and 443 must be reachable from the internet for the HTTP-01 challenge.
Set the domains
setup.sh writes them.
# on the control plane, in platinum/infra/compose
./setup.sh --domain api.example.com --sbx-domain sbx.example.com
docker compose up -d| Setting | Effect |
|---|---|
PT_DOMAIN | Caddy gets a certificate for this name. The API URL is https://<PT_DOMAIN>. |
PT_SBX_DOMAIN | Turns on the preview site. Unset, preview URLs use <port>-<sandbox>.127.0.0.1.nip.io, which resolves only on the control plane. |
PT_CONSOLE_DOMAIN | Turns on the console site. See Console. |
Choose how preview certificates are issued
On demand is the default and needs nothing more. Caddy fetches one certificate per preview hostname on its first request, which takes a few seconds. Before each one it asks the server, which says yes only for hostnames of existing sandboxes.
Let's Encrypt allows 50 new certificates per registered domain per week. For many previews, use one wildcard certificate instead.
Use a wildcard certificate
A wildcard needs the DNS-01 challenge. The stock Caddy image has no DNS modules, so build one with yours:
FROM caddy:2.8.4-builder AS b
RUN xcaddy build --with github.com/caddy-dns/<provider>
FROM caddy:2.8.4
COPY --from=b /usr/bin/caddy /usr/bin/caddyThen set DNS_API_TOKEN in .env, and in the Caddyfile preview block replace on_demand with dns <provider> {env.DNS_API_TOKEN}.
Serve previews without a domain
On a private network, preview URLs can use plain HTTP and nip.io. Preview tokens then travel in clear.
# in platinum/infra/compose/.env
PT_SBX_DOMAIN=<private-ip>.nip.io
PT_SBX_PUBLIC_PORT=80Then uncomment the plain-HTTP preview block at the end of the Caddyfile and run docker compose up -d.
Keep the proxy settings
The Caddyfile sets these for the server. Keep them when you edit it.
| Setting | Why |
|---|---|
header_up Host {host} | The server matches preview hostnames on it |
header_up X-Real-IP {remote_host} | The real client address, for rate limits and the admin allowlist |
versions 1.1 | Keeps WebSocket upgrades for terminals and previews simple |
response_header_timeout 90s | Must exceed the longest blocking call, a create with wait_for_state |
flush_interval -1 | Streams exec output, logs and server-sent events as they are written |
The server's :3000 is not published. Caddy is the only way in.
Check it
curl -s -o /dev/null -w '%{http_code}\n' https://api.example.com/health # 200
docker compose logs -f caddy # certificate issuanceThen expose a port on a running sandbox and open the URL it returns. See Networking.