Persistent State, Volumes, and Resources

A machine combines managed guest state with resources and optional host integrations. Their lifetimes differ.

Machine state

A persistent machine keeps changes to its managed disk across stop and start operations. Package installations, generated files, and other disk writes remain until the machine is deleted or the state is otherwise replaced.

Not every guest path survives a restart. Locally, /tmp is a directory on the machine’s disk that is emptied each time the machine boots, and /run and /dev/shm are backed by memory; all three hold their contents while the machine runs and are empty again after a stop and start. Write files that must outlive a restart to /workspace or another path on the machine filesystem. See Cloud Lifecycle, Storage, and Networking for the full breakdown.

Locally, /dev/shm is half the guest memory unless --shm-size sets it (512m, 2g, 25%). The guest’s inotify limits are raised to 655360 watches and 1280 instances, and vm.max_map_count to 262144.

An ephemeral run is cleaned up when it exits. Use a persistent machine, a pack, a volume, or external storage when data must outlive that run.

Volumes and mounts

A volume maps storage into the guest. It can come from three places.

A host-directory mount shares a selected host path with the machine and therefore crosses the VM isolation boundary. Edits made on the host wake file watchers in the guest, so Vite, webpack and nodemon reload without polling; a :staged mount does not, and SMOL_NO_HOT_RELOAD=1 in the host process turns it off. Locally, a host directory can also be mounted into a running machine until it stops, with machine mount add; see Mount a host directory.

A remote volume mounts S3-compatible object storage instead, using the same flag with an s3:// source. The bucket is mounted inside the guest by the machine’s agent, which speaks the S3 API and FUSE directly, so the image needs nothing installed: no rclone, no fuse3, not even a shell. A bucket can therefore be mounted into a distroless or scratch image. The mount is in place before the workload’s first instruction runs, and it is re-established on every start.

Credentials come from the machine’s own environment: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, with AWS_ENDPOINT_URL for S3-compatible services such as R2 or MinIO and AWS_REGION where it matters. Without them the bucket is read anonymously, which covers public datasets.

A disk volume is a guest path with no host side, such as -v /var/lib/docker. It is a directory on the machine’s own ext4 disk rather than its overlay root, bound into the workload on every start, and it persists across stop and start. Software that nests overlayfs, such as Docker, containerd or BuildKit, needs its data there, because overlayfs cannot use another overlay as its upper layer.

Volumes are runtime attachments. A .smolmachine artifact does not make its external volume bindings portable or persistent. Re-specify the required volumes each time a packed artifact runs.

For Docker inside a machine, give /var/lib/docker and /var/lib/containerd disk volumes; see Docker in a Machine.

Host mount performance

A host-directory mount is served over virtio-fs, which means every file operation in the guest is a round trip to the host. Bulk reads and writes move at close to host speed, because a large transfer amortises that cost over a lot of data. Operation-heavy work does not: a dependency install, a first build, or a walk over a large tree spends its time on metadata calls, and each one pays the round trip. That work is often several times slower on a mount than on the machine’s own disk.

The practical rule is to mount the source and keep the generated directories inside the machine. node_modules, target, .venv, and build output belong on the machine’s storage disk, not on a mounted host path.

Running a mount from a guest-local copy

A :staged mount is the other answer to the round-trip cost. Instead of serving every operation from the host, the guest takes a local copy of the directory, runs against that, and copies it back:

smolvm machine run --net --image node:22-alpine \
  --volume "$PWD:/app:staged" -- sh -c "cd /app && npm install && npm test"

The copy back happens on machine sync and on a graceful stop. That is the trade: metadata-heavy work runs at guest-disk speed, and the host directory is stale until one of those two points, so nothing else may write to the host copy while the machine is running. The copy back mirrors the guest’s copy onto the host: a host file the copy does not have is deleted, including one added on the host after the machine started, and a host edit made in the meantime is overwritten. A branch of the machine keeps its own working copies and never copies them back.

Use :staged for the work the round-trip cost hurts most, a dependency install or a build, and a plain mount when the host side has to stay live.

Mapping mounted files into guest memory

SMOLVM_MOUNT_DAX=1 is an experimental option that gives each host mount a 2 GiB DAX window, which lets the guest map host page-cache pages directly instead of copying them across:

SMOLVM_MOUNT_DAX=1 smolvm machine start --name dev

Set it on the process that starts the machine. A machine that is already running keeps the setting it started with, so it needs a stop and a start to pick this up.

It helps sequential reads and workloads that mmap large files. It does nothing for directory traversal, stat calls, or file creation, which are the operations that make a dependency install slow, so it is not a fix for the case above.

It takes effect on x86_64 guests only

The guest kernel has to be able to map the window. On Apple Silicon and on arm64 Linux it cannot, so the variable is accepted and silently does nothing. The mount still works; it is served without DAX.

The fallback is silent everywhere, so check inside the guest rather than assuming. An active DAX mount ends in dax=always:

smolvm machine exec --name dev -- grep virtiofs /proc/mounts

CPU and memory

CPU and memory are assigned when the machine is configured. The local CLI defaults are 4 vCPUs and 8 GiB of memory when no Smolfile overrides them. Smolfile defaults may differ, so set values explicitly when reproducibility matters.

--cpus and --mem on the command line beat a Smolfile or a packed artifact’s baked values, including when the value given equals the default. Passing --cpus 4 to a machine whose Smolfile asks for 8 gets 4, not 8.

Memory uses virtio ballooning. The configured amount is the guest-visible capacity; the host can reclaim unused guest memory. A high configured limit does not mean the host permanently commits that full amount. Once a machine that is not a branch source has used under about 1% of a core for 10 minutes, the balloon is pulsed once so the guest hands its page cache back to the host. SMOLVM_IDLE_RECLAIM sets the minutes, and 0 or off turns it off.

Available cloud sizes and limits are service-specific. Do not infer hosted limits from local defaults.

Architecture and portability

Machine artifacts are architecture-specific. An arm64 artifact cannot be restored as an x86_64 machine, or the reverse. Host support also depends on the required hypervisor and optional GPU features.