Sharing a port, linking a file
Two ways to let somebody who is not you see something inside a sandbox. Neither of them opens a port on the sandbox. That is the whole design, and it is worth being precise about, because "it has a public URL" and "it has a public port" sound like the same sentence and are not.
envend share omega-k4zt 4000 # https://omega-k4zt-4000.envend.dev
envend link omega-k4zt out/report.html
# https://omega-k4zt.envend.dev/f/out/report.html?exp=…&sig=…
Both are also MCP tools — env_share and env_link — so an agent can
do either without you.
A sandbox has no public port
There is no create-time alternative. env_create has no ports
argument, no backend publishes a port, and a sandbox nobody has shared has no public route to
it of any kind. There used to be a second way — one provider only, plain HTTP on a high port,
no TLS, no revocation, and reachable from the moment the machine booted — and it was deleted
rather than deprecated, because a worse version of this sitting alongside it was a trap rather
than a choice.
What actually happens
A sandbox's ssh server, its file server and its local agent are all envd, and
all sit behind a tunnel that answers exactly two peers: you, and the gateway. The gateway is on
that list because it put itself there when the sandbox was created, using one long-lived key it
keeps beside its OIDC signing key. So a request for a shared port arrives at the
gateway, not at the sandbox:
browser ──https──▶ the gateway
│ is this hostname a share?
▼
the gateway's own tunnel, key-pinned
▼
sandbox:4000
TLS terminates at the gateway. Nothing is opened on the machine and nothing new starts inside it. Three consequences worth knowing:
- Unsharing is instant and total. It removes a row in the gateway. There is nothing to restart, nothing cached, and no window in which somebody who already has the URL still gets through.
- A sleeping sandbox still works. The gateway asks the provider to start it and holds the request, for up to a minute. The first page load after an idle spell is slow; the rest are not.
- A share is not a login. Anyone with the URL reaches the app. The tunnel protects the sandbox, not the app: nothing else on the machine is reachable, and what you shared is shared. The app's own authentication is the only gate in front of it.
Do not share a port serving anything you would not put on the open internet.
Share a declared service, not something you started by hand
What comes back after a wake has to be listening after the wake. On a backend that
rebuilds everything outside /work, a server you started in an ssh session is gone
and the URL answers nothing. Declare it instead — services on
env_create, or in the profile — and envd brings it back at every boot
without being asked. See Declared services.
File links
A file link is the same path with a different endpoint at the far end: the gateway asks the
sandbox's own agent for one file out of /work and streams it out. This is how you
hand a file to a person — a coverage report, a rendered page, a screenshot, a PDF. They open
the URL on any device, with no envend account, no key and nothing to install.
The path has to be under /work, and the check happens after symlinks
are resolved rather than before, because a symlink at /work/out pointing at
/ contains no .. and never leaves /work as a string.
Links expire — an hour by default, seven days at most — and prefer short: a link is cheap to make again. They are signed, so the sandbox, the path and the expiry cannot be edited by whoever holds one, but until it expires anyone holding it can open the file. Treat it like a link to a private document.
envend revoke-links <name> rotates the per-sandbox signing secret, which
invalidates every link ever issued for that sandbox at once. There is no list to sweep, because
a link is verified rather than looked up, so revocation cannot miss one. Links made
afterwards work normally. Shared ports are unaffected — unshare is the equivalent
for those.
Two limits
- A sandbox created before the gateway had its own key cannot be shared. A
sandbox's list of admitted peers is baked into its machine config at create and cannot be
edited afterwards.
env_sharesays so, and says the only fix: destroy it and create it again./workgoes with it, so copy anything worth keeping out first. - Destroying a sandbox kills every share and every link immediately, for everyone, including people who already have the URL.
Operator setup
Sharing needs two DNS records and one certificate, none of which the gateway can do for itself. Until they exist, every share hostname simply fails to resolve.
- A wildcard
CNAMEfor*.envend.devpointing at the gateway. A specific record always wins over a wildcard, so the API hostname is unaffected — and the gateway excludes it by name as well, so a sandbox calledgatewaycannot shadow it. - A wildcard certificate for
*.envend.dev. It can only be issued against a DNS-01 challenge, so one more_acme-challengerecord is needed; the hash in its value is generated per certificate, so take it from the certificate command's own output rather than from any document. - Nothing else.
ENVEND_PUBLIC_DOMAINalready defaults toenvend.dev; set it only if shares live under a different domain, and remember the wildcard DNS and the wildcard certificate have to name the same one.
The success case for a check is a 404: TLS terminated, the request reached the
gateway, and the gateway correctly answered that nothing is shared at that hostname.