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 ./Caddyfile

Both 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 200

If 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

Was this page helpful?

Questions & Discussion