Volume Mounts
Share files between the host and machine using volume mounts.
Local (self-hosted) vs cloud
The host-directory bind mounts on this page — MountSpec{ source, target, readonly },
where source is a path on your own machine — only work when you run smolvm locally / self-hosted. The hosted smolmachines cloud has no host
filesystem to bind into a machine; it uses named persistent volumes instead. Every example above the Cloud volumes section is
local/self-hosted.
Basic Mount
Local / self-hosted. source is a directory on your host; target is the
path inside the machine.
const machine = await Machine.create({
name: 'with-mounts',
mounts: [
{ source: '/host/code', target: '/workspace' }
]
});
// Access mounted files
const result = await machine.exec(['cat', '/workspace/script.py']);
console.log(result.stdout); Python from smol import Machine, MachineConfig, MountSpec
config = MachineConfig(
name="with-mounts",
mounts=[
MountSpec(source="/host/code", target="/workspace")
]
)
async with Machine(config) as machine:
await machine.start()
result = await machine.exec(["cat", "/workspace/script.py"])
print(result.stdout) Read-Only Mounts
Mount directories as read-only for safety:
TypeScriptconst machine = await Machine.create({
name: 'readonly-mount',
mounts: [
{ source: '/host/data', target: '/data', readonly: true }
]
});
// Reading works
const result = await machine.exec(['cat', '/data/config.json']);
// Writing fails
const writeResult = await machine.exec(['touch', '/data/newfile']);
// writeResult.exitCode !== 0 Python config = MachineConfig(
name="readonly-mount",
mounts=[
MountSpec(source="/host/data", target="/data", readonly=True)
]
) Multiple Mounts
TypeScriptconst machine = await Machine.create({
name: 'multi-mount',
mounts: [
{ source: '/host/code', target: '/workspace' },
{ source: '/host/data', target: '/data', readonly: true },
{ source: '/host/output', target: '/output' }
]
}); Python config = MachineConfig(
name="multi-mount",
mounts=[
MountSpec(source="/host/code", target="/workspace"),
MountSpec(source="/host/data", target="/data", readonly=True),
MountSpec(source="/host/output", target="/output"),
]
) Writing Files
Write output from machine to host:
TypeScriptconst machine = await Machine.create({
name: 'writer',
mounts: [
{ source: '/tmp/machine-output', target: '/output' }
]
});
// Run computation and write result
await machine.run(
'python:3.12-alpine',
['python', '-c', `
import json
result = {"answer": 42}
with open("/output/result.json", "w") as f:
json.dump(result, f)
`]
);
// Result is now at /tmp/machine-output/result.json on the host Python config = MachineConfig(
name="writer",
mounts=[
MountSpec(source="/tmp/machine-output", target="/output")
]
)
async with Machine(config) as machine:
await machine.start()
await machine.run(
"python:3.12-alpine",
["python", "-c", """
import json
result = {"answer": 42}
with open("/output/result.json", "w") as f:
json.dump(result, f)
"""]
)
# Result is now at /tmp/machine-output/result.json Cloud volumes
The examples above are local / self-hosted host-directory bind mounts. The
hosted smolmachines cloud has no host filesystem to bind into a machine —
instead you attach named persistent volumes. A cloud mount uses a different
shape, MachineMountSpec{ volume, mountPath, readonly }:
{ "volume": "my-data", "mountPath": "/data", "readonly": false } How cloud volumes differ from local bind mounts:
- Mount by volume name, not host path. You reference a named volume and a
path inside the machine (
mountPath) — there is no hostsourcepath. - Persistent. A volume outlives the machine: data written to it survives the machine being stopped or deleted.
- Single-node affinity. A volume lives on one node. Every volume a machine mounts must be on the same node, and the machine is scheduled onto that node. Mounting volumes that live on two different nodes is rejected with HTTP 422.
- Exclusive read-write. A volume attaches read-write to exactly one machine at a time. Attaching a volume that another machine already holds is rejected with HTTP 409. (Re-attaching to the machine that already owns it is idempotent.) Referencing a volume that doesn’t exist returns HTTP 400.
Mount Path Guidelines
Use top-level paths. Avoid paths that overlap with system directories inside the container image.
Good paths:
/workspace— default persistent workspace; mounting here replaces it with your host directory/code/data/output
Avoid:
/var/data— conflicts with system directories inside the container/home/user/code— may conflict with the container image’s home directory
`/workspace` and host mounts
Every image-based machine exposes /workspace backed by the VM’s storage disk (persists across exec sessions and stop/start). Mounting a host directory at /workspace takes priority — your host directory is used instead of the storage-disk workspace. Any other mount target leaves /workspace intact.
Mount Permissions
The machine runs as root by default. Files created in mounted directories will be owned by root on the host.
To change ownership after execution:
# On the host, after machine execution
sudo chown -R $USER:$USER /tmp/machine-output Or create files with specific permissions in the machine:
await machine.exec([
"sh", "-c",
"echo 'content' > /output/file.txt && chmod 666 /output/file.txt"
])