Cubicle

Box commands

Reference for the cubicle Box commands boxes list, bases, create, get, start, stop and delete, exec, snapshot, snapshots and restore, with options, --wait behavior and the 10-minute deadline, output formats, exit codes, errors and what the CLI cannot do yet.

These commands create and manage Boxes, run commands in them and take snapshots. BOX is a Box ID or ORG/NAME, such as acme/job-1; see Box arguments. They work with your login and with an API key (API-key mode). Box lifecycle explains the states and generations that their output shows.

  • boxes start, boxes stop, boxes delete, snapshot and restore need a grant on the Box, or the owner or admin role in its organization. boxes create --snapshot needs the same on the snapshot's Box. exec needs a grant even for owners and admins. See Roles.
  • While a snapshot or restore of the Box runs, boxes start, boxes stop and boxes delete fail with conflict: a snapshot or restore is in progress for this box.

Waiting

CommandWaitsUntilTime limit
boxes create --waitOn requestssh.ready is true: the Box runs and accepts SSH and exec10 min
boxes create --no-start --waitOn requestThe Box's state is stopped10 min
boxes start --waitOn requestssh.ready is true10 min
boxes stop --waitOn requestThe state is stopped at the generation of this stop10 min
boxes delete --waitOn requestThe Box no longer resolves, which happens right away10 min
exec, snapshot, restoreAlwaysThe operation ends done, failed or unknownNone
  • --wait reads the Box every 1 s. If the Box turns failed, the command stops with box failed: <error>. After 10 minutes it stops with timed out waiting for the box. Both exit 1, and the request stays in effect.
  • --wait stops at the first failed report it reads, even one left over from an earlier failed start. After a failure, start the Box without --wait and check cubicle boxes list instead; see box failed.
  • exec, snapshot and restore read the operation every 500 ms, with no time limit in the CLI. Interrupting the CLI, for example with Ctrl-C, stops the wait, not the operation.
  • ssh.ready describes the Box. It does not check your own grant or SSH key.

cubicle boxes list

Lists Boxes, in all your organizations or in one.

cubicle boxes list [--org ORG] [--json]
FlagValueDefaultMeaning
--orgOrganization name or IDAll your organizationsList only this organization's Boxes.
  • Without --org, it lists the Boxes of every organization where you are an active member, by organization name and then creation time. With an API key, it lists the key's organization.
  • With --org, it lists that organization's Boxes by creation time.
  • A deleted Box stays listed, with desired=deleted, until its Node has removed it.
  • --json prints an array of Box objects. Without --org, each also has viewer (your role and whether you hold a grant), grants and openVpnSessions, the number of open private network sessions that route the Box.

Output is one line per Box, or no boxes:

<box ID>  <org>/<name>  <state>  desired=<desired state>@<generation>  node=<node>  ssh=<ready or reason>
FieldMeaning
stateThe Box's current state, such as running, stopped, hibernated or unknown.
desiredThe state last requested, and the generation of that request.
nodeThe Node that runs the Box. (unhealthy) follows the name when the Node has stopped reporting.
sshready, or why SSH and exec cannot be used yet, such as not_requested or generation_pending.

What each state means lists every state, and When a Box is ready every readiness reason. Calls GET /v1/boxes or, with --org, GET /v1/orgs/{org}/boxes.

cubicle boxes list --org acme
0b3f6c2e-8d4a-4f6e-9c1d-7a2b5e8f9d10  acme/job-1  running  desired=running@3  node=worker-1  ssh=ready
a5c1e9d3-7b2f-4a6e-8d0c-3f9b1e7a5c2d  acme/job-2  stopped  desired=stopped@2  node=worker-1  ssh=not_requested

cubicle boxes bases

Lists the base images an organization can create Boxes from.

cubicle boxes bases --org ORG [--json]
FlagValueDefaultMeaning
--orgOrganization name or IDRequiredThe organization.
  • Output: one line per base, <base ID> <name> available|unavailable.
  • available means at least one healthy Node that takes new Boxes has the image. It does not check free capacity.
  • --json also prints each base's default size as spec.

Calls GET /v1/orgs/{org}/bases.

cubicle boxes bases --org acme
default  Default  available
omarchy  Omarchy  available

cubicle boxes create

Creates a Box from a base image or from a snapshot.

cubicle boxes create --org ORG NAME [--vcpus N] [--memory MiB] [--disk GiB]
                     [--base default|omarchy | --snapshot SNAPSHOT_ID]
                     [--no-start] [--wait] [--json]
FlagValueDefaultMeaning
--orgOrganization name or IDRequiredThe organization that owns the Box.
--vcpus1 to 64The base's size, or the source Box's with --snapshotVirtual CPUs.
--memory256 to 262144 MiBThe base's size, or the source Box's with --snapshotMemory in MiB.
--disk1 to 2048 GiBThe base's size, or the source Box's with --snapshotDisk in GiB. With --snapshot, at least the source Box's disk.
--basedefault or omarchydefaultThe base image. Not allowed with --snapshot.
--snapshotSnapshot IDNoneStart from this snapshot's disk instead of a base image.
--no-startnoneOffCreate the Box stopped.
--waitnoneOffWait until the Box is ready, or stopped with --no-start.

The default base is 2 vCPUs, 2048 MiB and 12 GiB. The omarchy base is 4 vCPUs, 8192 MiB and 40 GiB. Limits and defaults lists every limit.

  • NAME is required in the CLI. It matches ^[a-z0-9][a-z0-9-]{0,62}$ and must be unique among the organization's Boxes.
  • Any active member can create a Box when the organization allows it. You get a grant on the new Box.
  • With --snapshot, the snapshot must belong to ORG and be complete. The new Box runs on the snapshot's Node and keeps the snapshot's base image. cubicle snapshots SOURCE_BOX prints snapshot IDs.
  • Each run sends its own Idempotency-Key, so running the command again makes a new request. With the same name, it fails because the name is taken.
  • Output: the Box's line, as in boxes list, when the request is accepted or, with --wait, when the wait ends. --json prints the Box object.
ErrorCause
choose --base or --snapshot, not bothBoth options were given.
usage: cubicle boxes create --org ORG NAME--org or NAME is missing.
box_create_disabled: this organization cannot create boxes; ask an operator to enable itThe organization may not create Boxes yet, as with a new personal organization.
conflict: a box named job-1 already existsThe name is in use.
capacity: no healthy node with the default base image has capacity for this boxNo Node has room. Try again later or ask for a smaller Box.
invalid_request: disk must be at least 12 GiB (the snapshot's size)--disk is smaller than the source Box's disk.
conflict: snapshot is not completeThe snapshot is still pending, or failed.

Calls POST /v1/orgs/{org}/boxes.

cubicle boxes create --org acme job-1 --wait
0b3f6c2e-8d4a-4f6e-9c1d-7a2b5e8f9d10  acme/job-1  running  desired=running@1  node=worker-1  ssh=ready

cubicle boxes get

Prints one Box as JSON.

cubicle boxes get BOX

No options. The command always prints JSON, with or without --json. The object has the Box's state, desired and observed states, spec, node, ssh and console readiness, privateIp, hibernateAt, restartRequired, deletion and more. A deleted Box fails with not_found: box not found.

Calls GET /v1/boxes/{box}, which documents every field.

cubicle boxes get acme/job-1 | jq '{state, desired, ssh, hibernateAt}'
{
  "state": "running",
  "desired": { "state": "running", "generation": 3 },
  "ssh": { "ready": true, "reason": null },
  "hibernateAt": "2026-11-05T10:42:17.311Z"
}

cubicle boxes start

Starts a stopped Box or resumes a hibernated one.

cubicle boxes start BOX [--wait] [--json]
FlagValueDefaultMeaning
--waitnoneOffWait until ssh.ready is true.
  • A hibernated Box resumes from its saved memory. A stopped Box boots from its disk.
  • It also clears the hold a Box gets when its host restarts (restartRequired).
  • On a running Box it does not reboot anything, but it advances the generation; see Generations.
  • Output: the Box's line. --json prints the Box object.

Calls POST /v1/boxes/{box}/start.

cubicle boxes start acme/job-1 --wait
0b3f6c2e-8d4a-4f6e-9c1d-7a2b5e8f9d10  acme/job-1  running  desired=running@3  node=worker-1  ssh=ready

cubicle boxes stop

Stops a Box. Its disk is kept; its memory is not.

cubicle boxes stop BOX [--wait] [--json]
FlagValueDefaultMeaning
--waitnoneOffWait until the Box is stopped at this stop's generation.
  • On a hibernated Box, stop discards the saved memory. The next start boots from the disk.
  • The CLI cannot hibernate a Box. See Not in the CLI.
  • Output: the Box's line. --json prints the Box object.

Calls POST /v1/boxes/{box}/stop.

cubicle boxes stop acme/job-1 --wait
0b3f6c2e-8d4a-4f6e-9c1d-7a2b5e8f9d10  acme/job-1  stopped  desired=stopped@4  node=worker-1  ssh=not_requested

cubicle boxes delete

Deletes a Box permanently, with its disk and every snapshot of it.

cubicle boxes delete BOX [--wait] [--json]
FlagValueDefaultMeaning
--waitnoneOffWait until the Box no longer resolves. This does not wait for cleanup.
  • It also fails while another Box is still being created from one of its snapshots: conflict: box job-2 is still being created from a snapshot of this box.
  • The name is free for a new Box at once.
  • Output: <box ID> deletion pending. With --wait: <box ID> deletion deleted.
  • --json prints the Box object with "deletion": "pending". With --wait, it prints { "id": "<box ID>", "state": "deleted" }.
  • --wait returns at once, because a deleted Box stops resolving right away. Its Node releases the capacity when it has removed the Box: to wait for that, check cubicle boxes list --org ORG until the Box is gone.

Calls DELETE /v1/boxes/{box}. See Deleting a Box.

cubicle boxes delete acme/job-1
0b3f6c2e-8d4a-4f6e-9c1d-7a2b5e8f9d10 deletion pending

cubicle exec

Runs one command in a running Box, waits for it and prints its output.

cubicle exec BOX [--timeout MS] [--json] -- COMMAND [ARGS...]
FlagValueDefaultMeaning
--timeout100 or more, in ms60000Kill the command after this many ms. Values above 600000 (10 min) become 600000.
  • Everything after -- is the command, run as an argv without a shell. Use sh -c '…' for pipes, redirects, && and variables. Put CLI options before --. Without -- and a command, the CLI prints usage: cubicle exec BOX -- COMMAND [ARGS...].
  • The command runs as your guest account, in its home directory. It has no stdin and no TTY.
  • Without a grant on the Box it fails with forbidden: an explicit box grant is required.
  • The Box must be running: otherwise conflict: box is not running. Run cubicle boxes start BOX --wait first.
  • The CLI prints the command's stdout on stdout and its stderr on stderr, once the command ends. Output is not streamed.
  • stdout and stderr together are cut at 64 KiB. The CLI then also prints cubicle: output truncated on stderr.
  • The CLI exits with the command's exit code. A command killed by --timeout exits 124, and the CLI prints the output the command wrote before it was killed. --json shows "timedOut": true.
  • An operation that ends failed or unknown exits 1. Without --json, the CLI prints exec failed: <error> or exec unknown: <error>. An unknown command may or may not have run; see Operations record every request.
  • --json prints the operation, with the output in result. The exit code is still the command's.
  • The CLI sends no Idempotency-Key with exec and does not retry it, so running the same cubicle exec again runs the command again.

Calls POST /v1/boxes/{box}/exec, then GET /v1/operations/{operation}. Run commands covers shell syntax, long jobs and timeouts.

cubicle exec acme/job-1 -- sh -c 'echo hello; echo oops >&2; exit 3'
echo "exit code: $?"
hello
oops
exit code: 3

With --json:

cubicle exec acme/job-1 --json -- echo hello
{
  "id": "c4e8a2f6-1b3d-4f5a-9c7e-2d4f6a8b0c1e",
  "boxId": "0b3f6c2e-8d4a-4f6e-9c1d-7a2b5e8f9d10",
  "type": "exec",
  "state": "done",
  "attempt": 1,
  "progress": null,
  "result": {
    "exitCode": 0,
    "stdout": "hello\n",
    "stderr": "",
    "timedOut": false,
    "truncated": false
  },
  "error": null,
  "createdAt": "2026-11-05T09:20:01.114Z",
  "startedAt": "2026-11-05T09:20:01.402Z",
  "completedAt": "2026-11-05T09:20:01.587Z"
}

cubicle snapshot

Takes a disk snapshot of a stopped Box and waits until it ends.

cubicle snapshot BOX NAME [--json]

No options besides --json.

  • The Box must be stopped: otherwise conflict: stop the box before taking a snapshot. Run cubicle boxes stop BOX --wait first. Stopping a hibernated Box discards its saved memory.
  • NAME matches ^[a-z0-9][a-z0-9._-]{0,62}$. Names need not be unique.
  • Snapshots explains what a snapshot holds and where it is kept.
  • Output: snapshot <NAME>: done, or snapshot <NAME>: failed (<error>). The command exits 0 only when the snapshot is done.
  • --json prints the operation. cubicle snapshots BOX prints the new snapshot's ID.

Calls POST /v1/boxes/{box}/snapshots.

cubicle boxes stop acme/job-1 --wait
cubicle snapshot acme/job-1 before-upgrade
0b3f6c2e-8d4a-4f6e-9c1d-7a2b5e8f9d10  acme/job-1  stopped  desired=stopped@4  node=worker-1  ssh=not_requested
snapshot before-upgrade: done

cubicle snapshots

Lists a Box's snapshots, newest first.

cubicle snapshots BOX [--json]

No options besides --json. Any active member of the Box's organization can list them.

  • Output: one line per snapshot, <snapshot ID> <name> <status> <created at>.
  • <status> is pending, complete or failed.
  • --json prints an array with id, name, status, operationId, createdAt and completedAt.

Calls GET /v1/boxes/{box}/snapshots.

cubicle snapshots acme/job-1
3f2a9c1e-7b4d-4e8f-a6c2-1d9e0b8f7a3c  before-upgrade  complete  2026-11-05T10:04:12.000Z

cubicle restore

Replaces a stopped Box's disk with one of its own snapshots, and waits until that ends.

cubicle restore BOX SNAPSHOT_ID [--json]

No options besides --json.

  • The Box must be stopped: otherwise conflict: stop the box before restoring a snapshot.
  • SNAPSHOT_ID is an ID from cubicle snapshots BOX, not a name. A snapshot of another Box fails with not_found: snapshot not found; one that is not complete fails with conflict: snapshot is not complete.
  • Every file on the Box's disk is replaced. The Box stays stopped; start it with cubicle boxes start BOX --wait.
  • Output: restore: done, or restore: failed (<error>). The command exits 0 only when the restore is done. --json prints the operation.

To copy a snapshot into a new Box instead, use cubicle boxes create --snapshot SNAPSHOT_ID. Calls POST /v1/boxes/{box}/restore.

cubicle restore acme/job-1 3f2a9c1e-7b4d-4e8f-a6c2-1d9e0b8f7a3c
cubicle boxes start acme/job-1 --wait
restore: done
0b3f6c2e-8d4a-4f6e-9c1d-7a2b5e8f9d10  acme/job-1  running  desired=running@5  node=worker-1  ssh=ready

Not in the CLI

These tasks need the TypeScript SDK or the HTTP API.

TaskTypeScript SDKHTTP API
Hibernate a Boxbox.hibernate()POST /v1/boxes/{box}/hibernate
List a Box's operationsbox.listOperations()GET /v1/boxes/{box}/operations
Read one operationcubicle.operations.get(id)GET /v1/operations/{operation}
Run a command in another directorybox.exec(argv, { cwd })POST /v1/boxes/{box}/exec with cwd
Keep more than 64 KiB of output, up to 1 MiBbox.exec(argv, { maxOutputBytes })POST /v1/boxes/{box}/exec with maxOutputBytes
List an organization's snapshots that you can create Boxes fromcubicle.listSnapshots()GET /v1/orgs/{org}/snapshots
Refer to a Box by its bare namecubicle.boxes.get("job-1")GET /v1/boxes/job-1, with ?org= when the name is in several organizations
  • The desktop console is in the Dashboard only. See Use the desktop console.
  • Streaming exec output, stdin or a TTY, file transfer, port forwarding, public URLs, fork, resize, rename and reboot are in no surface yet; see Not available yet. To copy files, use scp or rsync over SSH; see Copy files over SSH.

Boxes and handles shows how to get a box handle in the SDK.

On this page