> This page is for version Main (default).
> For other versions, use one of these documentation indexes:
> - Main (default): https://docs.nvidia.com/nemo/gym/main/llms.txt
> - 0.7.0: https://docs.nvidia.com/nemo/gym/v0.7.0/llms.txt
> - 0.6.0: https://docs.nvidia.com/nemo/gym/v0.6.0/llms.txt
> - 0.5.1: https://docs.nvidia.com/nemo/gym/v0.5.1/llms.txt
> - 0.5.0: https://docs.nvidia.com/nemo/gym/v0.5.0/llms.txt
> - 0.4.0: https://docs.nvidia.com/nemo/gym/v0.4.0/llms.txt
> - 0.3.0: https://docs.nvidia.com/nemo/gym/v0.3.0/llms.txt
> - 0.2.1: https://docs.nvidia.com/nemo/gym/v0.2.1/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/nemo/gym/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/nemo/gym/_mcp/server.

# nemo_gym.sandbox.compose_volumes

Plan the volumes of a Compose document for a sandbox that has no Docker volumes.

Parses each service's volume entries in their short and long forms, checks the target path and
the bind source against the task folder, refuses options the sandbox cannot honour, resolves
links before anything is read, and returns the plan the adapter uploads and mounts: bind mounts
as uploads, named volumes as shared storage, the logs tree kept apart. Every symbol is M22 and
the module reads only the standard library.

## Module Contents

### Classes

| Name                                                                         | Description                                                                                   |
| ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| [`ComposeVolumeError`](#nemo_gym-sandbox-compose_volumes-ComposeVolumeError) | A Compose `volumes` entry prepare cannot run; `name` is the volume, source or field it names. |

### Functions

| Name                                                                   | Description                                                                                       |
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| [`_bind_source`](#nemo_gym-sandbox-compose_volumes-_bind_source)       | A bind source as a host path: relative to `base_dir` (the Compose file's folder), `~` expanded.   |
| [`_checked_target`](#nemo_gym-sandbox-compose_volumes-_checked_target) | A mount target the group can serve: an absolute container path below `/` with no `..` segment.    |
| [`absolute_link`](#nemo_gym-sandbox-compose_volumes-absolute_link)     | The first component of `path` below `root` that is a link with an absolute target, or `None`.     |
| [`first_link`](#nemo_gym-sandbox-compose_volumes-first_link)           | The first symbolic link anywhere under `folder` (files or folders), or `None`.                    |
| [`lexical_target`](#nemo_gym-sandbox-compose_volumes-lexical_target)   | A mount target in its lexical form: trailing and repeated separators and `.` segments dropped, so |
| [`parse_volume`](#nemo_gym-sandbox-compose_volumes-parse_volume)       | One `volumes` entry as a long-syntax mapping: `type`, `source`, `target`, `read_only`             |

### Data

[`LOGS_TREE`](#nemo_gym-sandbox-compose_volumes-LOGS_TREE)

[`_VOLUME_NOOP_OPTIONS`](#nemo_gym-sandbox-compose_volumes-_VOLUME_NOOP_OPTIONS)

[`_VOLUME_OPTIONS`](#nemo_gym-sandbox-compose_volumes-_VOLUME_OPTIONS)

### API

```python
class nemo_gym.sandbox.compose_volumes.ComposeVolumeError(
    name: str,
    message: str
)
```

**Bases:** `ValueError`

A Compose `volumes` entry prepare cannot run; `name` is the volume, source or field it names.

```python
nemo_gym.sandbox.compose_volumes._bind_source(
    source: str,
    base_dir: pathlib.Path,
    where: str
) -> pathlib.Path
```

A bind source as a host path: relative to `base_dir` (the Compose file's folder), `~` expanded.

`~user` for a user the host does not know cannot be resolved; it raises `ComposeVolumeError`
naming the source, like any other bind source the task does not contain.

```python
nemo_gym.sandbox.compose_volumes._checked_target(
    target: typing.Any,
    where: str
) -> str
```

A mount target the group can serve: an absolute container path below `/` with no `..` segment.

The same rule for both syntaxes, and the one the Compose adapter applies to uploads, so a target it
would refuse fails at prepare naming the field (`target`).

```python
nemo_gym.sandbox.compose_volumes.absolute_link(
    path: pathlib.Path,
    root: pathlib.Path
) -> pathlib.Path | None
```

The first component of `path` below `root` that is a link with an absolute target, or `None`.

The server plans a task on a copy of its folder (links copied as links), where an absolute target still
points into the original folder: a bind source reached through one resolves inside the task at prepare and
outside the copy at seed. The planner refuses it, as the hub refuses such a link in a fetched task
(`_validate_task_tree`), so the two agree. A relative link that stays inside the task is fine at both.

```python
nemo_gym.sandbox.compose_volumes.first_link(
    folder: pathlib.Path
) -> pathlib.Path | None
```

The first symbolic link anywhere under `folder` (files or folders), or `None`.

A folder a Compose file binds is uploaded entry by entry (a copy, not a mount); a link inside it would
be read through, reaching whatever it points at on the host, so the planner and the Compose adapter both
refuse one.

```python
nemo_gym.sandbox.compose_volumes.lexical_target(
    target: str
) -> str
```

A mount target in its lexical form: trailing and repeated separators and `.` segments dropped, so
`/app/output/`, `/app/output/.` and `//app/./output` are all `/app/output`.

A target is recorded as written (`/data/.` stays `/data/.` in the planned document) and compared in
this form, by the planner's rule for a `main` logs bind and by the Compose adapter's, which also names the
link in it: a target that nests its source only once spelled out (`/logs/verifier:/logs/.`) is refused at
prepare like `/logs/verifier:/logs`, and never linked over the tree it names (M22 review).

```python
nemo_gym.sandbox.compose_volumes.parse_volume(
    entry: typing.Any,
    service: str
) -> dict[str, typing.Any]
```

One `volumes` entry as a long-syntax mapping: `type`, `source`, `target`, `read_only`
and, for a named volume, `volume.nocopy`.

Short syntax `SOURCE:TARGET[:OPTIONS]` is parsed the way Compose does: a source starting with
`.`, `/` or `~` is a bind mount, anything else a named volume. A bare `TARGET` (an anonymous
volume), a `tmpfs`/`npipe`/`cluster` type, a bind `propagation` or an unknown option raises
`ComposeVolumeError` naming the field: nothing in a sandbox group stands in for it.

```python
nemo_gym.sandbox.compose_volumes.LOGS_TREE = '/logs'
```

```python
nemo_gym.sandbox.compose_volumes._VOLUME_NOOP_OPTIONS = frozenset({'z', 'Z', 'cached', 'delegated', 'consistent'})
```

```python
nemo_gym.sandbox.compose_volumes._VOLUME_OPTIONS = {'ro': {'read_only': True}, 'rw': {'read_only': False}, 'nocopy': {'nocopy': Tru...
```