Networking and access
You reach a sandbox in two ways: over SSH, which goes through the workspace API, and through HTTP ports, which the workspace serves on public URLs. In the other direction, the sandbox's network mode and egress rules decide what the sandbox itself can reach: the internet, your Zerops project, or nothing.
SSH
zbox ssh opens an SSH session to a sandbox. The connection is carried through the workspace API, so you need no SSH key, no VPN and no SSH server in the image: the CLI's access to the workspace (API key or Zerops login) is the proof. It works for every base and in every network mode, airgapped included, and wakes a parked sandbox first.
Sessions run as root. Local port forwards and agent forwarding work, remote forwards (-R) are refused.
To use plain ssh, scp, sftp, sshfs or a remote SSH editor (VS Code Remote - SSH, JetBrains Gateway), install the SSH config block once:
--install writes a Host zbox-* block next to the zbox config file and adds an Include line for it at the top of ~/.ssh/config. Without --install, zbox ssh-config only prints the block. The block always follows the current workspace, and the host name is zbox- plus the sandbox ID, not its name. rsync works too when the image has rsync installed (the default base doesn't).
Every sandbox has its own host key, generated by the workspace when the sandbox is created, forked or restored. The CLI fetches it from the API and pins it in its own known_hosts file, so host key checking stays strict.
An SSH connection ends when the sandbox is parked and when a snapshot with memory is taken. It can't be resumed, reconnect instead.
Ports
A sandbox exposes nothing until you declare a port. A port is written as port[/access], for example 3000, 3000/public or 5432/project, where access is one of the access modes. Add /wake or /no-wake to override wake on request. Ports are HTTP: the workspace proxies HTTP requests to them.
Port changes apply to a running or parked sandbox on the next request. In the console, a sandbox's ports are edited in its Policy tab. A sandbox created from a template starts with the template's default ports unless you declare your own.
The application has to listen on all interfaces (0.0.0.0), not only on localhost: requests arrive on the sandbox's network interface. If nothing answers on the port, the request fails with UNREACHABLE.
The workspace forwards the request with the original Host header and X-Forwarded-* headers. It removes the Authorization header, its own session cookie and the ticket and token query parameters before the request reaches the sandbox, on every access mode.
Raw TCP ports, and per-port ingress limits: maximum connections, held requests, request rate and body size.
Port hostnames
Each port gets one URL per workspace domain, built from the port number and the sandbox ID, for example https://3000-l8mvceq5-zbox-6da-8081.prg1.zerops.app on the Zerops subdomain or https://3000-l8mvceq5.box.example.com on your own domain. See domains for the two forms and how to add a domain.
The URLs are listed by zbox port list and in the sandbox's hosts in the API, in the order of the workspace domains. A workspace without a domain gives ports no public URL. All of them are served by the workspace's port 8081 (subdomain access).
Access modes
The access mode of a port decides who gets through:
| Mode | Who can reach the port | How |
|---|---|---|
members (default) | Whoever can use the workspace API, with the API key or a Zerops login | A one-time ticket from zbox open or the console's port links, exchanged for a session cookie |
ticket | Anyone you give a ticket | A ticket from zbox credential issue, exchanged for a session cookie |
token | Clients holding a token | Authorization: Bearer <token>, or ?token=<token> in a link |
public | Anyone | No credential |
project | Services in your Zerops project | Requests from the project network only (how) |
In the API and in policy JSON, the modes are written in uppercase, for example MEMBERS. The workspace API key itself never opens a port, in any form. A request without a valid credential gets UNAUTHORIZED, a project port reached through the public URL gets PROJECT_ONLY.
To open a port in the browser, signed in:
zbox open issues a credential valid for 60 seconds for this one opening and opens the port URL with it: a ticket for a members or ticket port, a token for a token port. A public port opens bare. The workspace exchanges the credential for a session cookie and redirects to the clean URL, so the credential never stays in the address bar or reaches your application. The port links at the top of a sandbox's page in the console work the same way.
Ingress credentials
Ingress credentials are tickets and tokens for one port of one sandbox. A ticket is used once and works for members and ticket ports. A token can be used repeatedly and works for token ports. The secret is shown only when the credential is issued.
# a ticket for port 3000, valid 15 minutes unless --ttl says otherwise
zbox credential issue app --port 3000 --name review
# a token for a token port, valid 30 days unless --ttl says otherwise
zbox credential issue app --port 9000 --kind token --ttl 24h
zbox credential list app
zbox credential revoke <credential-id>
issue also prints an Open URL with the secret as a query parameter. Opening it signs the browser in with a session cookie. A client can send a token as a bearer header instead, on every request. Revoking a credential ends its sessions on their next request. Removing a sandbox revokes all of its credentials. The console lists and issues them in the sandbox's Access tab.
A session lasts only as long as the credential it was opened with, and 12 hours at most. A session started by zbox open or a console port link ends after about a minute, when its 60-second credential expires, and the next request gets UNAUTHORIZED. Open the port again, or open it with a longer-lived ticket from zbox credential issue --ttl.
Wake on request
A request to a port of a parked sandbox can wake it. The request is held while the sandbox wakes and is then passed on. Wake on request is on by default for every access mode except public, and /wake or /no-wake on the port spec overrides it. A request that fails the access check never wakes anything. A public port without wake on request answers PARKED.
A request is held for 30 seconds at most, also while the sandbox is being created or is in another transition. Set ingress.holdTimeoutSeconds in the sandbox's policy to change it, the same way as the egress rules. A sandbox with stale secrets isn't woken by a request, it answers SECRETS_STALE until you wake it explicitly.
Error responses
When the workspace can't pass a request on, it answers with a JSON body like the API's, for example {"error":{"code":"PARKED","message":"..."}}:
| Status | Code | Meaning |
|---|---|---|
| 404 | NO_ROUTE | No sandbox with this ID, or the port isn't declared |
| 401 | UNAUTHORIZED | The port needs a valid ticket, token or session |
| 403 | PROJECT_ONLY | A project port was reached from outside the project network |
| 410 | GONE | The sandbox was removed |
| 503 | PARKED | The sandbox is parked and the port doesn't wake it |
| 503 | SECRETS_STALE | The sandbox has stale secrets and needs an explicit wake |
| 504 | HOLD_TIMEOUT | The sandbox didn't come up within the hold timeout, Retry-After is set |
| 502 | FAILED | The sandbox is in the failed status |
| 502 | UNREACHABLE, UPSTREAM | Nothing listens on the port, or the application closed the connection |
On the Zerops subdomain, 502 answers show the Zerops error page instead of the JSON body.
Egress
What a sandbox can reach is set by its network mode, fixed when the sandbox is created:
| Mode | Reaches |
|---|---|
project_internet (default) | The internet and your Zerops project's private network |
internet | The internet, and the project's DNS resolver but nothing else of the project |
project | The project's private network only |
airgapped | Nothing: the sandbox has no network interface |
In the API and in policy JSON, the modes are written in uppercase, for example PROJECT_INTERNET. The workspace policy can limit which modes are allowed (workspace policy). In every mode, a sandbox can't reach other sandboxes or its own workspace directly. It can reach another sandbox's port through the port's public URL when its mode includes the internet. Every mode except airgapped reaches the credentials proxy.
zbox ssh, zbox exec and the other commands work in every mode, airgapped included: they don't use the sandbox's network. Ports of an airgapped sandbox never answer.
Egress rules
Inside its mode, a sandbox can be restricted further by the egress part of its policy:
denyrules are checked first and drop matching traffic.allowrules let matching traffic through, still within the network mode. With anegressblock present, everything not allowed is dropped, so an emptyallowlist leaves only DNS and the credentials proxy.- A rule's
cidris an IPv4 address or prefix, orprojectfor the project's private network.portslists destination ports (TCP and UDP), an empty list means every port. Domain names and IPv6 prefixes are refused. dnsisPROJECT(the sandbox uses the project's DNS resolver) orNONE(no DNS at all).
Without an egress block, the network mode alone applies. A sandbox created from a template gets the template's default egress unless its own policy sets one. Set the rules at create with zbox new -f policy.json, or change them on a running or parked sandbox, which applies at once:
Egress filtering by domain name, and bandwidth limits.
Project services
A sandbox whose network mode includes the project reaches the other services of your Zerops project by hostname with the .zerops suffix, for example http://api.zerops:3000 or db.zerops:5432. The bare hostname doesn't resolve inside a sandbox.
Reach a sandbox from the project
Services in the project reach a sandbox's project port through the workspace's port 8081 on the project network, with the port's host in the Host header:
Requests that come through the public URL are refused with PROJECT_ONLY.