> This page is for version 0.6.0.
> 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.tasks.harbor.image_configs

Record the OCI configuration of the images a task runs.

Docker reads an image's environment, user, working directory, entrypoint, command and
exposed ports from the image itself. A sandbox platform without Docker cannot, so Gym
records them from the registry into `compose-images.json` next to the task folders,
one entry per image reference exactly as the task wrote it, pinned to the linux/amd64
manifest digest it resolved to. Two readers depend on the file:

* the harbor resources server, for the sidecar images a Compose task starts (see
  `nemo_gym.sandbox.compose_config.resolve_compose`);
* the task loader, for the base image of a pull-mode or overlay-mode Dockerfile, whose
  `ENV`, `WORKDIR` and `USER` lines resolve from the base image's own configuration
  the way `docker build` resolves them (see `nemo_gym.tasks.harbor.dockerfile`).

The `image` an entry pins is the digest of the manifest the written reference named: the
multi-arch index behind a tag, from which a sandbox of any platform selects its own manifest,
or the single manifest of a single-platform reference. The loader starts a pull-mode or
overlay-mode task from that pinned reference rather than the tag the Dockerfile wrote, so a
moved tag cannot change the image under a recorded configuration; the entry stays keyed by
the written reference, which is how the task loader and the Compose resolver look it up.

## Module Contents

### Functions

| Name                                                                                              | Description                                                                                            |
| ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| [`compose_file`](#nemo_gym-tasks-harbor-image_configs-compose_file)                               | The task's Compose overlay, if it has one.                                                             |
| [`compose_image_references`](#nemo_gym-tasks-harbor-image_configs-compose_image_references)       | Every image a Compose task starts: the task image for `main` and each sidecar's image.                 |
| [`dockerfile_base_images`](#nemo_gym-tasks-harbor-image_configs-dockerfile_base_images)           | The base image of every task Dockerfile the loader resolves under `folder`, with the tasks using it.   |
| [`load_compose_document`](#nemo_gym-tasks-harbor-image_configs-load_compose_document)             | The task's Compose overlay with `env_file` entries inlined and `$&#123;VAR&#125;` references resolved. |
| [`record_base_images`](#nemo_gym-tasks-harbor-image_configs-record_base_images)                   | Record the base image of every Dockerfile under `folder` next to the task folders, before they load.   |
| [`record_compose_images`](#nemo_gym-tasks-harbor-image_configs-record_compose_images)             | Write `compose-images.json` in `folder` for the Compose tasks among `tasks`.                           |
| [`record_image_configs`](#nemo_gym-tasks-harbor-image_configs-record_image_configs)               | Record the configuration of `references` (image reference to the tasks using it) into `folder`.        |
| [`without_main_build`](#nemo_gym-tasks-harbor-image_configs-without_main_build)                   | `document` with `main`'s `build` and `pull_policy` removed, for a task that runs from the image        |
| [`without_pull_policy`](#nemo_gym-tasks-harbor-image_configs-without_pull_policy)                 | `document` with `pull_policy` removed from every service, for the sandbox Compose adapter.             |
| [`without_restated_main_build`](#nemo_gym-tasks-harbor-image_configs-without_restated_main_build) | `document` with `main`'s `build:` dropped when it only restates Harbor's own build of the task's       |

### Data

[`COMPOSE_FILE_NAMES`](#nemo_gym-tasks-harbor-image_configs-COMPOSE_FILE_NAMES)

[`COMPOSE_IMAGES_FILE`](#nemo_gym-tasks-harbor-image_configs-COMPOSE_IMAGES_FILE)

[`LOGGER`](#nemo_gym-tasks-harbor-image_configs-LOGGER)

[`MAIN_BUILD_KEYS`](#nemo_gym-tasks-harbor-image_configs-MAIN_BUILD_KEYS)

[`RESTATED_BUILD_KEYS`](#nemo_gym-tasks-harbor-image_configs-RESTATED_BUILD_KEYS)

### API

```python
nemo_gym.tasks.harbor.image_configs.compose_file(
    task: nemo_gym.tasks.harbor.task.HarborTask
) -> pathlib.Path | None
```

The task's Compose overlay, if it has one.

```python
nemo_gym.tasks.harbor.image_configs.compose_image_references(
    task: nemo_gym.tasks.harbor.task.HarborTask
) -> list[str]
```

Every image a Compose task starts: the task image for `main` and each sidecar's image.

```python
nemo_gym.tasks.harbor.image_configs.dockerfile_base_images(
    folder: pathlib.Path,
    select: typing.Callable[[str], bool] | None = None
) -> dict[str, list[str]]
```

The base image of every task Dockerfile the loader resolves under `folder`, with the tasks using it.

`folder` is a task folder or a folder of task folders, as `discover_tasks` reads it; with
`select`, only the tasks whose folder name it accepts count. The image a task declares is the
effective one, exactly as `load_task` sees it: `task.toml` after the dataset's `[gym]`
overrides. A per-task `docker_image` override
thus turns a Dockerfile with `RUN` lines into a prebuilt image whose `FROM` is never
resolved, so there is nothing to record for it. Tasks whose `task.toml` does not load, and a
dataset whose `dataset.toml` does not load (which fails every task), are left for the loader
to report.

```python
nemo_gym.tasks.harbor.image_configs.load_compose_document(
    task: nemo_gym.tasks.harbor.task.HarborTask
) -> dict[str, typing.Any] | None
```

The task's Compose overlay with `env_file` entries inlined and `$&#123;VAR&#125;` references resolved.

Returns `None` for a task without a Compose file. Prepare and the resources server both read the
file through this function, so the document the sandbox starts is the one prepare validated, provided
both run with the same process environment (see `compose_variables`). A required variable
(`$&#123;VAR:?msg&#125;`) without a value, a task variable the host does not set or a missing `env_file`
raises `ImageConfigError` naming it; a variable nobody sets is an empty string, with a warning.

```python
nemo_gym.tasks.harbor.image_configs.record_base_images(
    folder: pathlib.Path,
    client: nemo_gym.tasks.harbor.registry.RegistryClient | None = None,
    select: typing.Callable[[str], bool] | None = None
) -> pathlib.Path | None
```

Record the base image of every Dockerfile under `folder` next to the task folders, before they load.

`select` limits the scan to the tasks whose folder name it accepts (see
`dockerfile_base_images`). Returns the file's path, or `None` when no task has a
Dockerfile the loader resolves.

```python
nemo_gym.tasks.harbor.image_configs.record_compose_images(
    tasks: list[nemo_gym.tasks.harbor.task.HarborTask],
    folder: pathlib.Path,
    client: nemo_gym.tasks.harbor.registry.RegistryClient | None = None,
    skipped: dict[str, str] | None = None
) -> pathlib.Path | None
```

Write `compose-images.json` in `folder` for the Compose tasks among `tasks`.

Returns the file's path, or `None` when no task uses Compose. A task whose Compose file does not
resolve (an `ImageConfigError` from `load_compose_document`) does not stop the others:
with `skipped` given it is recorded there by task id with the reason and left out, otherwise one
`ImageConfigError` at the end names every such task. An image the registry cannot resolve
still raises for the whole set, as `record_image_configs` says.

```python
nemo_gym.tasks.harbor.image_configs.record_image_configs(
    references: dict[str, list[str]],
    folder: pathlib.Path,
    client: nemo_gym.tasks.harbor.registry.RegistryClient | None = None,
    what: str = 'image'
) -> pathlib.Path | None
```

Record the configuration of `references` (image reference to the tasks using it) into `folder`.

Entries already recorded are kept as they are, so a pinned recording never changes
under a completed run; only references missing from the file are resolved. An entry an
older Gym wrote (no current `record_version`: it lacks fields the Dockerfile resolver
reads) is read again, at the digest it was pinned to rather than the tag, so the pin does
not move either. Returns the file's path, or `None` when there is nothing to record. A
reference the registry cannot resolve raises `ImageConfigError` naming the image,
the tasks and the registry's answer.

The registry is asked outside the file's sidecar lock (`record_lock`), so a slow
registry never holds up another process; the file is then read again, merged and replaced
under the lock, and an entry another process recorded in the meantime is kept over the
one resolved here, as a pinned entry always is (a legacy entry is replaced by its re-read).

```python
nemo_gym.tasks.harbor.image_configs.without_main_build(
    document: dict[str, typing.Any]
) -> dict[str, typing.Any]
```

`document` with `main`'s `build` and `pull_policy` removed, for a task that runs from the image
prepare built and recorded.

Harbor builds `main` from the task's Compose file at every start, so the file may give `main` a
`build:` (args, a target, a `dockerfile_inline`) and Harbor's own template sets `pull_policy: build`.
Gym builds that image once at prepare, with the same file, and the loader then substitutes the recorded
`hb__&lt;hash&gt;` image for `main` (`MAIN_IMAGE_NAME` resolves to it). The two keys describe a build that
already happened; the sandbox Compose adapter starts images only and refuses them as unsupported fields,
so they are dropped here rather than failing every episode of a task prepare accepted. Nothing else is
touched: a sidecar's `build:` is refused at prepare, and a task in pull or overlay mode goes through
`without_restated_main_build` instead.

```python
nemo_gym.tasks.harbor.image_configs.without_pull_policy(
    document: dict[str, typing.Any]
) -> dict[str, typing.Any]
```

`document` with `pull_policy` removed from every service, for the sandbox Compose adapter.

The key tells `docker compose` when to pull or build an image before starting it (Harbor's template sets
`pull_policy: build` on `main`; a task file may give a sidecar `always` or `missing`). The sandbox
starts the recorded image it is given and has no pull step of its own to configure, so the key is dropped
from the document the sandbox receives, in every image mode, instead of being refused as an unsupported
field. `build:` is not touched here: a sidecar's is still refused (no provider starts a locally built
sidecar) and `main`'s is dropped by `without_main_build` for a task that runs from its recorded
built image, or by `without_restated_main_build` when it only restates Harbor's build.

```python
nemo_gym.tasks.harbor.image_configs.without_restated_main_build(
    task: nemo_gym.tasks.harbor.task.HarborTask,
    document: dict[str, typing.Any]
) -> tuple[dict[str, typing.Any], str | None]
```

`document` with `main`'s `build:` dropped when it only restates Harbor's own build of the task's
Dockerfile, for a task in pull or overlay mode, and a reason instead when that section carries a key Gym
cannot apply without a build. Any other task is returned as it is.

Harbor builds `main` from `environment/Dockerfile` through its own template, which already sets
`build.context: $&#123;CONTEXT_DIR&#125;` (`docker-compose-build.yaml` at v0.22.0, appended before the task file),
so a task file that repeats `context`, `dockerfile: Dockerfile` or `pull` changes nothing there;
orca-bench, terminal_bench_aai and data-eng-bench do, some 600 hub tasks. Gym runs such a Dockerfile without
a build (the loader folded it into the task in pull mode, or runs its `RUN` lines at seed in overlay mode),
so the restatement is dropped as a built task's section is (`without_main_build`). `args`,
`target`, `dockerfile_inline`, another `dockerfile` or another `context` would change the image
Harbor builds and have no equivalent without one, so the task is reported, not started on the wrong image.

```python
nemo_gym.tasks.harbor.image_configs.COMPOSE_FILE_NAMES = ('docker-compose.yaml', 'docker-compose.yml', 'compose.yaml', 'compose.yml')
```

```python
nemo_gym.tasks.harbor.image_configs.COMPOSE_IMAGES_FILE = IMAGE_CONFIGS_FILE
```

```python
nemo_gym.tasks.harbor.image_configs.LOGGER = logging.getLogger(__name__)
```

```python
nemo_gym.tasks.harbor.image_configs.MAIN_BUILD_KEYS = ('build', 'pull_policy')
```

```python
nemo_gym.tasks.harbor.image_configs.RESTATED_BUILD_KEYS = frozenset({'context', 'dockerfile', 'pull'})
```