# Images

> Run on the catalog images, build your own from a few steps or a Dockerfile, and use private registries.

Source: https://nodus-platform-site.pages.dev/docs/guides/images/
Build revision: 211ad9f836655b1c3a2668c4693e442471f28614

Every Job, Sandbox, Function and Agent runs in a container image. You can use a catalog image, any public or private registry image, or an Image that Nodus builds for you. When you submit work, Nodus resolves the image’s tag to a digest and records it, so a retry or a recovery runs exactly the same bytes.

## Catalog images

|Image|Contents|Default for|
|-|-|-|
|`nodus/python:3.12` (also `3.10`, `3.11`, `3.13`)|Debian slim, Python, uv, git|Jobs without a GPU|
|`nodus/pytorch:2.8-cuda12.8`|CUDA 12.8 runtime, Python 3.12, PyTorch 2.8|Jobs with a GPU|
|`nodus/agent-tools`|Python 3.12, Node 22, git, ripgrep, the Nodus SDK|Sandboxes and Agents|

Terminal window

```console
nodus get images -n nodus
nodus run --gpu L4 --image nodus/pytorch -- python -c "import torch; print(torch.cuda.get_device_name())"
```

Catalog images run as uid 1000 and include `/bin/sh`.

## Build availability

Current hosted deployments support catalog images and prebuilt public or private registry images. Image builds from steps, Dockerfiles and Sandbox snapshots are unavailable. Those requests return `ImageBuildUnavailable` with HTTP 503 before accepting a build or charging for one. Publish your image to a registry, then use its reference directly or create an Image with only `base` and no steps. Environment packages that need a pip build must instead name a prebuilt `package.image`.

Existing unstarted build requests report `Failed` with an explanation instead of waiting indefinitely. The build examples below describe the contract for a deployment with a configured build runner and registry.

## Build an Image

An Image starts from a `base` and adds `steps`: `aptInstall`, `pipInstall`, `uvPipInstall`, `uvSync`, `run`, `copy`, `env` and `workdir`.

image.yaml

```yaml
apiVersion: nodus.dev/v1
kind: Image
metadata:
  name: ex-images-build
spec:
  base: nodus/python:3.12
  steps:
    - aptInstall: [jq]
    - pipInstall: {packages: ["requests==2.32.5"]}
    - env: {APP_MODE: example}
```

Terminal window

```console
nodus apply -f image.yaml
nodus wait image/ex-images-build --for=condition=Ready
```

The build log streams from `GET …/images/{name}/log?follow=true`; the Python SDK prints it while it builds.

Run on it with `imageRef`:

job.yaml

```yaml
apiVersion: nodus.dev/v1
kind: Job
metadata:
  name: ex-images-build
spec:
  imageRef: {name: ex-images-build}     # runs the digest the Image built, pinned at admission
  command: [python, -c, "import os, requests; print('requests', requests.__version__, os.environ['APP_MODE'])"]
```

In Python:

```python
image = (nodus.Image.from_registry("nodus/pytorch:2.8-cuda12.8")
         .apt_install("git").pip_install("transformers==4.57.6", "peft")
         .env({"HF_HUB_ENABLE_HF_TRANSFER": "1"}))
```

Builds run on Nodus and are billed per vCPU-second at the CPU rate. An Image whose spec matches one your org has already built, on the same base digest, reuses that digest without building (`status.build.cached: true`). An Image with only a `base` is ready at once: it is that image’s digest.

You can also build from a Dockerfile, inline or uploaded with its context:

```yaml
spec:
  dockerfile:
    inline: |
      FROM nodus/python:3.12
      RUN pip install polars==1.33.1
```

Build-time credentials go in `buildSecrets`: they are mounted for the build only and never stored in a layer.

## Private registries

Create a `Registry` Secret ([Secrets](https://nodus-platform-site.pages.dev/docs/guides/secrets/#private-registries)) and name it under `imagePullSecrets`, on the Job or Sandbox for `image`, or on the Image for a private `base`.

When work runs on a provider that pulls containers itself, Nodus copies the image by digest into your org’s space in the Nodus registry first, so your registry credential never reaches the provider. Such providers need `/bin/sh` in the image; an image without it gets the `ProviderContainerNeedsShell` warning and runs elsewhere.

## Pull a built Image yourself

A built Image lives in the Nodus registry under your org; `status.reference` holds its full reference. Log in with any username and an API key as the password, then pull it:

Terminal window

```bash
REF=$(nodus get image ex-images-build -o jsonpath='{.status.reference}')
echo "$NODUS_API_KEY" | docker login "${REF%%/*}" -u nodus --password-stdin
docker pull "$REF"
```

Viewers, and keys without the `images:write` scope, can pull but not push.

## Status and errors

`nodus get image ex-images-build` shows `PHASE`, `DIGEST` and `SIZE`. A failed build has a reason:

|Reason or error|Meaning|
|-|-|
|`BaseNotFound`|The base does not exist, or its registry refused the pull Secret|
|`StepFailed`|A step exited non-zero; the build log shows which|
|`BuildTimeout`|The build ran past its time limit|
|`ImageNotFound`, `ImagePullFailed`|A Job’s image could not be resolved when you submitted it|
|`ImageNotReady`|A Job or Sandbox names an Image that has not finished building|
