Architecture
Almost every shard command is a request to one daemon on the host. The exceptions are shard setup, shard daemon itself, shard serve, shard info, and the shard tokens commands that mint, list and revoke tokens. Help and --version also work without a daemon. The CLI sends each local request over a unix socket, and you can send the same request yourself:
sudo curl --unix-socket /var/lib/shard/shard.sock http://localhost/v0/versionOn a Mac, leave out sudo. shard daemon status shows the version, the provider, the process details and the background tasks of the daemon that answers.
The parts
Section titled “The parts”shard CLI on the host ─────── unix socket ───────┐ ├──> shard daemon ──> provider ──> sandboxCLI or SDK elsewhere ── TLS proxy ── shard serve ┘ └ shard-init, PID 1| Part | What it does |
|---|---|
shard CLI |
Sends commands to the daemon and prints the answer. It holds no sandbox state. |
| TypeScript and Python SDKs | Send the same requests from your program, through shard serve. |
shard serve |
Checks the bearer token of each request, then passes the bytes to the daemon’s socket. |
shard daemon |
Owns every sandbox, image, snapshot, secret and policy, and runs every command on them. |
| Provider | Isolates each sandbox: runsc for gVisor, sysbox-runc, runc, one firecracker process per sandbox, or one Virtualization.framework VM per sandbox on a Mac. |
shard-init |
Runs as PID 1 inside every sandbox. It starts the sandbox’s command, if any, and stays up. |
The daemon
Section titled “The daemon”The daemon is the only process that writes the sandbox records, the images, the snapshots, the secrets and the policies. Every command on them, local or remote, goes through it, so these stores need no lock between processes. It keeps them under /var/lib/shard by default. shard daemon --root selects another data directory.
One daemon serves one data directory. It takes a lock on daemon.lock there, and a second daemon on the same directory refuses to start. On Linux a host runs one daemon, because every data directory shares the sandbox bridge and its nft table. A daemon on a second directory refuses to start while another one runs. On Linux it runs as root. On a Mac it runs as your user, which owns the data directory.
The daemon runs these background tasks. shard daemon status lists them:
| Task | What it does |
|---|---|
api |
Serves the REST API on shard.sock. |
proxy |
The egress proxy on the gateway of the sandbox network. For a sandbox with a policy or a secret, it judges each HTTP and HTTPS request by host name. It puts a secret value into the headers of an HTTPS request only when the secret is granted to that destination. |
dns |
Answers DNS lookups for every sandbox that has a policy. |
egress-log-tailer |
Copies the host’s records of dropped packets into each sandbox’s egress log. |
liveness |
Every 5 seconds, checks each running sandbox against the provider and corrects the record. |
firewall |
Every 5 seconds on Linux, puts back the host firewall accepts for the sandbox bridge that a reload of ufw or iptables flushes. |
processes |
Every second, copies the state, restarts and last exit of each process onto its sandbox’s record, which shard ps reads. |
ports |
Every second, listens on each host port forwarded into a running sandbox, and closes the rest. Each connection goes to the sandbox through its provider. |
autostart |
Once per daemon start, after the proxy, dns and firewall tasks are up, starts the sandboxes and processes whose restart policy brings them back. |
held-log-rotation |
Caps each output log that the runtime writes at about 16 MiB, and keeps one older file. |
log-reopen |
Reopens the daemon’s own log file, if shard daemon --log names one. |
A failed task restarts after a backoff of up to one minute.
A sandbox outlives the daemon
Section titled “A sandbox outlives the daemon”Sandboxes that already run normally survive a daemon restart. On startup, the daemon checks their records against the provider before it serves any request. A create, fork or restore that the restart interrupted can instead end as failed or stopped. A record that says running with no process behind it usually becomes stopped, with the reason daemon restarted and found no process. When the provider reports an OOM kill or a supervisor failure, the record keeps that specific reason. The daemon then starts a sandbox that ran out of memory again, as it does while it is up.
Then the autostart task starts each stopped sandbox that holds an always process, or an unless-stopped one that neither shard stop nor shard kill ended, and runs those processes again. In a sandbox that still runs, it starts any such process that the guest no longer runs. This is how sandboxes come back after a host reboot.
An exec session does not outlive the daemon. A restart disconnects every client and discards the daemon’s output buffer and command handle. A command that already started can continue inside the sandbox, but the client cannot reconnect to that handle.
A sandbox also outlives its own command. When the command exits, the sandbox stays running, and shard list shows the exit code, for example running (exited 7). shard stop ends the sandbox without deleting its files. A forced remove, a host shutdown or a runtime failure can also end it.
Remote access
Section titled “Remote access”The daemon never serves its API on TCP. That keeps the API behind the socket’s file permissions, and a caller from the network always meets a token check first. To reach the daemon from another machine, run shard serve on the same host. shard serve listens on 127.0.0.1:7850 by default, behind a proxy that terminates TLS. It reads the request line and the headers, and rejects the request unless the token is valid and its scopes cover the route. Only then does it connect to the daemon. It adds the token’s scopes as a header and asks the daemon to close the connection after the answer. It passes the body and the answer on unchanged.
On Linux, a non-root user needs membership in the shard group to reach the socket. Without that group, only root can reach it. On a Mac, run shard serve as the daemon’s user.
The CLI picks the daemon in this order:
--remote <url>, orSHARD_REMOTE.- The connection that
shard setupsaved. - Otherwise, the local daemon’s socket.
For a remote connection, the CLI uses SHARD_API_KEY first. Otherwise, it uses the saved key only when the selected URL names the saved server. An explicit --remote "" selects the local socket and ignores the saved connection.
The SDKs use a remote URL and API key from constructor options or the environment variables SHARD_REMOTE and SHARD_API_KEY. They ignore the saved connection and always go through shard serve.
Isolation and the network
Section titled “Isolation and the network”The provider sets how strongly each sandbox is isolated from the host. Each sandbox gets its own address. The host, not the sandbox, holds the network rules, and it writes them again on every create, start, resume, fork and policy change. A sandbox never holds a secret value. Its commands see a placeholder, and the egress proxy puts the value into the HTTPS request headers only on the way to a destination that the secret is granted to.