Purgatory architecture

Traefik v3.6 routes requests; Sablier measures idle time and puts a sandbox to sleep. Purgatory creates sandboxes, wakes every sleeping one (within the owner's limits), takes long-asleep ones into deep sleep, and archives them on an explicit delete. A running sandbox's web traffic does not go through Purgatory. Nothing is ever deleted automatically.

1. Components and the path of a request

User app in a browser Coding agent psql, ssh, docker CLI (TLS, port 443) Browser (UI) / API OIDC or admin sign-in, p7y_ tokens Traefik v3.6 :80 / :443 frps-<name> <raw>-*.<domain> + Sablier middleware only while it runs p7y-wake catch-all, prio 1 → /wake p7y-tcp (TCP) SNI *-tcp, *-docker TLS passthrough p7y.<domain> registry.<domain> HTTPS Sablier :10000 idle time per group · state.json renew sandbox stack (compose project = Sablier group) socat carries the router; healthy = tunnel up frps vhost :8080 API :7500 <name>-socat:8080 sandbox (DinD / sysbox) own dockerd :2376 (TLS, own CA) frpc docker-gen frpc config from published ports inner stack (back via restart policy) portainer app, db… sshd :22 (SSH sandboxes) tunnel :7000 Purgatory :3000 (host :8081) /wake: wakes asleep and deep-sleeping sandboxes within the owner's limits create (quota), archive, deep sleep TCP gateway :4443 (-tcp, -docker) /v2/auth tokens, push scans UI, Overview, terminal, logs usage and disk sampling Sablier /metrics: time left quota: running sandboxes per user server limit: running + asleep data/: tokens, keys, scans, disk /wake TLS TCP / dockerd (traefik-net) registry:2 token auth, notifications token, push Docker engine (host) /var/run/docker.sock compose stop the idle group opt/sandboxes · opt/archive compose, certs · volumes on delete
A web request to a running sandbox goes through its frps-<name> router to <name>-socat:8080 (by name, not by container address) → frps → tunnel → frpc → the app; Purgatory is not on that path, so running apps keep working while it is down. The router lives on the socat container without allownonrunning: while the sandbox sleeps there is no router, and the request falls to the p7y-wake catch-all, where Purgatory decides whether it may wake. The Sablier middleware on the router only renews the idle session; Sablier stops the group once it has had no traffic for idle_timeout. TLS TCP addresses (*-tcp) and Docker access (*-docker, TLS passed through to the sandbox's own dockerd) go through Purgatory's TCP gateway, which also wakes the sandbox. The registry asks Purgatory for tokens and tells it about every push, which Purgatory scans for secrets.

2. The lifecycle of a sandbox

POST /sandboxes (a running place free) Running counts against the quota Asleep containers stopped, network kept Deep sleep no containers or network, data kept Archived opt/archive, the name is free idle_timeout without traffic (Sablier), or Sleep a request, Wake, TCP or Docker: Purgatory starts it, a few seconds refused at the running limit (the owner may swap) deep_sleep_after (Purgatory, compose down) first request: Purgatory, compose up, ~20–60 s refused at the running or server limit DELETE DELETE
Sablier makes the orange transition (to sleep), Purgatory every other one. Only running sandboxes count against a user's quota (SANDBOX_QUOTA, default 3): letting one sleep frees its place. Asleep ones still hold a Docker network, so the server-wide SANDBOX_MAX_TOTAL (default 200) counts running and asleep ones. At the running limit nothing is put to sleep automatically: the wake is refused, and the owner can choose a running sandbox to put to sleep instead. Only an explicit DELETE archives a sandbox, from any state.

3. What happens to an incoming request?

Request <raw>-….<domain> Sandbox router exists? (only while it runs and socat is healthy) Sablier renews the session idle countdown starts over yes the app answers (socat → frps → frpc) Purgatory /wake (catch-all) which sandbox, in what state, within the owner's limits? no waiting page; Purgatory starts it (asleep / deep) may wake waiting page: just started, router not loaded yet 409 Running limit reached · retries every 30 s at the limit 404 → error-pages (never existed / archived) no sandbox
A sandbox router exists only while the sandbox runs and its socat is healthy (the tunnel is up). Otherwise every request reaches Purgatory: it starts an asleep sandbox (compose start) or a deep-sleeping one (compose up) when the owner has a running place free, shows a waiting page until the router is back, and at the limit answers Running limit reached with a link where the owner can choose what sleeps (the page does not name the owner's other sandboxes). A sandbox Purgatory has just started also gets the waiting page until Traefik picks up its router.

Known weak spots