> ## Documentation Index
> Fetch the complete documentation index at: https://starforge.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 构建自定义镜像

> Dockerfile、不要打进镜像的东西、仓库白名单、tag 和 digest

```dockerfile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
FROM nvidia/cuda:12.4.1-devel-ubuntu22.04
RUN pip install torch transformers trl accelerate
COPY . /workspace
```

训练镜像里装 CUDA、Python，以及你的 `train.sh` 会 import 的东西。仅此而已。

<Warning>
  不要把 StarForge CLI 装进镜像，也不要从旧的教程仓库里抄可观测性代码。
  平台运行时会在启动时以内容寻址 PEX 的形式注入——镜像里不需要任何东西，回传就能工作。
</Warning>

## 镜像里装什么

| 装进去                                                                               | 不要装                              |
| --------------------------------------------------------------------------------- | -------------------------------- |
| 和集群驱动匹配的 CUDA / cuDNN                                                             | `sf` CLI、`forge-console`         |
| Python 3.10 到 3.13（runner 的 `requires_python` 是 `>=3.10,<3.14`）                   | `.forge/` token、当成一层的 `HF_TOKEN` |
| 你的训练栈（transformers、deepspeed、vLLM 等）                                              | 数据集（运行时拉取或用平台数据集）                |
| 若走 catalog 的 custom recipe（`external` 观测）并要 `import starforge`：装 `starforge-core` | 写死的控制台 URL                       |

日志：`print`、`logging`、框架打到 stdout/stderr 的内容进控制台日志页。不要 POST `/api/ingest/logs`。

曲线：不会从 stdout 里猜。训练代码里调 `starforge.report`。

## 一份能改的 Dockerfile

风格对齐平台 TRL 镜像：CUDA devel 基底、venv 在 `PATH` 最前。catalog `custom/custom` 建议在后面的 `RUN` 里装 `starforge-core`。

```dockerfile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
# syntax=docker/dockerfile:1
ARG BASE_IMAGE=nvidia/cuda:12.8.0-devel-ubuntu24.04
FROM ${BASE_IMAGE}

ARG PYTHON_VERSION=3.12
ENV DEBIAN_FRONTEND=noninteractive \
    HF_HUB_ENABLE_HF_TRANSFER=1

RUN apt-get update && apt-get install -y --no-install-recommends \
        git curl ca-certificates \
    && rm -rf /var/lib/apt/lists/*

COPY --from=ghcr.io/astral-sh/uv:0.8 /uv /usr/local/bin/uv

RUN uv python install ${PYTHON_VERSION} \
 && uv venv --python ${PYTHON_VERSION} /opt/train-venv
ENV VIRTUAL_ENV=/opt/train-venv PATH=/opt/train-venv/bin:$PATH

# 生产请钉版本，不要留未 pin 的 extra。
RUN uv pip install --no-cache \
        "torch" \
        "transformers" \
        "accelerate" \
        "trl" \
        "hf-transfer" \
        "starforge-core"

# Job Capsule 用镜像里的 Python。保持这个 venv 在 PATH 最前。
CMD ["python", "-c", "import torch; print(torch.__version__)"]
```

包名按 `train.py` 实际 import 来改。DeepSpeed 要运行期 JIT 的话，通常需要带 nvcc 的 devel 镜像，和 `Dockerfile.trl` 一样。

`local` 执行器会用 `bash` 跑入口。`PATH` 仍须指向那个 venv，否则 `train.sh` 里的 `python` 可能是没有 torch 的 `/usr/bin/python`。

## 构建和推送

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
docker build -t myregistry.io/my-train:v1 .
docker push myregistry.io/my-train:v1

# 记下 digest 再提交：
docker buildx imagetools inspect myregistry.io/my-train:v1
```

tag 或 digest 都能提交：

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
sf submit my-custom --profile h200:8 \
  --image myregistry.io/my-train:v1 \
  --observability-url https://wandb.example/my-proj

sf submit my-custom --profile h200:8 \
  --image myregistry.io/my-train@sha256:abcd… \
  --observability-url https://wandb.example/my-proj
```

## 白名单

`--image` 会解析出仓库主机（`ghcr.io`、`localhost:5000`、没有主机名时是 `docker.io`）。这个主机必须出现在服务端 `FORGE_ALLOWED_IMAGE_REGISTRIES`（`.env` 里逗号分隔的主机名）。

| 服务端设置      | 自定义 `--image`      | 一等框架的 `--image` 覆盖 |
| ---------- | ------------------ | ------------------ |
| 名单为空       | **拒绝**（「空：禁止用户镜像」） | 允许（空名单不限制一等框架覆盖）   |
| 有名单，主机不在里面 | 拒绝，并打出当前允许的主机      | 同样拒绝               |
| 主机在名单里     | 允许                 | 允许                 |

运维可以写成 `FORGE_ALLOWED_IMAGE_REGISTRIES=registry.example.com,ghcr.io`。节点还要能拉到镜像（KubeRay 的 imagePullSecret、agent 上的 Docker login、Slurm 侧已经转好的 SIF）。

## 各执行器

| 执行器               | 镜像形态                                                                  |
| ----------------- | --------------------------------------------------------------------- |
| `local` / `agent` | OCI。agent 节点需要同样的拉取凭据。第一期 agent 是一作业一节点。                              |
| `kuberay`         | OCI + RayJob 上的 pull secret。拉得慢会停在 `PENDING`。                         |
| `slurm`           | runtime 注册表通常指向 SIF/SQSH。笔记本上的 Docker tag 往往不够，运维要转换并登记 `runtime_id`。 |

拉镜像慢时作业停在 `PENDING`，直到 preRunning 超时变成 `FAILED`。这不是训练代码的问题。

## 不要做的事

* 把 StarForge 源码 COPY 进 `/opt`，指望它和服务器 runner 对得上。capsule 是内容寻址、启动时注入的。
* 把 `HF_TOKEN` 烤进镜像层。服务端会注入 `HF_TOKEN` / `HUGGING_FACE_HUB_TOKEN`，或给一个密钥文件路径（`CLUSTER_SECRETS_FILE`）。
* 把 `FORGE_INGEST_URL` 设成控制台机器上的 `127.0.0.1`。训练节点用不了你笔记本的 loopback。这个变量在服务端，提交的人不用设；stdout 正常但图表空，多半是运维配错了。
