> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tensorlake.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Read-only Mounts

> Give sandboxes read-only views of shared file systems, and pin mounts to permanent snapshots for reproducible inputs.

Use read-only mounts when a sandbox needs files but should not write to them.

A file system is the shared writable surface, so `tl fs mount` is always writable. Read-only is expressed where the consumer attaches:

* **Sandbox mounts** take a per-mount `read_only` option, plus an optional `snapshot_id` that pins the mount to a permanent snapshot.
* **Git repositories** have `tl git mount --ro` for stateless read-only views of a branch, pinned commit, or subtree.

There are two read-only shapes:

* **Following**: tracks the file system's current state and refreshes as replicated autosaves or permanent snapshots land.
* **Pinned**: serves one permanent snapshot and never changes.

## Following Read-only Mount

A following mount is best for shared skills, prompts, docs, configs, and dependencies. Pass `read_only` on the sandbox mount and the guest directory is mounted read-only, enforced down to the storage credential — writes inside the guest fail with `EROFS`.

<Tabs>
  <Tab title="CLI">
    ```bash theme={null}
    tl sbx create -f agent-assets:/skills:ro
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from tensorlake.sandbox import FileSystemMount, Sandbox

    sandbox = Sandbox.create(
        file_systems=[
            FileSystemMount(
                file_system_id="agent-assets",
                mount_path="/skills",
                read_only=True,
            ),
        ],
    )
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    import { Sandbox } from "tensorlake";

    const sandbox = await Sandbox.create({
      fileSystems: [
        { fileSystemId: "agent-assets", mountPath: "/skills", readOnly: true },
      ],
    });
    ```
  </Tab>

  <Tab title="HTTP">
    ```bash theme={null}
    curl -X POST https://api.tensorlake.ai/sandboxes \
      -H "Authorization: Bearer $TL_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "file_systems": [
          {"file_system_id": "agent-assets", "mount_path": "/skills", "read_only": true}
        ]
      }'
    ```
  </Tab>
</Tabs>

When the shared timeline advances, Tensorlake refreshes only the paths that changed. Unchanged files keep their warm cache.

Inside the sandbox, the mount is reachable by every user — including a custom image's non-root default user — with ordinary file permission bits deciding access from there. Mounted files present one owner (the image's `tl-user` account when present, otherwise root), and the mount's optional `owner` option changes whose files they appear to be — see [Ownership](/sandboxes/mount-filesystems#ownership).

See [Mount Filesystems](/sandboxes/mount-filesystems#read-only) for the full sandbox mount guide, including warm-pool claims and runtime attach/detach.

## Pinned Read-only Mount

A pinned mount is best for reproducible builds, evals, benchmarks, and released assets: an entire fleet mounts one immutable state, and later pushes never change what any sandbox sees.

Pass the mount a `snapshot_id` naming a permanent snapshot. Create the snapshot with `tl fs snapshot` (or a message-bearing `tl fs push -m`) and find its id under `Snapshots` in `tl fs history`:

```bash theme={null}
tl fs snapshot /mnt/skills -m "skills release 2026-08-20"
tl fs history skills
```

<Tabs>
  <Tab title="CLI">
    ```bash theme={null}
    tl sbx create -f 'skills@4b8e2d6f0a3c7e1b5d9f2a6c8e0b4d7f1a3c5e9b2d6f8a0c4e7b1d3f5a9c2e60:/skills:ro'
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from tensorlake.sandbox import FileSystemMount, Sandbox

    sandbox = Sandbox.create(
        file_systems=[
            FileSystemMount(
                file_system_id="skills",
                mount_path="/skills",
                read_only=True,
                snapshot_id="4b8e2d6f0a3c7e1b5d9f2a6c8e0b4d7f1a3c5e9b2d6f8a0c4e7b1d3f5a9c2e60",
            ),
        ],
    )
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    import { Sandbox } from "tensorlake";

    const sandbox = await Sandbox.create({
      fileSystems: [
        {
          fileSystemId: "skills",
          mountPath: "/skills",
          readOnly: true,
          snapshotId: "4b8e2d6f0a3c7e1b5d9f2a6c8e0b4d7f1a3c5e9b2d6f8a0c4e7b1d3f5a9c2e60",
        },
      ],
    });
    ```
  </Tab>

  <Tab title="HTTP">
    ```bash theme={null}
    curl -X POST https://api.tensorlake.ai/sandboxes \
      -H "Authorization: Bearer $TL_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "file_systems": [
          {
            "file_system_id": "skills",
            "mount_path": "/skills",
            "read_only": true,
            "snapshot_id": "4b8e2d6f0a3c7e1b5d9f2a6c8e0b4d7f1a3c5e9b2d6f8a0c4e7b1d3f5a9c2e60"
          }
        ]
      }'
    ```
  </Tab>
</Tabs>

A pinned mount requires `read_only` (the server rejects a writable pin with `400`) and never follows the file system: `skills` can keep advancing for following mounts while every pinned sandbox keeps serving the release. Pins work at creation, on warm-pool claims, and on runtime attach — see [Pinned mounts](/sandboxes/mount-filesystems#pinned-mounts) for the full sandbox-side guide, including the `FileSystemSnapshotNotFound` error for ids that are not permanent snapshots.

A pinned release cannot be deleted out from under its consumers: `tl fs delete-snapshot` refuses with a `409` naming the pin count while any live mount pins the snapshot.

### Forking for promote-later releases

If a release needs its own file-system name — one you might promote, point tooling at, or eventually write to — fork the file system at the permanent snapshot instead. A fork is metadata-only (the server shares the immutable content) and gives the release a stable name that no writer advances:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from tensorlake.filesystem import FilesystemClient

    client = FilesystemClient()
    client.fork(
        "agent-assets-v1",
        "agent-assets",
        "9f2a1c8e4d6b1a0f3c7e9d2b8a4f6c1e0d3b7a99fedcba987654321001234567",
    )
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    import { FilesystemClient } from "tensorlake";

    const client = new FilesystemClient();
    await client.fork(
      "agent-assets-v1",
      "agent-assets",
      "9f2a1c8e4d6b1a0f3c7e9d2b8a4f6c1e0d3b7a99fedcba987654321001234567",
    );
    ```
  </Tab>
</Tabs>

Then mount the fork read-only:

```bash theme={null}
tl sbx create -f agent-assets-v1:/release:ro
```

The fork stays fixed as long as nothing writes to it. Note that a fork of a fixed snapshot mounted `read_only` behaves like a pin, but the fork is a new writable file system: anything that writes to it advances every following mount of the fork. When you only need "every run sees this exact state", pin the snapshot directly.

For assets kept in a Git repository, `tl git mount` pins directly — no snapshot or fork needed:

```bash theme={null}
tl git mount agent-outputs:9f2a1c8e4d6b1a0f3c7e9d2b8a4f6c1e0d3b7a99 /release --ro
```

For a complete asset distribution workflow, see [Distribute Files to Agents](/filesystems/distribute-files).

## Choosing Between Them

| Need                                                         | Use                                                                            |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------ |
| Every run must see the same files                            | Pin a permanent snapshot: `read_only` + `snapshot_id` (`skills@<snapshot-id>`) |
| A release that needs its own name to promote or edit later   | Fork the snapshot, mount the fork `read_only`                                  |
| Many sandboxes should receive updates without image rebuilds | Following `read_only` sandbox mount                                            |
| A fixed commit, branch, or subtree of a Git repository       | `tl git mount --ro`                                                            |
| An agent needs to write files                                | [Writable mount](/filesystems/filesystem-mounts#writable-mounts)               |
