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);
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:

const 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
config = MachineConfig(
    name="readonly-mount",
    mounts=[
        MountSpec(source="/host/data", target="/data", readonly=True)
    ]
)

Multiple Mounts

const 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' }
  ]
});
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:

const 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
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 host source path.
  • 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"
])