Cubicle

Boxes

What a Box is, a QEMU/KVM virtual machine with its own Linux kernel and disk, with its isolation boundary, Boxes vs containers, the default and omarchy base images, vCPUs, memory and disk and how they are placed and reserved, free Omarchy Boxes and their automatic deletion, names, IDs, Box references and the Box object's key fields.

A Box is a Linux virtual machine that Cubicle runs for you. You create and control it from the TypeScript SDK, the HTTP API, the cubicle CLI or the Dashboard. You run commands in it with exec, and you reach it over SSH and Cubicle's private network.

A Box is a virtual machine

  • Each Box is a QEMU virtual machine accelerated by KVM. It uses QEMU's microvm machine type and the host's CPU model (-cpu host) on x86-64.
  • It boots its own Linux kernel, which Cubicle supplies from the host. There is no bootloader, so installing a kernel inside the Box does not change the kernel it boots.
  • It runs a full Linux system with systemd, an SSH server and user accounts.
  • It has one root disk (ext4), one network interface with a private IPv4 address, and a virtual display, keyboard and tablet for the desktop console.
  • It lives on one Node, a Cubicle host, for its whole life. It never moves to another Node. The Box's node field names that Node.

The virtual machine is the isolation boundary

  • Code in a Box, including code running as root, runs on the Box's own kernel. It does not share a kernel with the host or with other Boxes.
  • On the host, each Box's QEMU process runs under its own unprivileged user, inside a seccomp sandbox, with CPU and memory limits taken from the Box's size.
  • Boxes cannot reach each other over the network, even in the same organization. A Box reaches the public IPv4 internet, and connections into it come only over Cubicle's private network; see Outbound traffic from a Box for the rules.
  • Inside a Box, users are not isolated from each other. Cubicle gives its guest accounts and the desktop account passwordless sudo, so anyone who signs in over SSH or opens the desktop console can become root in the Box and read the other users' files. Give separate users or tenants separate Boxes.
  • The guest account of someone without an SSH key, such as a service account, is locked and expired in the Box. Exec still runs commands as it, but do not rely on sudo from it.

Boxes vs containers

BoxContainer
KernelIts own Linux kernel, managed by CubicleThe host's kernel, shared with other containers
Isolation boundaryHardware virtualization (KVM)Kernel namespaces and cgroups on the shared kernel
What runs insideA full Linux system: systemd, sshd, user accountsUsually one application process tree
StartingBoots the guest operating system, or resumes it from hibernationStarts a process on a kernel that is already running
ImagesOne of Cubicle's base images, or a snapshot of one of your BoxesAny OCI image
Saved stateThe disk persists across stop and start; hibernation also saves memoryDepends on the runtime and its volumes
How you get inSSH, exec through the API, the desktop console, the private IPv4 networkThe runtime's exec, and ports the host publishes

Base images

Every Box starts from a base image, or from a snapshot of another Box. There are two base images. baseImage defaults to default.

IDWhat it is
defaultA Debian-based Linux system with OpenSSH, sudo and Python 3. Its console desktop is Xfce, signed in automatically as sandbox.
omarchyArch Linux with Omarchy. Its console desktop is Hyprland with software rendering, signed in automatically as cubicle.
  • Each image has its own default size; Limits and defaults lists them.
  • GET /v1/orgs/{org}/bases (reference), cubicle.listBases() and cubicle boxes bases --org acme list the base images.
  • A base image is available when at least one healthy Node offers it. available does not consider free capacity.
  • The desktop accounts (sandbox, cubicle) are separate from the accounts you use for SSH and exec. See Use the desktop console.
  • Run cat /etc/os-release in a Box to see the exact distribution release.
  • There are no custom base images; create Boxes from a template Box's snapshot. See Build a template Box.

When Cubicle creates a Box's disk, it writes these into it, whatever the image, without running any program from the image:

  • an SSH server policy: key login only, no passwords, no root login
  • an SSH host key generated for this Box
  • a helper that runs exec commands and manages accounts
  • a sudo rule that gives Cubicle's accounts passwordless sudo
  • /etc/hostname set to box-<name>, and a DNS resolver in /etc/resolv.conf

Cubicle then creates one account in the Box for each person or service account with a grant on it. Guest accounts describes that account.

Resources

A Box has a fixed number of vCPUs (vcpus), an amount of memory (memoryMiB) and a disk size (diskGiB), each a whole number. Limits and defaults gives the allowed ranges and each base image's default size.

  • Sizes you leave out come from the base image. For a Box created from a snapshot, they come from the source Box.
  • Cubicle places a new Box on a healthy Node that offers its base image and has the free vCPUs, memory and disk. A Box created from a snapshot goes on the Node that holds the snapshot. When no Node fits, create fails with 503 capacity.
  • The Box's vCPUs, memory and disk stay reserved on its Node for its whole life, including while it is stopped or hibernated, so a lack of capacity never stops it from starting again. Free Omarchy Boxes are the exception. The reservation is released when deletion finishes.
  • A Box cannot be resized yet. To change its size, snapshot it and create a new Box from the snapshot with the size you want; the new disk can be larger but not smaller.

Disk size and snapshots

  • The disk keeps its contents across stop, start, hibernation and resume.
  • When Cubicle creates the disk, it grows the disk and its filesystem to diskGiB.
  • A diskGiB smaller than the base image itself fails at provisioning, not at request time: the Box goes to failed with disk is smaller than its source image.
  • A Box created from a snapshot needs a disk at least as large as the source Box's. See Size rules for a Box created from a snapshot.
  • Deleting a Box deletes its disk and every snapshot of it.

Free Omarchy Boxes

A free Omarchy Box comes from the free Omarchy sign-up in the Dashboard, not from a create request. Its pooled field is true, and Cubicle deletes it if it stays stopped or hibernated.

  • Getting one: enter your email address at https://app.cubicle.sh/omarchy. Cubicle emails you a one-time sign-in link. Opening the link creates the Box, or starts it when it is not running, and shows its desktop console in your browser.
  • No invitation needed: for a new email address, the sign-up creates a Cubicle account. While the sign-up is closed, it says that free Omarchy Boxes are not available. A free Omarchy Box you already have keeps working.
  • One per account: it is named omarchy, lives in your personal organization and uses the omarchy base image at its default size. After it is deleted, the sign-up gives you a new one while it is open.
  • Not from a create request: POST /v1/orgs/{org}/boxes, the SDK, the CLI and the Dashboard's create form always create ordinary Boxes.
  • Pooled vCPUs and memory: once its Node reports it stopped or hibernated, Cubicle gives its vCPUs and memory back to the Node. Its disk stays reserved. A start reserves them again on the same Node, and fails with 503 capacity (free boxes are at capacity right now; try again shortly) when they do not fit.
  • Automatic deletion: when Cubicle gives back the vCPUs and memory, it sets expiresAt. If the Box is still stopped or hibernated at that time, Cubicle deletes it with its disk and snapshots. A start before then clears expiresAt.

Because every Box hibernates automatically, a free Omarchy Box you leave alone is deleted at expiresAt unless you start it again. Keep anything you need from it somewhere else.

Names and IDs

IdentifierFormRules
IDA UUID, such as 0b3f6c2e-8d4a-4f6e-9c1d-7a2b5e8f9d10Assigned at creation. It never changes.
Name1 to 63 lowercase letters, digits and -, starting with a letter or digit (^[a-z0-9][a-z0-9-]{0,62}$)Unique among the organization's Boxes that are not deleted. It cannot be changed.
Hostnamebox-<name>, such as box-job-1Written to /etc/hostname when the disk is created.
Private DNS name<name>.<org>.cubicle.internal, such as job-1.acme.cubicle.internalResolves only over Cubicle's private network. See Private DNS names.
  • When you leave out the name, the API and the SDK generate one: box- followed by six hex digits, such as box-3fa9c1. The CLI requires a name.
  • Creating a second Box with a name that is in use returns 409 conflict, with the message a box named job-1 already exists.
  • A deleted Box's name is free as soon as the deletion is accepted.

Box references

Every route, method and command that acts on one Box takes a Box reference.

ReferenceExampleHTTP API ({box})SDKCLI
ID0b3f6c2e-8d4a-4f6e-9c1d-7a2b5e8f9d10YesYesYes
ORG/NAMEacme/job-1Yes, with the slash encoded: acme%2Fjob-1YesYes
NAMEjob-1YesYesNo
  • ORG is the organization's name or ID.
  • A bare NAME is looked up in every organization where you are an active member. Narrow it with ?org=acme in the API, the SDK client's org option, or an API key, which only sees its own organization.
  • A bare NAME that matches Boxes in several organizations returns 409 ambiguous_box. A reference that matches nothing returns 404 not_found.
  • In the API and the SDK, names never resolve to deleted Boxes or to Boxes in organizations you do not belong to.
  • Use the ID in code that retries or runs for a long time. Once a Box is deleted, its name can belong to a new Box.

The Box object

GET /v1/boxes/{box} and the SDK's cubicle.boxes.get() return the Box object. These are the fields that the concept pages explain. The API reference and the SDK's BoxInfo type describe every field, including the Box's identity, size, Node, private IP address and timestamps.

FieldMeaning
stateWhat the Box is doing, from its Node's latest report. See Box lifecycle.
desiredWhat was last requested: { state, generation }.
observedThe Node's latest report, { state, generation, observedAt, ageSeconds, fresh, error }, or null before the first one.
ssh, console{ ready, reason }: whether exec and SSH, or the desktop console, can be used now. See When a Box is ready.
restartRequiredtrue when the Node's host restarted while the Box should have been running, until you start or stop it.
connectionGenerationA counter that advances with lifecycle changes, restores and host restarts. SSH and private-network access from an earlier value no longer applies. See Generations.
pooledtrue for a free Omarchy Box, otherwise false.
expiresAtWhen Cubicle deletes a stopped or hibernated free Omarchy Box unless it is started first, otherwise null.
deletionpending from an accepted delete until its Node has removed the Box and listings stop showing it, otherwise null. See Deleting a Box. A create repeated with the same Idempotency-Key can return complete for a Box removed since; see Idempotency keys.

When not to use a Box

  • You need a public URL or inbound port from the internet. Public URLs, port forwarding and proxies are not available yet.
  • Your code must connect to a service in the Box. API keys cannot use the private network, so a program that holds only an API key cannot open a connection to a port in a Box. Run the client inside the Box with exec instead, and connect to localhost. See Networking.
  • You need an always-on service. Every Box hibernates automatically, whatever it is doing.
  • You need a GPU. A Box has a virtual display only; its desktops render in software.
  • You need Windows, macOS, another CPU architecture or your own kernel. Boxes run x86-64 Linux on a kernel that Cubicle manages.
  • You need IPv6. It is disabled in every Box.
  • You need to isolate several users or tenants inside one machine. Anyone who can sign in to a Box over SSH or open its desktop console can become root in it.
  • The Box would hold the only copy of important data. A Box's disk and its snapshots live on one Node and are deleted with the Box. Keep important data somewhere else too.

On this page