Appearance
SSH & the front door
The front door is a plain SSH listener where the username is a box spec and the client key is the identity — no signup, no pre-created box. It's how humans reach boxes; anything that speaks SSH works (ssh, VS Code Remote-SSH, scp, rsync, JetBrains Gateway), with no public port on the box.
sh
ssh proj@host # spawn/attach box "proj" (default image)
ssh proj:ubuntu-22.04@host # pick a catalog image
ssh proj:debian-12:cpu+5m@host # image + flavor, stay alive 5m after disconnect
ssh proj+@host # force a fresh box
ssh images@host # list the image catalog (spawns no box)
ssh cli@host ls # the management CLI (spawns no box)How it works
The box's agent dials out to hopboxd and serves an SSH server over that reverse tunnel — nothing routes into the box. The front door authenticates you by key, then proxies your session straight into the box's SSH server, so shells, exec, and the sftp subsystem (scp/sftp/rsync) all work end-to-end.
Port forwarding
Local forwarding (ssh -L) works — tunnel a port on a box back to your machine:
sh
ssh -L 8080:127.0.0.1:8080 proj@box.hopbox.dev # reach the box's :8080 at localhost:8080The box dials the target from inside itself, so a forward reaches only what the box can reach — its own loopback and whatever its egress allows. That is the box's existing network fence doing its job, not a separate forwarding policy; you cannot use a forward to pivot to something the box couldn't already talk to. A forward to a closed port fails with the target named (direct-tcpip: dial …: connection refused) and leaves the session intact.
This is what VS Code Remote-SSH, JetBrains Gateway, and dev-server / database tunnels use. Reverse forwarding (ssh -R) is not supported yet.
For an HTTP service, you may not need a tunnel at all. The API can proxy a box port directly — GET /v1/boxes/{box}/proxy/{port}/…, authed by your hbx_ key — so a browser can open the app a box is serving with nothing to set up. See the HTTP API. Reach for ssh -L when the thing on the port isn't HTTP (a database, a raw TCP service) or when you want it on your own machine's localhost.
Username grammar
[workspace/]name[:image[:flavor]][^ttl][+duration]| Segment | Meaning |
|---|---|
workspace/ | Optional workspace prefix. Omit = your default workspace (unchanged behavior); acme/web is box web in workspace acme, with its own /wrk drive, home, and secrets. |
name | Box name, created on first connect. Names are per-owner, and per-workspace — your proj is distinct from another key's proj, and acme/proj is distinct from your default proj. Append + to force a fresh box. |
:image | Box image. With docker, any OCI ref. With microVM, a catalog name (ssh images@host lists them). Omit = --default-image. |
:flavor | Hardware flavor. A recognized named flavor sets the box's CPU/memory caps, overriding the front-door defaults. |
^ttl | Hard deadline from creation (30m, 2h). The box is torn down then, whatever is happening — an attached session, a keep-alive pin and the durable flag do not extend it. Clamped to your tier's ceiling. Omit = no deadline (a run box gets its tier's default). |
+duration | Stay-alive grace after disconnect (5m, 1h). Omit = the daemon default grace. |
^ttl is written before +duration when both are used — proj:go^30m+5m is a box that keeps running 5 minutes after you disconnect and is destroyed 30 minutes after it started. ^ rather than ! because an interactive bash or zsh would history-expand a ! inside an ssh argument.
A deadline is what makes a box safe to walk away from: a build that wedges or an agent that loops stops costing you something at a time you chose, rather than at idle-reap hours later. It is enforced by the daemon, so it survives whatever started the box crashing.
Some usernames are reserved:
images(orimage) — prints the image catalog (spawns no box).cli— your management CLI (ls,rm, …), key-scoped (no box).sudo— the fleet-wide admin console (all owners); allowed only for keys in the daemon's--admin-keysfile (no box)._— an anonymous throwaway box: a fresh one each connect, no stable name.session-<id>— reconnect to one of your boxes by id (or the id-prefix shown byssh cli@host ls).jump— the confidential relay:ssh -J jump@host root@<box>reaches a box end-to-end, with the gateway relaying ciphertext only (no box).hbxs_<token>— a share link: connects the holder to someone else's box (the one the token grants), with any key. Minted by the box owner withssh cli@host share <box>.
Lifetime — auto-suspend
Front-door boxes auto-suspend when idle: after --idle-timeout (default 2m) with nobody attached and no shell open in the box, it is snapshotted to disk (compute stops) and your next connection resumes it — nothing is lost on disconnect, you reconnect straight back in. A box never reconnected to is eventually idle-reaped (--idle-reap, default 3h); box-guest auto-suspend off or a keep-alive pin holds it longer. See lifecycle.
Identity
Your SSH key is your identity; the box is owned by that key's fingerprint. Reconnecting with the same key reuses the same box (within its lifetime); a different key connecting to the same name is refused while that box is alive. See Identity & tiers.
Confidential access — ssh -J
By default the front door terminates your SSH: it authenticates you, opens its own session into the box, and proxies it — so the gateway can see the session. For work where the gateway must not be able to read the session, reach the box end-to-end with a jump:
sh
ssh -J jump@box.hopbox.dev root@mybox # end-to-end; the gateway only relays
scp -J jump@box.hopbox.dev ./f root@mybox:/tmp/ # scp/sftp ride the same relayThe reserved jump front-door user puts the connection in relay mode: the gateway forwards raw bytes to the box's SSH server and never opens a session of its own — it holds no key to the box and sees only ciphertext. The box authenticates your key directly (the gateway hands it your public key at spawn; your private key and the session never leave your client).
A box first reached this way is confidential for its lifetime: it authenticates you and refuses the normal terminated path — ssh mybox@host returns a pointer to use ssh -J. The jump target is a box spec (root@mybox, or root@mybox:python+1h).
Reference
- ssh cli commands —
ls,rm,help. hopboxdconfig —--ssh-addr,--grace, the front-door flags.