Skip to content

Purgatory roadmap

Open, not yet scheduled

  • Isolation between neighbouring sandboxes: published ports (foxglove image). Today this is Docker's usual behaviour: a port an inner container publishes on all interfaces (9000:9000, as the starter does) is reachable from every other sandbox on the DinD container's traefik-net address, protected only by the app's own login; 127.0.0.1:9000:9000 stays private (see Isolation between sandboxes). The frps API (:7500) has a password since 0.6, and the Docker API needs the sandbox's client certificate. To close published ports to neighbours: in the foxglove image (dind and sysbox), after dockerd starts, iptables -I DOCKER-USER -i eth+ -m conntrack ! --ctstate RELATED,ESTABLISHED -j DROP (and ip6tables); frpc connects from the inside, so the tunnels keep working. To work out:
  • Purgatory's TCP gateway connects over traefik-net too: the rule would cut the -tcp addresses, SSH and Docker access unless Purgatory's address is let through (it changes when Purgatory is recreated; a fixed address on traefik-net, or a mark the rule can match);
  • sandboxes that are meant to talk to each other (one's app using another's database): opt in per sandbox, or through the TLS TCP addresses;
  • a new foxglove release for both runtimes, and existing sandboxes on it;
  • the "by design" check in tests/isolation.sh reversed.
  • A Kata Containers runtime, for untrusted code. Each sandbox in its own lightweight VM with its own kernel, so root inside a sandbox no longer means root on the host (today's dind runtime is privileged). Docker supports Kata through its containerd shim ("runtimes": {"kata": {"runtimeType": "io.containerd.kata.v2"}} in the host's daemon.json, then runtime: kata in the sandbox's compose service), so it fits next to dind and sysbox as a third runtime with the same wiring (FRP tunnel, Traefik route, Sablier, docker_data volume, archive). To work out:
  • the host needs hardware virtualization (KVM): bare metal, or a cloud VM with nested virtualization;
  • dockerd inside the Kata VM: privileged there applies to the VM only, not the host, but must not pass the host's devices through;
  • the docker_data volume and its performance (virtio-fs), and how the CPU and memory limits map to the VM's size;
  • a foxglove image for it, and the same integration tests as for the other runtimes;
  • still a sandbox (same API, lifecycle, quota and UI), with its isolation shown as a badge next to its name and chosen at create time ("Isolation: Container / MicroVM"): dind 🟠 privileged, sysbox 🔵 container, kata 🟢 microVM. "MicroVM" is said only where it is true: dind and sysbox sandboxes share the host's kernel.
  • GPUs in sandboxes. Today no sandbox sees a GPU. A sandbox could ask for one at create time, like CPUs and memory, and the list, the panel and the API would show which have one (a GPU column with the model, e.g. RTX 4090). It suits the sleep model: a sleeping sandbox could give its GPU back, and the next one to wake take it. To work out:
  • per runtime: dind needs the NVIDIA driver and nvidia-container-toolkit on the host, the device passed to the sandbox container and the toolkit in the foxglove image, so the inner Docker hands it on to the apps; whether and how sysbox (the production runtime) passes GPUs through, to check first; Kata only through VFIO, typically one GPU per VM;
  • detecting the host's GPUs at startup and in check-host.sh (nvidia-smi, Docker's runtimes);
  • limits and quota (SANDBOX_MAX_GPUS, the admin beyond), as for CPU and memory;
  • a whole GPU per sandbox, or shared (time-slicing, MIG);
  • what a waking sandbox does when every GPU is taken: wait, refuse, or start without one.
  • To review: every path into a sandbox through frp. Today there are two paths. Web addresses go Traefik → <name>-socat:8080 → frps → tunnel → frpc → app. TLS TCP addresses (-tcp), Docker access (-docker) and SSH (-shell-tcp) go Traefik (TLS passthrough) → Purgatory's TCP gateway → straight to the sandbox container's port on traefik-net. The frp tunnel is the one to keep and extend: frpc connects out of the sandbox, so Purgatory and Traefik never need to reach into it. That is what lets the same wiring carry sandboxes whose inside is not directly reachable: a microVM runtime (Kata, Firecracker), sandboxes on another machine or behind NAT, Kubernetes pods. The direct TCP path works for dind and sysbox on one host only. To work out:
  • TCP addresses, Docker access and SSH through frp as well (frp has TCP proxies, not only HTTP): frpc registers them like the web ports, and Purgatory's TCP gateway keeps only what it is for (TLS for the -tcp names, waking, choosing the target), connecting to frps instead of the sandbox container;
  • what frps exposes for that (a port per TCP proxy, or frp's tcpmux/STCP) and how it stays unreachable from neighbouring sandboxes;
  • the TCP path still needs Purgatory (its addresses do not answer while Purgatory is down or upgrading, and open connections drop): whether Traefik could carry it with TCP routers to frps, which have no Sablier middleware;
  • one rule for staying awake on every path: traffic in the last minute, not an open connection (today: every web request; a -docker connection while bytes go through it, never waking a sleeping one, since Docker Desktop reconnects to every context at once; a -tcp connection not at all, though it wakes one);
  • moving existing sandboxes over; the Kata runtime and Kubernetes support build on this.
  • Deploy from git. Today a sandbox gets its apps through docker compose (the API, SSH, the Docker CLI or an agent). Next: connect a repository, and a push builds the image and deploys it into the sandbox, like a self-hosted Vercel; optionally a preview sandbox per branch or pull request, which sleeps like any other and is archived when the branch is merged. To work out:
  • where the build runs: in the sandbox's own Docker (its CPU and memory limits apply, and the image goes to the Purgatory registry, which already scans pushes for secrets);
  • how Purgatory reaches the repository and hears about pushes: a webhook from GitHub/GitLab/Gitea, or polling, and a deploy key per sandbox;
  • projects without a Dockerfile or a compose file: buildpacks (Nixpacks, Cloud Native Buildpacks), or refuse them with a clear message;
  • build logs in the panel and the API, and what a failed build does to the running version (it keeps running).
  • Teams: a sandbox owned by a group, so several people see and manage it, and other teams do not; optionally a quota per team.
  • A hard disk quota per sandbox. Today a sandbox above its disk limit is only flagged (see Disk). For a hard limit: its docker_data as a fixed-size ext4 image, loop-mounted through the local volume driver (type=ext4, o=loop); to work out: moving existing sandboxes' data, resizing (only while stopped), the archive, Docker Desktop. The Kata runtime gets this from its VM disk.
  • Kubernetes support: move the runtime layer behind the Docker-specific code; Sablier's Kubernetes provider helps here.