Core concepts
The pieces that make up a solitary cell.
Cell
A cell is the unit of isolation and lifecycle. It has a definition, a Lima VM, a persistent disk, and a container. Cells are meant to be disposable: destroy one when its state is no longer useful, then create a clean one from the same definition.
VM
The VM is the boundary. It is the part that separates a cell from the host kernel. On macOS and Linux, solitary uses Lima to create and manage it.
Container
The container is the toolset, not the security boundary. Change image: or build: and bring the cell up again to replace the container while retaining the cell's disk.
Host
The host is the operator and the display. It starts machines, forwards selected ports, stores secrets, and performs explicit file transfers. It does not mount your repository into a cell.
Naming
A cell is named by its directory, and its VM is that name with a solitary- prefix. The VM also records the name inside itself: /etc/solitary/cell in the guest says which cell you are in, and the same name is kept in the VM's definition, where limactl list --json reports it back. That is what lets solitary recognise a VM it created after the cell directory is deleted — see machines that outlived their cell.
Persistence
The cell's home directory lives on the VM disk. It survives a stopped machine and a replaced container. rm destroys the VM and its disk; the cell definition and host-held secrets remain available for a fresh up.
Changing a cell
A cell is edited by editing cell.yaml and running up again. What that costs depends on which of the three layers the setting belongs to:
| Layer | Settings | Applied by |
|---|---|---|
| Container | image, build, command, secrets, git | up — the container is replaced |
| Machine | vm, ports, network, gpu | The next boot: down then up |
| Disk | what vm.provision did, and everything the cell has done since | rm then up — the disk goes with it |
up says which one it is: it replaces a container without being asked, warns when a running machine is holding settings that changed, and applies them itself when the machine is stopped.
Removing a setting is applied like adding one. A machine that was restricted or tunnelled has its resolver, firewall and tunnel taken back out at the next boot, so the guest matches the definition in both directions — the machine is not left enforcing an allow list nothing describes any more.
vm.provision is the exception, and the reason the third row exists. The script runs in the machine and changes its disk; the disk survives every change short of rm. A new script runs at the next start, but nothing undoes what the old one did, so a machine built by the current script alone is a machine created again from scratch. Both up and the dashboard say so when the script changes.
Updating the tools in a cell
The container is started over from its image every time the machine boots. The home is the one thing that carries over, because it is a directory on the machine's disk rather than in the container. So a tool that updates itself inside the cell — an agent downloading its next release, a package installed by hand outside the home — has its update until the next down and up, and then downloads it again.
Tools are updated in the image instead, by rebuilding it:
solitary up --rebuild claudeFor a cell with build:, that builds the Containerfile again with no cached layers and the base image pulled again, so whatever it does not pin arrives at its latest; for a cell with image:, it pulls the image again. Either way the container is replaced with one started from the result, even though nothing in cell.yaml changed, and the images it replaced are removed from the machine's disk. In the dashboard it is b.
Replacing the container ends whatever is running in it. Anything in the home — sign-ins, editor plugins, work — is untouched.
The examples turn the self-updaters off in their images for this reason: an update that does not survive a reboot is only a download repeated at every start.
How it works
up renders an embedded Lima template into a machine definition, creates the machine, and starts one rootless podman container inside it. Both are driven by shelling out — limactl on the host, podman through limactl shell in the machine — so there is nothing to install beyond Lima.
The container runs with --network host. The machine is the boundary, so there is no reason to put a second one between the container and the machine it lives in; ports reach the host through Lima's forwarding.
A container records its image, command digest, keyed environment digest, user, devices and shared-memory size. up compares these settings and replaces the container when one changes.
Authority
A cell receives authority in named pieces:
secretscontrols which environment variables enter the container.portscontrols which listening ports are reachable from the host.network.allowcontrols which destinations the cell can reach.imageorbuilddetermines which code is trusted inside the cell.
A cell has no integration that launches a host browser. Open a displayed sign-in URL yourself, or authenticate on the host and pass the credential in under secrets. Unrestricted networking can still reach host services; browser integration and network isolation are separate permissions.
The configuration should make those decisions reviewable before the cell runs. Because values are separated from names, the definition can be committed and shared as a reusable workflow without sharing credentials, and taken with solitary clone.