---
title: "Persistent State, Volumes, and Resources"
description: "Machine disks, host mounts, CPU, memory, and storage."
canonical: "https://smolmachines.com/docs/introduction/concepts/persistent-state-volumes-resources"
source: "https://github.com/smol-machines/docs/blob/main/introduction/concepts/persistent-state-volumes-resources.md"
---

# 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](/docs/cloud/lifecycle-storage-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](/docs/local/machine-lifecycle-cli-reference#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](/docs/guides/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:

```bash
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:

```bash
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.

::: warning 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`:

```bash
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.
