0021: Reaching sandboxes from outside the cluster: none, service or cloudflare¶
Date: 2026-10-13 · Status: accepted
Context¶
Developers want to open a restored sandbox from their own database client. The only external route was a
Cloudflare Tunnel behind Access. Cloudflare passes raw TCP only to clients running cloudflared, so every developer
needed it, an Access login, and the Tenax API to look up the route. Tenax is self-hosted by many organizations,
which reach their clusters in different ways; none of them should depend on a service we run.
Decision¶
expose.provider chooses how sandboxes are reached; Tenax opens nothing itself in any of them.
none(default): in-cluster only.tenax sandbox connectforwards a local port to the sandbox pod through the user's kubeconfig (Kubernetes RBAC onpods/portforwarddecides).service(new): each exposed sandbox gets a Kubernetes Service (db-external) built fromexpose.service: type, load-balancer class, annotations (with{id}), source ranges. The cluster's own machinery gives it an address: a cloud load balancer, MetalLB, the Tailscale operator, external-dns for a name. The route is direct (host:port,routeType: direct) and any client connects to it. While exposed, a second NetworkPolicy (tenax-sandbox-external) admitsexpose.service.allowFromon the database port. A route without an address yet (a load balancer still provisioning) is retried every 10 seconds.cloudflare: unchanged (routeType: tunnel).- Postgres sandboxes serve TLS in every mode, with a self-signed certificate the controller generates
(
tenax-sandbox-tls). An init container hands the key to the postgres user, and the server starts withssl=on. The command line clears CA, CRL and passphrase settings a physically restored configuration may carry, since it overrides the files. MySQL uses the certificate it generates itself. Direct connection strings require TLS. Exposer.Unexposereceives the sandbox's in-cluster service too, so a provider working with Kubernetes objects finds them without guessing names.
Consequences¶
- An administrator picks one option per installation; developers get a connection string and need nothing else
with
service. - A public load balancer protects a sandbox only with its password, TLS and TTL; the docs and chart comments say
to set
sourceRangesor use a private option. With the defaultallowFrom, in-cluster pods can also reach an exposed sandbox (its address is public anyway). - On k3s's built-in load balancer, exposed sandboxes of one engine share the node port, so one at a time.
- Every Postgres sandbox pod has one more init container and a small Secret.