About hh.pe
hh.pe (Hot Handoff Platform Engine, sandboxo in the code) is a self-hostable-to-be
Deno Deploy ripoff. It speaks enough of Deno Deploy's APIs that the unmodified
deno deploy CLI, jsr:@deno/sandbox and npm:e2b work against it, and it
runs everything on one host you control: every build, every app instance and
every interactive sandbox gets its own gVisor sandbox.
The other sections are Deno's own Deno Deploy and Deno Sandbox documentation (MIT, from denoland/docs). Read them with this page in mind: where they say console.deno.com, think of the hh.pe web UI, and anything listed under [what-is-missing] is not there.
How it works
-
Upload:
deno deploysends a manifest of file hashes and then uploads only the files the server does not have yet. -
Build: install and build commands run in a pre-warmed build sandbox, with network for jsr and npm, a timeout and optional cgroup memory and CPU limits. The result is a self-contained directory, packed into a read-only EROFS image, so the app starts with
--cached-only. -
Promote: a production revision becomes the app's current revision once its build succeeds.
-
Serve: the first request takes a pre-warmed runtime sandbox, mounts the build image into it and proxies to it. Warm means V8 has already booted, using Deno's prewarm control socket (the mechanism Deno Deploy itself uses), so a cold start takes hundreds of milliseconds.
-
Idle: an instance is stopped after zoned's
idle_mswithout requests, and the pool refills in the background.
Apps are served on <app>.<domain>, previews of a single revision on
<app>--<rev>.<domain>. Records live in Redis and blobs in S3, see
State directory. The control plane (sandboxod), the sandboxes
(zoned, one per availability zone) and the routers that take public traffic
over HTTPS (obice, envoy driven by its own xDS server) are separate programs
that can run on separate hosts; zones only dial out, so they can sit behind
NAT, see Zones.
What works
-
deno deploywithDENO_DEPLOY_ENDPOINT: deploys (production and preview),create,apps,deployments,env,logs,login. Thedeno.jsondeploysection (install,build,predeploy,runtime,buildTimeout) overrides the app config per revision. -
Quick deploy links compatible with Deno Deploy's
/new?clone=..., for publichttps://git repositories. Empty settings are detected from the repository. -
Sandboxes through
jsr:@deno/sandbox,deno deploy sandboxandnpm:e2b, all on one core: a sandbox created through one API can be used through the other. Volumes, timeouts,exposeHttpand an SSH gateway forexposeSsh. -
Logs and OpenTelemetry: app, build and sandbox logs are stored in Loki; apps export traces and metrics the way they do on Deno Deploy, without endpoints or credentials in the app; see Observability.
-
Web UI: apps, revisions with promote and redeploy, live build and app logs, config and env editors, sandboxes with a web terminal, and these docs at
/docs. -
Cron:
Deno.cron()definitions are discovered when a revision is built and run on the production revision, with retries, execution history and traces. Differences: no branch timelines, names are capped at 64 characters by Deno, and the app must callDeno.serve.
What is missing
-
More than one control plane host, latency-aware routing (a router sends an app's requests to its zones by replica count, not by distance), authentication on tunnels and route fetches beyond a private network, moving volumes between zones.
-
Custom domains: routers serve
<domain>and*.<domain>with one certificate. -
WebSockets and other HTTP upgrades through the app router.
-
KV, databases, queues, cloud connections, OIDC,
deno run --tunnel. -
GitHub integration beyond public clones: no GitHub app, no builds on push.
-
Env contexts beyond Local: production and preview always share variables; secrets are write-only but stored unencrypted on the host.
-
Framework presets other than Next.js: give install, build and runtime settings explicitly.
-
Sandboxes: booting from a snapshot, bootable volumes, VS Code,
spawnDeno,fetch, deploying from a sandbox. -
Volume capacity is recorded but not enforced.
Running
nix develop
pnpm install
deno task ui:build
deno task docs:build
process-compose up
SANDBOXO_TOKEN=secret deno task dev
dev.ts runs sandboxod and two zones in one process, with the config the dev
shell generates (SANDBOXO_DEV_CONFIG); sandboxod/main.ts and
zoned/main.ts run them separately, each with its own config file, see
Configuration. The web UI, the API and the Deno Deploy
endpoint listen on http://127.0.0.1:8001, apps on
https://<app>.sandboxo.localhost:8443 (an app named root on
https://sandboxo.localhost:8443 itself). Redis, Garage,
the telemetry backends and the router come from process-compose up. The
router's certificate is signed by a development CA (packages.dev-tls): deno
in the dev shell trusts it through DENO_CERT, curl needs
--cacert $SANDBOXO_DEV_TLS/ca.pem.
export DENO_DEPLOY_ENDPOINT=http://127.0.0.1:8001 DENO_DEPLOY_TOKEN=secret
deno deploy create --org local --app hello --source local \
--runtime-mode dynamic --entrypoint main.ts --region us
deno deploy --org local --app hello --prod
curl --cacert $SANDBOXO_DEV_TLS/ca.pem https://hello.sandboxo.localhost:8443/
Deno sandboxes additionally need DENO_SANDBOX_ENDPOINT=http://127.0.0.1:8001
and DENO_DEPLOY_ORG=local. For e2b, set E2B_API_URL=http://127.0.0.1:8001,
E2B_SANDBOX_URL=https://sandboxo.localhost:8443 and E2B_API_KEY=secret.
Caveats
-
The
deno deployCLI keeps one keychain token for all endpoints, sodeno deploy loginagainst hh.pe replaces your real Deno Deploy token. PreferDENO_DEPLOY_TOKEN, which is never stored. -
The
deno deployCLI evaluates server responses as code: only point it at an hh.pe you trust.