> This page is for version 0.5.1.
> 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.prepare_builds

The build step of prepare and the two prepare-time Compose field checks.

Before any build runs, each build context's raw Compose file is checked against the sandbox
adapter's field allow-list, and after the tasks load each task's resolved document is checked
the same way, so a task the adapter would refuse at seed is refused at prepare with the same
message. The build step plans the builds, drops refused contexts, reuses recorded images that
fit, honours the builds table, constructs the ImageBuilder and collects per-task failure
reasons. cli.py calls it from prepare_target.

## Module Contents

### Functions

| Name                                                                                                         | Description                                                                                              |
| ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| [`_build_dockerfiles`](#nemo_gym-tasks-harbor-prepare_builds-_build_dockerfiles)                             | Build and record the images of the selected tasks whose Dockerfile needs a build.                        |
| [`refused_before_build`](#nemo_gym-tasks-harbor-prepare_builds-refused_before_build)                         | The field refusals of the tasks `plan` would build, read from their Compose files before any build runs. |
| [`unsupported_compose_fields_error`](#nemo_gym-tasks-harbor-prepare_builds-unsupported_compose_fields_error) | Why the sandbox Compose adapter would refuse `task`'s document at seed, or `None` when it starts it.     |

### Data

[`REFUSED_NOTE`](#nemo_gym-tasks-harbor-prepare_builds-REFUSED_NOTE)

### API

```python
nemo_gym.tasks.harbor.prepare_builds._build_dockerfiles(
    folder: pathlib.Path,
    build: nemo_gym.tasks.harbor.build_options.BuildOptions,
    select
) -> dict[str, str]
```

Build and record the images of the selected tasks whose Dockerfile needs a build.

With builds disabled, the tasks that need one are left for the loader to skip with its
message, and a recorded image that does not fit the run (another platform, or local when the run
pushes) leaves its tasks out with the reason, as the enabled path would rebuild it. When every context
has a usable record nothing is built and no build tool is looked for. Otherwise a missing builder stops
the preparation, naming the tasks. A failed build does not:
each context is built on its own, and the tasks of one that fails are returned by id with the
reason, for the caller to leave out the way it leaves out a task that does not load. A planned task
whose Compose file the sandbox adapter would refuse whatever the build gave (`refused_before_build`)
is returned the same way, and its context is neither built nor pushed.

```python
nemo_gym.tasks.harbor.prepare_builds.refused_before_build(
    plan: list[nemo_gym.tasks.harbor.build_plan.BuildPlanEntry]
) -> dict[str, nemo_gym.tasks.harbor.errors.HarborTaskError]
```

The field refusals of the tasks `plan` would build, read from their Compose files before any build runs.

`unsupported_compose_fields_error` is the authoritative check, on the loader's document once the
tasks load. This one needs only key names, which interpolation and `env_file` inlining do not add, so it
reads the raw file the way the loader's document will look: `pull_policy` dropped, `main`'s `build:`
dropped (the task is about to be built and will run from its recorded image) and `env_file` set aside,
as the loader folds it into `environment`. A task it names is neither built nor pushed for an image no
episode would start, and a build that would fail for it cannot stop the preparation. A file that does not
parse is left to the loader to report in its own words.

```python
nemo_gym.tasks.harbor.prepare_builds.unsupported_compose_fields_error(
    task: nemo_gym.tasks.harbor.task.HarborTask
) -> nemo_gym.tasks.harbor.errors.HarborTaskError | None
```

Why the sandbox Compose adapter would refuse `task`'s document at seed, or `None` when it starts it.

The document checked is the one the server hands the adapter (`environment_servers/harbor/app.py`,
`_compose_document`): interpolated and `env_file`-inlined by the loader, `pull_policy` dropped from
every service, `main`'s `build:` dropped when the task runs from its recorded built image or when it
only restates Harbor's own build of the task's Dockerfile (`without_restated_main_build`; a build
key Gym cannot apply without a build is this task's reason). A field no provider starts (`deploy`,
`extra_hosts`, `platform`, a sidecar `build:`) is then a per-task reason naming the service and the
field, recorded in `skipped.json` like a task that does not load, instead of a failure of every episode
at seed.

```python
nemo_gym.tasks.harbor.prepare_builds.REFUSED_NOTE = ' (the sandbox Compose adapter does not start them)'
```