> 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.image_build

Build the task images a Dockerfile overlay cannot express (image mode three of three).

Prepare decides one of three modes per distinct Dockerfile. Pull, for a base-image-only file.
Overlay, for a pullable `FROM` plus `RUN`, `WORKDIR`, `ENV` and `USER` lines the
server applies at seed (see `nemo_gym.tasks.harbor.dockerfile`). Build, for everything else
(`COPY`, `ADD`, heredocs, BuildKit flags, multi-stage files): the task's `environment/`
folder is the build context, built once with docker, buildah or podman, pushed to a registry
when one is named, and recorded in `built-images.json` next to the task folders, keyed by
Harbor's content hash of the build context. The loader then runs the task in pull mode from the
recorded image, with `WORKDIR`, `ENV` and `USER` read from the image's recorded configuration
in `compose-images.json` like any other pulled image.

The hash and the image name are Harbor's (`nemo_gym.tasks.harbor.harbor_compat`, vendored from
Harbor v0.22.0): the hash is Harbor's environment id of the folder and the image is `hb__&lt;hash&gt;`, the
value Harbor exports to every Compose command as `MAIN_IMAGE_NAME`, under the repository's registry
path when one is named. Harbor's own build template sets no `image` (Compose then tags
`&lt;session&gt;-main`); Gym sets `image: hb__&lt;hash&gt;` so the result can be inspected, pushed and recorded,
and identical contexts get the same name on every prepare.

The docker backend is Harbor's build step. Harbor's Docker environment builds a task by running
`docker compose build` over its own `docker-compose-build.yaml` (service `main` with
`build.context: $&#123;CONTEXT_DIR&#125;`) followed by the task's `environment/docker-compose.yaml` when
there is one, with the project directory set to the `environment/` folder and its infrastructure
variables (`MAIN_IMAGE_NAME`, `CONTEXT_DIR` and friends) in the process environment:

* `DockerEnvironment.start()`, the build under the per-environment lock:
  [https://github.com/harbor-framework/harbor/blob/v0.22.0/src/harbor/environments/docker/docker.py#L868](https://github.com/harbor-framework/harbor/blob/v0.22.0/src/harbor/environments/docker/docker.py#L868)
  (`compose build` at L889-L896)
* `_run_docker_compose_command`, the `docker compose --project-name --project-directory -f ...` line:
  [https://github.com/harbor-framework/harbor/blob/v0.22.0/src/harbor/environments/docker/docker.py#L606](https://github.com/harbor-framework/harbor/blob/v0.22.0/src/harbor/environments/docker/docker.py#L606)
* `_docker_compose_paths`, the file order (build template, then the task's Compose file):
  [https://github.com/harbor-framework/harbor/blob/v0.22.0/src/harbor/environments/docker/docker.py#L316](https://github.com/harbor-framework/harbor/blob/v0.22.0/src/harbor/environments/docker/docker.py#L316)
* `ComposeInfraEnvVars` and `_compose_infra_env_vars`, the variables:
  [https://github.com/harbor-framework/harbor/blob/v0.22.0/src/harbor/environments/docker/docker.py#L238](https://github.com/harbor-framework/harbor/blob/v0.22.0/src/harbor/environments/docker/docker.py#L238)
  and #L552
* the template itself:
  [https://github.com/harbor-framework/harbor/blob/v0.22.0/src/harbor/environments/docker/docker-compose-build.yaml](https://github.com/harbor-framework/harbor/blob/v0.22.0/src/harbor/environments/docker/docker-compose-build.yaml)

Gym writes the same file per build (`harbor_compose_document`, with `CONTEXT_DIR` resolved and
`image: hb__&lt;hash&gt;` added), appends the task's `environment/docker-compose.yaml` the way Harbor does
(that one file name; the other Compose spellings the loader accepts are refused at build time, see
`task_compose_file`), and runs `docker compose build` with Harbor's environment
(`compose_build_env`: the process environment, the task's `[environment].env`, then Harbor's
infrastructure variables), so `main`'s own build args and target in the task file apply. The task
file is third-party content run on the build host, so it is checked first (`validate_task_compose`),
interpolated against the build's own environment the way Compose interpolates it (`environment/.env`
included, its values expanded as Compose's dotenv reader expands them), so a refused value
cannot hide behind `$&#123;X:-host&#125;`: build secrets, ssh, extra contexts and hosts, host networking, cache
credentials and any context or Dockerfile outside `environment/` are refused, and so is a sidecar with a
`build:` section, since no Gym provider starts a locally built sidecar (the loader records sidecars from
their `image:`). At seed the loader runs the task from the recorded image, so `main`'s `build:` and
`pull_policy` in the task file are dropped there (`nemo_gym.tasks.harbor.image_configs.without_main_build`).
`docker build` is never called. Harbor serialises concurrent builds of one environment with an
in-process lock; prepare builds each distinct context once, sequentially, so no lock is needed here.
buildah and podman are the OpenSandbox path: they build the Dockerfile directly and push it to a registry.

A build is cancel-safe: nothing is recorded until the tool has built, pushed and inspected the
image, and each image is recorded as soon as it is done, so an interrupted prepare resumes
where it stopped. The record files are replaced atomically under a sidecar lock
(`nemo_gym.tasks.harbor.task.record_lock`), so two Gym processes recording into one datasets
folder keep each other's entries and a reader never sees a half-written file.

The hash names the image; it is not the whole build. Harbor's hash covers file names and bytes only, so the
configuration record of each built image in `compose-images.json` also carries an inputs digest
(`build_inputs_digest`, over every entry of the context tree with its mode, content or link target, the
names Harbor's hash skips included, plus `main`'s `build` section of the task's Compose file as the build reads
it: interpolated against the build's environment, its paths resolved, its arguments as the tool is given them),
beside the `config_digest` of what came out. A recorded image is reused only when the hash and the digest both
match: a context whose inputs changed without changing its hash is rebuilt under the same name and its records
replaced (`build_inputs_mismatch`).

## Module Contents

### Functions

| Name                                                              | Description                                                                           |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| [`build_images`](#nemo_gym-tasks-harbor-image_build-build_images) | Build and record every context under `folder` that needs it and has no usable record. |

### Data

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

### API

```python
nemo_gym.tasks.harbor.image_build.build_images(
    folder: pathlib.Path,
    builder: nemo_gym.tasks.harbor.build_tools.ImageBuilder,
    select: typing.Callable[[str], bool] | None = None,
    log: typing.Callable[[str], None] = print
) -> list[nemo_gym.tasks.harbor.build_plan.BuildResult]
```

Build and record every context under `folder` that needs it and has no usable record.

A recorded image is reused when it fits this builder: pushed, or local when nothing is pushed,
and of the requested platform. Otherwise it is rebuilt and its record replaced, and the reason
is logged. A context is keyed by its content hash, so a changed build context is a new build;
deleting a context's entry from `built-images.json` forces one for an unchanged context.
Returns the images built by this call. Raises `BuildError` on the first failure, after
recording whatever was built before it.

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