Security & TLS
Terminate TLS in front of a self-hosted instance and lock down its secrets
Neither the frontend nor the backend terminates TLS. In production you put a reverse proxy in front of the stack to handle certificates, then point the frontend at the HTTPS endpoint. This page covers both, plus where production secrets should live and why the code executor needs its own host.
Terminate TLS with a reverse proxy
Run Caddy, nginx, or Traefik in front of the stack. Caddy is the shortest path because it issues and renews Let’s Encrypt certificates on its own.
Run the proxy
Put a Caddyfile next to your compose file, pointing each hostname at the port the stack already publishes. The full port list is in Requirements.
# Caddyfile
app.yourcompany.com { reverse_proxy localhost:3000 }
api.yourcompany.com { reverse_proxy localhost:8000 }Then start Caddy on the host, from the same directory:
caddy run --config ./CaddyfileBoth hostnames have to resolve to this machine before Caddy can complete the Let’s Encrypt challenge.
Point the frontend at HTTPS
Set VITE_HOST_API=https://api.yourcompany.com in .env. The frontend container reads it on start and writes it into config.js, so no rebuild is needed. Nothing on the backend needs to know about the new origin.
Close the plaintext ports
The frontend and backend publish on every interface, so :3000 and :8000 stay reachable in plaintext once the proxy is live and anyone can bypass TLS by going straight to them. Firewall both at the host, or bind them to loopback in the same .env so only the proxy can reach them:
FRONTEND_PORT=127.0.0.1:3000
BACKEND_PORT=127.0.0.1:8000 Apply and verify
Recreate the containers so both the new origin and the new bindings take:
docker compose up -d
curl -sI https://app.yourcompany.com | head -1 # expect HTTP/2 200If the browser still calls the old host, the frontend container wasn’t recreated: run docker compose up -d frontend again.
Keep secrets out of .env
For anything past a single trial host, store the nine production secrets in a dedicated manager instead of a plain file on disk:
- AWS Secrets Manager
- HashiCorp Vault
- GCP Secret Manager
Rotate the dev-only defaults first: the Checklist lists all nine and the overlay that refuses to boot without them. Then move those values into the manager and inject them as environment variables at deploy time.
Isolate the code executor
code-executor runs with privileged: true so it can sandbox evaluation code. Keep it on a host you control, an EC2 or GCE instance, never a managed-container platform that can’t grant that flag. If your platform can’t, the rest of the stack still runs without it and you lose only code-based evaluations.
Dive deeper
Questions & Discussion