Skip to content

Overview

A runtime for isolated sandboxes on your own infrastructure. (pre-alpha)

shard [OPTIONS] COMMAND [ARGS...]

Set SHARD_REMOTE and SHARD_API_KEY for remote access. Without a remote URL or a saved connection, shard connects to the local daemon.

Get started

Terminal window
shard setup

For automated setup options

Terminal window
shard setup --help

Run shard COMMAND --help for options and examples.

Sandboxes

Command Description
shard create create a sandbox and print its ID
shard run start a supervised process in a running sandbox
shard exec execute a command in a running sandbox
shard shell open an interactive shell in a running sandbox
shard list list active sandboxes
shard ps list the processes of a sandbox
shard logs show output from a process
shard kill stop a process and cancel its restarts
shard inspect show detailed information about a sandbox
shard stop stop sandboxes and preserve their files
shard start start a stopped sandbox with its saved files
shard remove remove sandboxes and their files
shard prune remove all stopped sandboxes
shard pause suspend sandboxes and save their state
shard resume resume paused sandboxes from their saved state
shard fork create a sandbox from a running sandbox’s memory and files
shard cp copy files or directories between your machine and a running sandbox
shard port forward host ports to ports inside sandboxes

Images, snapshots, secrets and network policies

Command Description
shard pull download an image
shard image manage downloaded images
shard snapshot save and manage filesystem snapshots
shard secret manage secrets and sandbox access to them
shard policy manage outbound network rules and view network logs

Host and access

Command Description
shard capabilities show the lifecycle verbs and swap the server supports
shard daemon start the daemon or show its status
shard info show available providers and the default for this host
shard serve start an HTTP API server with token authentication
shard setup configure a local sandbox host or a remote connection
shard tokens create, list and revoke API keys
shard version show the client and daemon versions
Option Description Default
--root <dir> directory for local shard data /var/lib/shard
--remote <url> URL of the shard API server; HTTPS recommended
--version show the client version
Variable Description
SHARD_REMOTE API server URL; --remote overrides it
SHARD_API_KEY API key from shard tokens mint
SHARD_CA_FILE custom CA certificate file; HTTPS only

Global options go before the command. If you type one after it, the command fails, and the error shows where the option goes:

shard: --remote goes before the verb: shard --remote <url> list

A command’s own options go before its arguments, as in shard exec -it web sh. shard reads an option after an argument as an argument, and the error says to put the flags before the arguments.

run, logs and kill also take their options after the sandbox and the process name, as in shard logs web api -f. In run, the command starts at the first word that is not an option. Put -- before a command that starts with a dash.

With --remote, SHARD_REMOTE or a connection that shard setup saved, a command goes to that server. --remote overrides SHARD_REMOTE, and SHARD_REMOTE overrides the saved connection. SHARD_API_KEY overrides the saved key.

An http remote encrypts nothing. Every command that uses one prints this line once, to stderr and never to stdout:

Warning: HTTP does not encrypt this connection. Use it only on localhost or through a trusted encrypted network.

The client refuses SHARD_CA_FILE with an http remote before it connects.

Some commands act on this host’s files and processes, so they run only on the daemon host:

  • pull, image list, image remove, image prune, daemon status, info, tokens mint, tokens list and tokens revoke refuse --remote, SHARD_REMOTE and a saved connection. Their error names the command.
  • daemon and serve refuse --remote and SHARD_REMOTE, and ignore a saved connection.
  • --remote "" runs a command on this host even when a connection is saved.
  • tokens scopes asks the server, so it follows the remote.

A command has one name. list and remove also take ls and rm, at the top level and under image, secret, policy and snapshot. tokens list takes ls. An alias runs the same code and prints the same help, and the help never lists it.

Short options:

Command Short Long
exec -i, -t, -it for both --interactive, --tty
list -q --quiet
logs, policy logs -f --follow
run -e, -w, -u --env, --workdir, --user
setup -y --yes
secret set --dest and --destination are the same option

The Format column shows the default --format. A dash means the command has no --format. A command that prints the id prints it even when you pass the name.

Sandboxes

Command Format Prints
create - the id
run - the process name, or the process’s output with --attach
exec - the command’s output
list table the sandboxes, or one id per line with -q
ps table the processes of the sandbox
logs - the output of one process
kill - the process name, once the process has ended
inspect json the record
stop, start, pause, resume - the id of each sandbox
remove - the id of each sandbox, or nothing for a missing one with --force
prune - the id of each stopped sandbox it removed
fork - the new id
cp - nothing

Without --all, list leaves out stopped sandboxes. The table then ends with a count on stderr, such as 2 stopped sandboxes; shard list --all. JSON and -q output have no such line.

stop, remove, pause and resume take more than one sandbox, as in shard stop $(shard list -q). Each id prints as soon as its sandbox succeeds. If a sandbox fails, the command goes on to the next one. It prints the failures on stderr at the end and exits with code 1.

Images, snapshots, secrets and network policies

Command Format Prints
pull - the reference and the digest
image list table the images
image remove - the reference
image prune - each reference it removed
snapshot create - the snapshot id
snapshot list table the snapshots
snapshot inspect json the record
snapshot remove - the id or name it was given
secret set, secret remove - the name
secret list table the secrets, never a value
secret grant, secret ungrant - the sandbox id
policy create, policy remove - the name
policy show json the policy and the sandboxes that hold it
policy list table the policies
policy attach, policy detach - the sandbox id
policy logs - the egress decisions, one JSON record per line

Host and access

Command Format Prints
capabilities table the nine lifecycle verbs and swap, and whether the server supports each
daemon - its log
daemon status table the daemon’s state and its tasks
info table the provider a daemon would pick, and why
serve - its log
setup - the wizard, or a checklist when you give every answer as an option
tokens mint json the token record
tokens list table the token ledger
tokens revoke - revoked token <id>, or revoked <n> tokens of <name> with --name
tokens scopes table every scope a token can carry on the server it asks
version table the client and daemon versions

--format takes json or table. Any other word is a usage error. If you name the default, nothing changes: list --format table prints the same as list.

A --format json call writes one value or nothing:

  • The value is indented by two spaces, and a list command prints an array.
  • tokens mint writes its value on one line.
  • A failure to parse the command, to reach the daemon or to encode the value writes nothing to stdout.
  • A list with warnings, or a daemon status with a task in backoff, still writes the whole value. Then it writes the warnings or the error to stderr and exits 1.
  • version --format json with no daemon writes nothing and fails.

inspect, snapshot inspect, policy show and tokens mint print JSON by default. The values in the examples below are synthetic.

list prints an array of sandbox records, the same on the daemon host and with --remote:

[
{
"id": "misty-otter-81c0",
"name": "web",
"image": "python:3.12",
"provider": "gvisor",
"state": "running",
"resources": {"memory_mib": 512, "vcpus": 1, "disk_mib": 2048},
"processes": [
{
"name": "api",
"command": ["python", "-m", "http.server"],
"restart": {"policy": "on-failure", "retries": 3, "backoff": 1},
"status": {"state": "running", "restarts": 0, "started_at": "2026-10-01T09:30:06Z"}
}
],
"secrets": ["API_TOKEN"],
"policy": "api-only",
"started_at": "2026-10-01T09:30:05Z",
"created_at": "2026-10-01T09:30:00Z"
}
]

id, image, provider, state, resources and created_at are always present. state is pending, created, running, paused, unresponsive, stopped or failed. resources holds memory_mib, vcpus and disk_mib. Every other field is absent when empty:

Field Present when
name the sandbox has a --name
digest the provider created it; the image digest
snapshot create --snapshot made it; the snapshot id
forked_from fork made it; the id of the source sandbox
stopped_reason shard stopped it with no operator, or shard-init died on a stop
oom the host ended it for its memory: {"kills": 1, "killed_at": "2026-10-01T09:40:00Z", "restart_at": "2026-10-01T09:40:05Z"}, where restart_at is a start the daemon still owes
failed_reason state is failed
processes run started a process in it
secrets it holds a placeholder; the secret names
policy it holds a network policy
started_at the daemon started it at least once; a resume keeps the first start

A process has name, command, restart and status, and env, workdir and user when the run set them. killed is true once shard kill ended it, until a run of the same name. ps --format json prints the same records as an array.

  • restart.policy is no, on-failure, always or unless-stopped. retries is absent when there is no cap. backoff is the first wait in seconds.
  • status.state is running, restarting, exited, killed, gave-up or stopped.
  • status.restarts counts the restarts since the run or the sandbox’s last start.
  • status.exit is the last exit, such as {"code": 3, "signal": 0}, and is absent before the first one.
  • status.started_at is the last time shard-init started the process.

inspect prints one sandbox record. It adds egress when the record names a network policy:

{
"id": "misty-otter-81c0",
"image": "python:3.12",
"provider": "gvisor",
"state": "stopped",
"resources": {"memory_mib": 512, "vcpus": 1, "disk_mib": 2048},
"processes": [
{
"name": "api",
"command": ["python", "-m", "http.server"],
"restart": {"policy": "unless-stopped", "backoff": 1},
"status": {"state": "stopped", "restarts": 0, "exit": {"code": 0, "signal": 15}}
}
],
"policy": "api-only",
"created_at": "2026-10-01T09:30:00Z",
"egress": {
"rules": [
{"action": "allow", "destination": {"kind": "group", "value": "dns"}, "protocol": "udp", "ports": [53], "id": "1", "implied": "dns"},
{"action": "allow", "destination": {"kind": "group", "value": "dns"}, "protocol": "tcp", "ports": [53], "id": "2", "implied": "dns"},
{"action": "allow", "destination": {"kind": "domain", "value": "api.example.com"}, "protocol": "tcp", "ports": [80, 443], "id": "3"}
]
}
}

egress.rules lists the rules in the order the host and the proxy enforce them. id is the rule’s position in that order, starting at "1". action is allow or deny. destination.kind is cidr, domain, domain-suffix or group. protocol and ports are absent for a rule that covers every protocol. implied marks a rule that the policy did not write: dns when a name rule opened DNS, and dns-rule when a dns rule did. When the store no longer holds the policy, egress is {"missing": true, "rules": []} and the sandbox reaches nothing.

snapshot inspect prints one snapshot record, and snapshot list prints an array of them:

{
"id": "quiet-heron-4f2a",
"name": "web-base",
"source": "misty-otter-81c0",
"source_name": "web",
"image": "python:3.12",
"digest": "sha256:0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0",
"provider": "gvisor",
"disk_mib": 1024,
"memory_mib": 512,
"size": 7340032,
"created_at": "2026-10-01T09:30:00Z"
}

name and source_name are absent when empty. disk_mib and memory_mib are the bounds the source ran under. size is the space the copy takes on the host, in bytes.

image list prints an array of images. digest is the full digest, size is in bytes, and broken is absent when empty:

[
{
"reference": "python:3.12",
"digest": "sha256:0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0",
"size": 52428800,
"created": "2026-10-01T09:30:00Z"
}
]

secret list prints the secrets, never a value:

[
{
"name": "API_TOKEN",
"destinations": ["api.example.com"],
"placeholder": "mock-API_TOKEN",
"updated_at": "2026-10-01T09:30:00Z"
}
]

policy list prints each policy with its number of rules:

[{"name": "api-only", "rule_count": 2}]

policy show prints {name, rules, dns, holders}. dns is open or closed. holders names the sandboxes whose record names the policy, and is absent when there are none.

tokens list prints the token ledger. expires_at is null for a token that never expires, and status is active, revoked or expired:

[
{
"id": "0123456789abcdef0123456789abcdef",
"name": "build-agent",
"issued_at": "2026-10-01T09:30:00Z",
"expires_at": null,
"scopes": ["sandbox:read", "exec"],
"status": "active"
}
]

tokens mint prints {token, expires_at, scopes} on one line.

info prints the provider a daemon would pick, and why. unreadable is absent unless the root holds a record that shard cannot read:

{"provider": "gvisor", "reason": "no /dev/kvm"}

daemon status is the body of GET /v0/daemon. A task has last_error when it failed:

{
"version": "v0.1.0",
"pid": 4242,
"started_at": "2026-10-01T09:30:00Z",
"socket": "/var/lib/shard/shard.sock",
"provider": "gvisor",
"capabilities": {"pause": true, "resume": true, "fork": true, "port": true, "swap": false},
"proxy": {"plain_port": 30080, "tls_port": 30443},
"tasks": [{"name": "proxy", "state": "running", "restarts": 0}]
}

capabilities is the body of GET /v0/capabilities, with the same ten keys for every provider:

{"create": true, "start": true, "stop": true, "remove": true, "pause": true, "resume": true, "fork": false, "snapshot": true, "port": true, "swap": false}

version prints the client and daemon versions. On a Mac, it adds shim, which is embedded or absent:

{"client": "v0.1.0", "daemon": "v0.1.0"}
Command Columns
list ID NAME IMAGE STATE UPTIME PROCESSES POLICY
ps NAME STATE RESTARTS EXIT POLICY COMMAND
image list REFERENCE DIGEST SIZE CREATED
snapshot list ID NAME SOURCE IMAGE SIZE CREATED
secret list NAME DESTINATIONS PLACEHOLDER UPDATED
policy list NAME RULES
tokens list ID NAME ISSUED EXPIRES SCOPES STATUS
tokens scopes SCOPE DESCRIPTION
capabilities CAPABILITY SUPPORTED

In list, PROCESSES is the processes that have not ended, of all the sandbox has, such as 2/3, or - for none. In ps, RESTARTS is 2/5 for an on-failure policy with 5 retries, and EXIT is - before the first exit, the exit code, or signal 15.

The commands that print JSON by default have these tables:

  • inspect prints FIELD VALUE rows, with every time in RFC 3339 to the second, in UTC. A sandbox with a network policy then gets an ID RULE IMPLIED section, with each rule as policy create takes it, for example allow suffix:example.com tcp:443.
  • snapshot inspect prints FIELD VALUE rows.
  • policy show prints name, dns and holders, then a RULE section.
  • tokens mint prints TOKEN EXPIRES SCOPES.
Code When
0 the verb did what it says
1 any failure of shard, or of the request, with shard: <reason> on stderr
the command’s code exec, and run --attach with the process’s last code
126, 127 exec of a command that is not executable, or not found
1 or more exec whose output was lost, so a lost stream never reads as a pass
128 + n run --attach of a process that signal n ended
125 run when shard itself fails, so a script tells it from a process that exits 1
130 run that Ctrl+C left before the daemon answered
130 setup that Ctrl+C left; the steps done so far stay in place

Ctrl+C on run --attach detaches: the process runs on, and run exits 0.

daemon status exits 1 when a background task is in backoff, after it prints the whole status.