jetson-containers Agent Skills & AI Rules

GitHub

Machine Learning Containers for NVIDIA Jetson and JetPack-L4T

4,888 stars Jupyter Notebook 57 files ยท ~125,704 tokens #containers#docker#dockerfiles#jetson#machine-learning#numpy#nvidia#pandas
RAW Rules
257 Discovered Rules โ€ข ~548,846 Estimated Tokens
Quick:
Loading skill content...

AI Agent Protocol & Instructions

AGENTS.md root-instruction ~2,402 tokens
Raw
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Overview

jetson-containers is a modular container build system for NVIDIA Jetson devices. It discovers packages, resolves their dependencies, chains their Dockerfiles together, and runs `docker build` to produce multi-stage images optimized for specific L4T/JetPack/CUDA versions.

## Commands

### Build & Run
```bash
# Build one or more packages (chains Dockerfiles in dependency order)
jetson-containers build <package> [<package2> ...]
# or equivalently:
./build.sh <package> [<package2> ...]

# Find and run a compatible container image
jetson-containers run $(autotag <package>) [command]
# or equivalently:
./run.sh $(autotag <package>) [command]

# Find the best matching image tag (local โ†’ DockerHub โ†’ build)
autotag <package>

# Useful build flags
jetson-containers build --simulate <package>              # dry-run: print commands without building
jetson-containers build --multiple pytorch tensorflow     # build each package as a separate image
jetson-containers build --list-packages                   # list all discovered packages
jetson-containers build --show-packages <package>         # show resolved metadata for a package
jetson-containers build --base=xyz:latest <package>       # start the build chain from an existing image
jetson-containers build --push=myuser <package>           # push to a registry after building

# Check for upstream version upgrades (--apply to write changes)
jetson-containers upgrade [<package> ...] [--apply]
```

### Code Style
```bash
# Install pre-commit hooks (one-time setup)
pip install -r requirements.txt
pre-commit install

# Run linting/formatting checks manually
pre-commit run --all-files
```

Pre-commit runs **Black** (formatter, line-length=88) and **Flake8** (linter) only on `jetson_containers/` and `test_precommit.py`. `packages/` Dockerfiles and `config.py` files are **not** linted by pre-commit. Note: `skip-string-normalization = true` in `pyproject.toml`.

### Tests
```bash
# Run a package config unit test directly (uses stdlib unittest, no pytest needed)
python3 tests/verify_triton_config.py
```

### Installation
```bash
bash install.sh   # sets up venv, installs deps, symlinks CLI tools to /usr/local/bin
```

## Architecture

### Package System

Every buildable unit is a **package**. Packages live under `packages/<category>/<name>/` and define metadata via one of (in priority order):

1. **YAML header in Dockerfile** โ€” metadata between `#---` markers at the top
2. **`config.yaml` / `config.yml`** โ€” static YAML config file
3. **`config.json`** โ€” meta-container (no Dockerfile, just dependency composition)
4. **`config.py`** โ€” dynamic config, returns a list of package dicts at build time

Key metadata fields: `name`, `alias`, `depends` (build-order deps), `requires` (version constraints on L4T/CUDA/Python), `test`, `build_args`, `dockerfile`.

### Build Pipeline

1. **`jetson_containers/packages.py`** โ€” scans `packages/` recursively, runs `config.py` scripts, resolves the dependency graph, filters by L4T version compatibility, and exposes a custom Python meta path finder so packages can `from packages.ml.pytorch.version import ...`
2. **`jetson_containers/build.py`** โ€” CLI entry point; parses args and dispatches
3. **`jetson_containers/container.py`** โ€” core build logic: stitches multiple package Dockerfiles into one combined `Dockerfile`, invokes `docker build`, optionally runs per-package tests, and optionally pushes to a registry
4. **`jetson_containers/l4t_version.py`** โ€” detects architecture (tegra-aarch64, aarch64, x86_64) and reads L4T/JetPack/CUDA/GPU-arch info from `/etc/nv_tegra_release`; all version variables (e.g. `CUDA_VERSION`, `PYTORCH_VERSION`) flow from here into build args

### Dynamic Package Generation

`config.py` files execute at build time and can return multiple package dicts to produce version variants. For example, PyTorch's `config.py` generates `pytorch:2.8`, `pytorch:2.8-all`, `pytorch:2.8-builder`, etc. Aliases (`torch` โ†’ `pytorch:2.8`) are resolved during package discovery.

When a package's version selection logic is complex, put it in a separate `version.py` alongside `config.py`. This lets other packages cross-import it:
```python
from packages.ml.pytorch.version import PYTORCH_VERSION
```
The `_PackagesFinder` meta path finder in `packages.py` resolves these imports against the `packages/` tree without requiring `__init__.py` files.

### Dockerfile Conventions

Use `uv pip install` (not plain `pip install`) inside Dockerfiles โ€” `uv` is pre-installed in the base images and is significantly faster. Every Dockerfile must start with:
```dockerfile
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
```

### Version Overrides

Copy `.env.default` โ†’ `.env` to override `L4T_VERSION`, `CUDA_VERSION`, `LSB_RELEASE`, `CUDA_ARCH`, local PyPI/APT mirror URLs, and SCP/webhook credentials without modifying tracked files.

### Creating a New Package

1. Create `packages/<category>/<name>/Dockerfile` with a `#---` YAML header (or a `config.yaml`/`config.py`).
2. Declare `depends` on upstream packages (e.g., `pytorch`, `cuda`).
3. Optionally add `test.py` or `test.sh` for post-build validation.
4. Build: `jetson-containers build <name>`

## Security Features

### Multi-Stage Builds

The build system is multi-stage aware. Packages can use `FROM ... AS <alias>` for intermediate stages (e.g. a compiler/builder stage) without those stages polluting the final image. The build system tracks stage boundaries and only injects BuildKit features (ccache env vars, `--device` mounts, ccache `--mount` flags) into the **final** stage (the `FROM ${BASE_IMAGE}` stage). Intermediate stages are left untouched.

Declare multi-stage packages in config metadata:
```yaml
#---
# name: mypackage
# multistage: true
# depends: [cuda]
#---
ARG BASE_IMAGE
FROM gcc:12 AS builder
RUN make install

FROM ${BASE_IMAGE}
COPY --from=builder /usr/local /usr/local
RUN ldconfig
```

The final stage **must** use `ARG BASE_IMAGE` / `FROM ${BASE_IMAGE}` so it chains correctly in the dependency build pipeline.

### Non-Root User

**At build time** โ€” pass identity via env vars; packages can create and switch to the declared user:
```bash
export CONTAINER_USER=jetson
export CONTAINER_UID=1000
export CONTAINER_GID=1000
jetson-containers build mypackage
```

`CONTAINER_USER`, `CONTAINER_UID`, `CONTAINER_GID` are forwarded as `--build-arg` automatically. Packages consume them like:
```dockerfile
ARG CONTAINER_USER=root
ARG CONTAINER_UID=0
ARG CONTAINER_GID=0
RUN if [ "$CONTAINER_UID" != "0" ]; then \
        groupadd -g $CONTAINER_GID $CONTAINER_USER && \
        useradd -u $CONTAINER_UID -g $CONTAINER_GID -m $CONTAINER_USER; \
    fi
USER $CONTAINER_USER
```

Declare the supported user in package metadata (`run_user` is informational for tooling):
```yaml
# run_user: jetson
```

**At runtime** โ€” set `DOCKER_USER` to run the container as a non-root identity:
```bash
DOCKER_USER=1000:1000 ./run.sh myimage
# or by name (the user must exist in the image):
DOCKER_USER=jetson ./run.sh myimage
```
X11 forwarding (`xhost`) is automatically updated to grant the declared user display access.

For GPU access, `run.sh` also discovers the numeric GIDs that own the NVIDIA/Tegra device nodes (`/dev/nvhost*`, `/dev/nvmap`, `/dev/nvgpu/*`, `/dev/dri/render*`) and adds them via `--group-add <gid>`. Without this, a non-root process can't open the GPU nodes and CUDA fails with error 801 (`cudaGetDeviceCount` "operation not supported"). Numeric GIDs are used because the host's `render` GID usually doesn't match the container's. See [`docs/run.md`](/docs/run.md#gpu-access-as-non-root-cuda-error-801).

### Secrets

**Build-time secrets** (never baked into image layers) โ€” declare secrets in package metadata:
```yaml
#---
# name: mypackage
# secrets: [huggingface_token]
#---
```

Point each secret at a file on the host via `JETSON_SECRET_<NAME>`:
```bash
export JETSON_SECRET_HUGGINGFACE_TOKEN=~/.hf_token
jetson-containers build mypackage
```

Secrets are mounted as `/run/secrets/<name>` inside `RUN` steps only (BuildKit `--secret`). Use them in the Dockerfile:
```dockerfile
RUN --mount=type=secret,id=huggingface_token \
    HF_TOKEN=$(cat /run/secrets/huggingface_token) \
    python3 download_model.py
```

If the env var is missing, a warning is printed and the build continues without that secret.

**Runtime secrets** โ€” prefer file-backed tokens over env vars (values in env vars are visible in `docker inspect`):
```bash
# Preferred: mount token file into /run/secrets/
HUGGINGFACE_TOKEN_FILE=~/.hf_token ./run.sh myimage

# Fallback (token visible in docker inspect):
HUGGINGFACE_TOKEN=hf_xxx ./run.sh myimage
```

`HUGGINGFACE_TOKEN_FILE` mounts the file read-only at `/run/secrets/huggingface_token` and sets `HF_TOKEN_FILE` inside the container.

### Key Supporting Modules

- `jetson_containers/tag.py` โ€” image tagging conventions
- `jetson_containers/docs.py` โ€” auto-generates per-package docs from metadata
- `jetson_containers/ci.py` โ€” CI/CD helpers
- `jetson_containers/network.py` / `webhook.py` โ€” GitHub API integration and build notifications
- `jetson_containers/logging.py` โ€” colored terminal output used throughout
- `jetson_containers/upgrade.py` โ€” checks packages against upstream (PyPI/GitHub) for newer versions; powers `jetson-containers upgrade`
- `jetson_containers/db.py` โ€” syncs DockerHub/GitHub metadata for registry introspection