environments
Set up reproducible robotics environments with uv, Docker, or GPU hosts.
Install
npx skills add https://github.com/robium-ai/robium/tree/main/skills/environments
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install robium-ai-robium@llmmart
git clone https://github.com/robium-ai/robium.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole robium-ai/robium collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Environments
Define the runtime contract before writing around machine-specific accidents. Local and remote runs should differ only where the hardware genuinely differs.
Choose the smallest sufficient boundary
- uv: pure-Python robotics, ML, data, and tooling without required system
packages. Commit the lockfile and run project commands through
uv run. - Docker: ROS 2, native libraries, apt packages, an exact Linux userspace, or deployment as a container.
- Docker plus uv: use Docker for the system layer and a project environment for substantial Python dependencies; do not turn the image's system Python into an untracked package set.
- Remote GPU host: use when the workload has no viable local hardware path. Record this as a deliberate exception to local/remote symmetry.
Preflight the actual machine before deciding. Confirm architecture, OS, Docker daemon, accelerator, driver, disk, and available Python tooling. Use the Robium doctor if present; fall back to direct probes when it is not.
Keep the contract reproducible
- Pin Python and dependency resolution with a lockfile, or pin the container
base and build inputs. Avoid
latestfor reproducible applications. - Keep dependency declarations in one source of truth; do not preserve manual installation steps as hidden prerequisites.
- Match container CUDA/runtime requirements to the target host driver rather than the developer laptop.
- Design remote work as headless first. Use web visualization instead of making X forwarding part of the normal workflow.
- Test the same entry command in the target environment and prove important hardware, file, device, network, and display assumptions.
- Before claiming first-run reproducibility, run the complete setup from a clean project copy with the relevant package, model, and asset caches empty. A warm development checkout can hide broken fetch and bootstrap behavior.
Go deeper only when needed
- Pure Python and lockfiles: uv patterns.
- ROS/system images, build layout, and parity: Docker patterns.
- NVIDIA passthrough and headless operation: GPU and remote.
- Real-robot LAN, Wi-Fi, DDS/NAT, and Mac host issues: robot networking.
- Workloads that can exist only on cloud GPUs: GPU cloud, then the relevant provider skill for provisioning.
- macOS, Apple Silicon, arm64, and cold-build evidence: platform notes.
- Use the bundled examples only as starting shapes; verify all tags and install steps against current upstream documentation.
Cross into integration when multiple modules, containers, or transports must
be wired together. Cross into a deployment skill only after the image and
runtime contract work locally or in an equivalent target environment.
Done
- A fresh machine can reproduce the environment from committed inputs.
- The same documented command starts the workload locally and remotely, except for named hardware flags.
- GPU, devices, network, files, and display behavior are verified on the target.
- Current details match official uv, Docker, ROS image, and NVIDIA Container Toolkit documentation.
Files (robium)
-
examples
-
Dockerfile.gpu-ml 2.3 KB · in bundle
-
Dockerfile.ros2 1.7 KB · in bundle
-
pyproject-uv.toml 1 KB
# status: unverified # source: https://docs.astral.sh/uv/guides/projects/ (fetched via ctx7, astral-sh/uv) # # Minimal pyproject.toml for a pure-Python robium project managed with uv. # Use this shape when the project does NOT need ROS 2 or other system-level # dependencies; see the uv choice in skills/environments/SKILL.md. Generated # by `uv init` + `uv add`, then hand-trimmed for illustration; re-run # `uv add`/`uv lock` in a real project rather than copying this verbatim. [project] name = "robium-example-agent" version = "0.1.0" description = "Example pure-Python ML/robotics project managed with uv" readme = "README.md" requires-python = ">=3.11" dependencies = [ "numpy>=1.26", "torch>=2.3", ] [dependency-groups] dev = [ "pytest>=8.0", "ruff>=0.6", ] [tool.uv] # Include the `dev` group by default on `uv sync` / `uv run`, so CI and local # dev use the same environment without extra flags. default-groups = ["dev"] [build-system] requires = ["hatchling"] build-backend = "hatchling.build"
-
-
references
-
docker-patterns.md 5.6 KB
# Docker patterns How to shape a Docker image for a robium project that needs ROS 2 or other system-level dependencies. This covers a *single environment's* Dockerfile shape; wiring multiple app modules together with compose is the `integration` skill's job; don't duplicate that here, cross-reference it. Sources: official ROS 2 images at [hub.docker.com/_/ros](https://hub.docker.com/_/ros) (fetched directly, not from memory: tags below were current at authoring time; re-verify before using), and the uv + Docker integration guide at [docs.astral.sh/uv/guides/integration/docker](https://docs.astral.sh/uv/guides/integration/docker/). ## Official ROS 2 image tags The official `ros` image on Docker Hub publishes, per distro, three variant tiers plus an OS-codename-suffixed form: - `ros:<distro>-ros-core`: minimal ROS 2 install. - `ros:<distro>-ros-base`: adds basic tools/libraries (the usual starting point for an application image). - `ros:<distro>-perception`: adds perception-related packages. - Each of the above also has an explicit OS-codename form, e.g. `ros:jazzy-ros-base-noble` / `ros:lyrical-ros-base-resolute`; prefer the explicit form when you want to pin the Ubuntu base as well as the ROS distro, for the strongest local/remote parity guarantee. Currently published distros include `jazzy` (Ubuntu Noble base) and `lyrical` (Ubuntu Resolute base, current LTS), among others (`humble`, `kilted`, `rolling`). Confirm the current set and exact tags at [hub.docker.com/_/ros](https://hub.docker.com/_/ros) before pinning one; this list changes as distros reach EOL and new ones ship. Desktop-variant images are not part of the official minimal set (kept lean/secure); if you need a desktop image, that's a deliberate, separate choice. There is no official `ros-desktop` reason to reach for the OSRF-hosted `osrf/ros2` images for a headless application container: official + minimal is the default; only deviate with a stated reason. ## Base pattern: ROS 2 image + uv for the Python layer Even inside a ROS 2 container, keep the "never pip install into system Python" directive: if the workspace has pure-Python glue code (a bridge script, a data-processing node, an ML inference node) with its own dependencies, manage those with uv rather than `pip install`ing them into the image's system Python. ```dockerfile FROM ros:jazzy-ros-base # Install uv by copying the binary from the official distroless image; # no pip/curl needed for this step. COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/ WORKDIR /workspace COPY ./src ./src COPY pyproject.toml uv.lock ./ # Python-side deps for glue code, isolated in a project venv, not the # container's system Python. RUN uv sync --locked # ROS 2 workspace build (colcon ships in ros-base; add # python3-colcon-common-extensions via apt if a variant lacks it). RUN . /opt/ros/jazzy/setup.sh && colcon build --symlink-install ``` See `examples/Dockerfile.ros2` for the full, runnable-shape version (non-root user, entrypoint that sources both the ROS 2 and workspace overlays). ## Multi-stage builds with uv (mixed / heavy-Python case) When the Python side is heavy (a training or inference stack with large dependencies like torch), use a proper multi-stage build so the final image doesn't carry uv's cache or build-only tooling: ```dockerfile FROM python:3.12-slim-trixie AS builder COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ ENV UV_PYTHON_DOWNLOADS=0 UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy WORKDIR /app RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,source=uv.lock,target=uv.lock \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ uv sync --locked --no-install-project COPY . /app RUN --mount=type=cache,target=/root/.cache/uv \ uv sync --locked FROM python:3.12-slim-trixie COPY --from=builder /app/.venv /app/.venv ENV PATH="/app/.venv/bin:$PATH" WORKDIR /app COPY . /app CMD ["python", "train.py"] ``` The `--mount=type=cache` on `/root/.cache/uv` speeds up rebuilds without baking the cache into any image layer; the two-step `sync` (deps first, then the project) maximizes Docker layer-cache hits when only application code changes. See `examples/Dockerfile.gpu-ml` for the GPU-base variant of this same shape. ## Non-root users Run the final container as a non-root user; it's both a security default and a parity aid (file permissions on mounted volumes behave the same locally and remotely): ```dockerfile RUN groupadd --system --gid 1000 robium \ && useradd --system --gid 1000 --uid 1000 --create-home robium USER robium ``` ## Local == remote parity, the Docker half These checks make the runtime contract in `SKILL.md` reproducible: - **Pin tags, not `latest`.** `ros:jazzy-ros-base-noble`, not `ros:jazzy`; ideally pin a digest (`@sha256:...`) for anything long-lived. - **`.dockerignore` your `.venv`, `build/`, `install/`, `log/`** (ROS 2 colcon artifacts); building on a clean tree locally and remotely avoids "works because of stale local build state" bugs. - **Bake the lockfile in, don't `COPY` a `requirements.txt` generated ad-hoc.** `uv.lock` (or the ROS 2 `rosdep`-resolved package list) is the single source of truth referenced from both local and CI/remote builds. - **Build the same image for local dev and remote deployment**: a `docker build` + volume-mount for live-editing locally, the same image pushed and run remotely, rather than two divergent Dockerfiles. - **Compose/multi-service wiring is out of scope here**: once you have more than one container that needs to talk to each other, hand off to `integration` for the compose file and inter-node comms plan. -
gpu-and-remote.md 5.1 KB
# GPU passthrough and remote/headless environments How to get an NVIDIA GPU into a Docker container, and how to think about display/visualization once the project is running on a headless or remote machine. Sources: [NVIDIA Container Toolkit docs](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/) (install guide + sample workload page), fetched directly; re-verify package versions before use, they move frequently. ## GPU passthrough: nvidia-container-toolkit (Linux only) GPU access inside a Docker container requires the **NVIDIA Container Toolkit** installed on the *host*; this is a Linux-only requirement (the official install guide covers Linux distributions; there is no first-party macOS host path). See [platform notes](../PLATFORM-NOTES.md) for Robium's observed platform tradeoffs. **Install (Ubuntu/Debian host, apt):** ```bash # 1. Configure NVIDIA's package repository (see the current install guide # for the exact repo-setup commands: they change; don't hardcode a key # URL from memory). # 2. Install the toolkit: sudo apt-get install -y nvidia-container-toolkit # 3. Wire it into the Docker daemon: sudo nvidia-ctk runtime configure --runtime=docker sudo systemctl restart docker ``` **Verify it works:** ```bash sudo docker run --rm --runtime=nvidia --gpus all ubuntu nvidia-smi ``` If `nvidia-smi` prints the host's GPU(s) from inside the container, the toolkit is wired up correctly. Do this verification step on any *new* remote host before assuming GPU workloads will just work there; it's a common source of "works locally, fails on the server" when the toolkit is missing or the Docker daemon wasn't restarted after configuration. **Running a GPU workload:** ```bash docker run --rm --gpus all my-gpu-image ``` `--gpus all` is the flag that matters day to day; `--runtime=nvidia` above is mainly for the one-time verification (once `nvidia-ctk runtime configure` has run, `--gpus` works without needing to also set `--runtime` explicitly on every invocation, but check current NVIDIA docs if you see different behavior on a given Docker version). **CUDA base image, host driver compatibility:** the CUDA toolkit version baked into the container image must be supported by the host's installed GPU driver. Check the host's supported CUDA version with `nvidia-smi` (top-right of its output) before picking a CUDA base image tag; this is one of the sharpest local/remote parity failure modes: a dev laptop with a newer driver building an image whose CUDA version the remote server's older driver can't run. See `examples/Dockerfile.gpu-ml` for a concrete base-image example (marked `status: unverified`; re-check the tag against [hub.docker.com/r/nvidia/cuda](https://hub.docker.com/r/nvidia/cuda) before using it in a real project). **WSL2 note:** the official install guide referenced above documents Linux distributions only and doesn't cover WSL2 specifically; if a remote/dev target is Windows+WSL2, verify the current WSL2-specific GPU support path against NVIDIA's docs separately rather than assuming the Linux steps apply unchanged. ## Headless / remote display strategy The instinct when a container needs to show something is X11 forwarding (`-e DISPLAY=$DISPLAY`, mounting `/tmp/.X11-unix`, `xhost +local:docker`). That works for **local-Linux-only, single-user** development, but it breaks down fast: - It doesn't work at all from a headless remote server with no X server. - It's fragile over SSH (needs `-X`/`-Y` forwarding, a working X client chain, and generally poor performance for anything beyond simple 2D UI). - It doesn't work from macOS/Windows hosts without extra tooling (XQuartz, VcXsrv) that itself isn't containerized or reproducible. **Default instead to headless containers + web-based visualization.** Run the container with no display requirement at all, and expose whatever needs visualizing over a web UI that any browser (local or remote, any OS) can reach. This is exactly what the `foxglove` skill covers; route to it instead of building out X11 forwarding, especially for: - Any remote/cloud GPU server (the common case for training or Isaac Sim work). - Cross-platform teams where not everyone is on Linux. - CI or automated runs where no display exists at all. Reserve X11/Wayland forwarding for a narrow case: local Linux development, one user, and a tool that genuinely has no web-based alternative, and treat it as a local-dev convenience, not something the deployed/remote environment depends on. ## Local vs remote parity for GPU + display - **Don't let "works on my GPU laptop" silently mean "works on the remote GPU server."** Confirm both the CUDA-version-vs-driver compatibility above and that `nvidia-container-toolkit` is actually installed and configured on the remote host; it's easy to develop for weeks locally without ever hitting this gap. - **Don't build the visualization story around a display that only exists locally.** If any part of the workflow assumes a local X server, it will silently fail (or need a completely different setup) the moment the project moves to a remote/headless host. Default to `foxglove` from the start so there's nothing to redo. -
gpu-cloud.md 2.8 KB
# Cloud-GPU environment boundary and hyperscaler quota gates When a project needs a real NVIDIA GPU that the dev machine doesn't have (Isaac Sim / Isaac Lab, GR00T, heavy CUDA training), the environment is a cloud GPU that you drive as a thin client. This reference owns the environment boundary and hyperscaler quota context. Use the `runpod` skill for RunPod inventory, provisioning, storage, networking, diagnostics, billing, and cleanup. Battle-tested on go2-locomotion (Isaac Lab, 2026-07-26..28). ## The one genuine exception to virtual-first local==remote Robium's default is virtual-first with a local==remote parity guarantee. Some GPU apps have **no local mirror on a Mac at all**: - Isaac Sim / Isaac Lab (Omniverse Kit) needs NVIDIA RTX + CUDA on Linux or Windows; there is no macOS path, and Docker can't pass through a GPU the Mac doesn't have. - Same class: GR00T and other heavy-CUDA training stacks. For these, the Mac is a **thin SSH/browser client** and the sim/training runs entirely on a cloud GPU. State this explicitly in the architecture brief; it's the sanctioned exception to the local==remote rule, not a parity failure to fix. ## Choose a provider after defining the environment contract First pin the image/CUDA contract, GPU floor, storage, network exposure, budget, and acceptance test. Then compare live provider capacity and quota. Do not state stock, price, or time-to-first-GPU from memory. RunPod-specific selection and paid-compute gates live in the `runpod` skill. ### GCP GPU quota gotchas (if you must use GCP) - The binding quota is the **global `GPUS_ALL_REGIONS`** metric, not the per-region one (e.g. `NVIDIA_L4_GPUS`). GCP enforces both; the lower one binds, so a per-region grant is useless if the global is still 0. - A well-formed `GPUS-ALL-REGIONS` increase request can be **auto-denied in under a second** on a young project with no billing history; Google Support confirmed this is account-standing, not a malformed request. Budget 2+ days of lead time or choose a provider whose current account and capacity checks satisfy the workload. - Diagnose current quota programmatically via the Cloud Quotas API (`quotaPreferences`). ## Provider handoff - RunPod inventory, exact GPU/datacenter selection, network volumes, Pod creation, proxy/SSH behavior, interactive diagnostics, billing, and cleanup: use the `runpod` skill and verify its current official sources. - Google Cloud Run deployment: use the `cloud-run` skill. - Framework-specific cloud-image and runtime mechanics stay with the framework skill, such as `isaac-lab` or `lerobot`. The environment contract remains provider-independent: a pinned image or lock, explicit CUDA/architecture floor, no host-only paths, headless operation, durable evidence, and a documented exception when no local mirror exists. -
robot-networking.md 4.8 KB
# Robot networking for real-robot bring-up Getting a laptop (usually a Mac) onto a physical robot's network well enough to SSH in, reconfigure it, and run ROS 2 against it. This is the messy, pre-virtual layer: before any container or lockfile matters, you have to reach the robot at all. Battle-tested on a real TurtleBot 4 (tb4-teleop, 2026-07-24/25). Everything here is about *host/LAN plumbing*; the parity mechanics for the environment that runs on top live in the other references. ## Headless first contact on no shared network When the robot ships headless and there's no shared Wi-Fi yet, cable a direct Ethernet link (USB-Ethernet on the Mac) and find the robot over IPv6 link-local: ```bash ping6 -c 4 ff02::1%en5 # all-nodes multicast on the direct iface ndp -a # read the neighbor table for fe80::... entries ssh ubuntu@fe80::...%en5 # SSH the link-local addr, scoped to the iface ``` **IPv6 link-local is flaky for unicast SSH.** Multicast ping (`ff02::1`) answers reliably, but the `ndp -a` entries expire and drop under load; an `apt` install over that SSH will stall out. The stable fix is to add an IPv4 alias in the robot's own static subnet and SSH *that* instead. If the robot's eth0 is statically `192.168.185.3`: ```bash sudo ifconfig en5 inet 192.168.185.10 netmask 255.255.255.0 alias ssh ubuntu@192.168.185.3 ``` ## Reconfiguring the robot's Wi-Fi via netplan On Ubuntu the Wi-Fi config lives at `/etc/netplan/50-wifis.yaml` (NetworkManager renderer). The file is marked do-not-edit, but a direct edit followed by `netplan generate` / `netplan apply` works. Keep it `chmod 600`; netplan warns when the file is world-readable. Applying the change bounces eth0, which will kill the very SSH session you issued it from. Detach the apply so it survives the bounce: ```bash sudo chmod 600 /etc/netplan/50-wifis.yaml sudo netplan generate sudo nohup netplan apply >/tmp/netplan.log 2>&1 & ``` With `autoconnect: yes` (NetworkManager default) the new Wi-Fi persists across reboots. ## DDS multicast, NAT, and where the bridge must run - **DDS multicast discovery does NOT cross NAT.** If you cable the Mac to a router's WAN port and turn on macOS Internet Sharing, the Mac ends up *behind* NAT and ROS 2 sees **zero topics**: discovery multicast never reaches the robot's LAN. The teleop host must sit on the robot's own LAN subnet, not behind a NAT boundary. - **In the 2026-07-24/25 TurtleBot 4 + Docker Desktop trial, the Linux VM did not discover DDS participants on the physical robot LAN.** Running the ROS↔browser bridge on the robot and connecting from the Mac over TCP was the validated resolution for that setup. Docker Desktop networking modes, RMWs, and LAN topologies change; probe bidirectional reachability and ROS discovery before generalizing this result or choosing bridge placement. See the foxglove skill for the bridge side. ## macOS Internet Sharing failure modes - **Sharing from an iPhone-hotspot uplink won't activate.** `bridge100` (192.168.2.1) never comes up. Worse, the half-on state *breaks the direct cable*: the robot still answers IPv6 multicast ping but unicast SSH times out. Turning Internet Sharing OFF restores the cable. (For a Mac cabled to a robot LAN losing its own internet, see [platform notes](../PLATFORM-NOTES.md), a separate issue.) ## DHCP and MAC-address gotchas on the shared SSID - **Two DHCP servers behind one SSID hand out different subnets on the same L2.** A Wi-Fi extender running its own DHCP will put the robot and the laptop on different subnets even though they share one SSID; they become mutually unreachable. Fix by pinning static IPs on both: - netplan: `dhcp4: false` plus explicit `addresses:`, `routes:`, and `nameservers:`. - or NetworkManager: `nmcli con mod <name> ipv4.method manual` (with `ipv4.addresses` / `ipv4.gateway` / `ipv4.dns`). Both persist across reboots. - **Wi-Fi MAC randomization hides the hardware MAC from the router's DHCP table.** The table shows a randomized `a8:e2:91:...` address, not the hardware `d8:3a:dd:...`. Don't try to find the robot by grepping for its hardware MAC; identify it by hostname / successful SSH, or sidestep the whole problem with static IPs. ## Validating a camera panel on a Mac (no V4L2) macOS Docker cannot pass a USB webcam into a container; V4L2 is Linux-only, so the container has no `/dev/video*` to bind. To exercise an MJPEG video panel locally on a Mac without the real camera, serve a synthetic multipart stream: - Serve a local `multipart/x-mixed-replace` source (e.g. an ffmpeg `testsrc`) that a browser `<img>` will render as a live MJPEG feed. - ffmpeg's built-in `-listen` HTTP server sends `application/octet-stream`, which a browser `<img>` will **not** render; you need a proper multipart server in front, not raw ffmpeg `-listen`. -
uv-patterns.md 4.9 KB
# uv patterns How to set up and run a pure-Python robium project with [uv](https://docs.astral.sh/uv/). This is the default for any project that doesn't need ROS 2 or other system-level dependencies; see the uv choice in `SKILL.md`. Sources: [uv docs](https://docs.astral.sh/uv/), fetched via the `ctx7` documentation tool (`astral-sh/uv`) rather than from memory; re-verify against current docs before relying on exact flag behavior. ## Project setup ```bash uv init my-project # scaffolds pyproject.toml, a src/ layout, .gitignore cd my-project uv add numpy torch # adds runtime dependencies, updates pyproject.toml + uv.lock uv add --dev pytest ruff # adds a `dev` dependency group ``` `pyproject.toml` after a couple of `uv add` calls looks like: ```toml [project] name = "my-project" version = "0.1.0" requires-python = ">=3.11" dependencies = [ "numpy>=1.26", "torch>=2.3", ] [dependency-groups] dev = ["pytest>=8.0", "ruff>=0.6"] ``` See `examples/pyproject-uv.toml` for a complete minimal file. ## Running things: always through `uv run` ```bash uv run python train.py uv run pytest uv run ruff check . ``` `uv run` resolves and syncs the project's `.venv` automatically before running the command; there is no separate "activate the venv" step to forget or get wrong. This is why the key directive is "never `pip install` into system Python": `uv run` (and `uv sync`) already give you an isolated, reproducible environment for free, so there is no reason to fall back to a global install. If you do want an activated shell, `uv venv` creates `.venv` explicitly and you can `source .venv/bin/activate` as usual, but prefer `uv run` for scripts and CI since it doesn't depend on shell state. ## `uv sync` vs `uv pip install` - **`uv sync`** (project-mode) makes the environment match `pyproject.toml` + `uv.lock` exactly: installs missing packages *and* removes anything not declared. This is the reproducibility guarantee: `uv sync` on two different machines with the same lockfile produces the same environment. - **`uv pip install`** (pip-compatible mode) behaves like `pip install`: additive only, no lockfile awareness by default. Reach for it only for one-off/ad-hoc installs (e.g. pre-installing a build dependency like `torch`/`setuptools` before a package that needs it at build time), not as the primary install path for a project. For robium projects, default to `uv sync` / `uv run` as the primary workflow; treat `uv pip install` as an escape hatch, not the norm. ## Lockfiles Commit `uv.lock` to the repository. It pins every resolved dependency (including transitive ones) to an exact version, which is what makes "works on my machine" become "works everywhere"; this is the uv half of the local == remote runtime contract from `SKILL.md`. Re-run `uv lock` (or `uv sync`, which updates the lock as needed) after changing `pyproject.toml`, and commit the updated lockfile in the same change. ## Pinning the Python version ```bash uv python pin 3.11 ``` Writes a `.python-version` file; `uv run`/`uv sync` then provision and use that exact interpreter version (downloading it if needed, unless `UV_PYTHON_DOWNLOADS=0`). Pin explicitly rather than relying on "whatever Python happens to be on this machine"; another local/remote parity point. ## When `--system` / `UV_SYSTEM_PYTHON` is acceptable The "never `pip install` into system Python" directive has exactly one sanctioned exception: inside a **disposable container build stage**, where "system Python" means the container's own throwaway Python, not a host machine's. Two legitimate cases: - **CI runners that are themselves ephemeral** (a fresh container per job): setting `UV_SYSTEM_PYTHON=1` for the whole job lets `uv pip install` target the runner's Python directly, since there's no persistent host to pollute. - **A Docker build stage that installs directly into the image's Python** rather than creating a nested venv; acceptable *only* when that stage's entire filesystem is the deliverable (i.e., you're not also using that Python for anything else). Prefer the multi-stage venv pattern in `references/docker-patterns.md` when you have a choice; it keeps the Dockerfile identical in spirit to the non-Docker uv workflow. Never use `--system` on a developer's laptop or on a long-lived server's bare OS Python; that is exactly the case the directive exists to prevent. ## When to graduate from uv to Docker Stop trying to make uv alone carry a project once any of these become true: - The project needs ROS 2 or another apt/system package that isn't Python-installable. - The project depends on a specific OS/kernel feature (e.g. certain GPU driver interactions, real-time kernel patches). - You need the *exact same OS base*, not just the same Python packages, reproduced on another machine. At that point, move to `references/docker-patterns.md`, and keep using uv *inside* the container for the Python-dependency layer; the two are complementary, not alternatives, once you're in Docker.
-
-
evals.yaml 278 B
triggers: positive: - phrase: should this Python robotics project use uv or Docker - phrase: make the local and remote CUDA environments reproducible negative: - phrase: find an available RunPod GPU in the same region as my volume expect: runpod tasks: [] -
PLATFORM-NOTES.md 2.2 KB
# Environment platform notes These observations explain choices that can look arbitrary without their measured conditions. They are evidence, not universal performance claims. - **ROS 2 on macOS:** use Linux or a container for the normal supported ROS 2 workflow. Gazebo by itself may run natively, but the full ROS integration inherits this boundary. - **Apple Silicon acceleration:** Docker Desktop does not pass Metal/MPS into a Linux container. In Robium's `manip-trial` and `vla-trial`, SmolVLA inference measured about 0.55 seconds per forward pass with host-native MPS and about 9 seconds on CPU in Docker or Cloud Run, roughly a 17x difference. For an ML policy on a Mac, measure CPU latency before choosing Docker. A trivial host dependency such as `ffmpeg` may be a better documented exception than losing the accelerator. - **arm64 slim images:** a dependency without an arm64 manylinux wheel falls back to a source build. In the 2026-07-15 `manip-trial`, `pymunk` required a compiler and failed with `gcc` absent. Install the build toolchain in a build stage or choose a base that contains it; do not assume all architectures have the same wheels. - **GPU containers:** NVIDIA Container Toolkit is a Linux-host dependency. Docker on macOS cannot create an NVIDIA accelerator path. Some applications, including Isaac Sim/Lab and CUDA-heavy training, therefore use the Mac only as an SSH/browser client and run on a compatible remote GPU machine. - **Cold rebuild claims:** `docker compose down --rmi local` removes local images but not the BuildKit/buildx layer cache. Robium's 2026-07-11 `nav-trial` used an additional builder-cache prune to test a genuinely cold build. Cache removal is destructive to build performance, so use it only when the test explicitly needs that evidence. - **Mac plus private robot LAN:** in the 2026-07-24 `tb4-teleop` setup, macOS preferred a wired no-internet robot LAN over Wi-Fi for the default route. Moving Wi-Fi above the USB LAN in Network Service Order restored internet while the robot subnet continued over the cable. Read [robot networking](references/robot-networking.md) before generalizing that topology. -
SKILL.md 3.5 KB
--- name: environments description: Set up reproducible robotics environments with uv, Docker, or GPU hosts. --- # Environments Define the runtime contract before writing around machine-specific accidents. Local and remote runs should differ only where the hardware genuinely differs. ## Choose the smallest sufficient boundary - **uv:** pure-Python robotics, ML, data, and tooling without required system packages. Commit the lockfile and run project commands through `uv run`. - **Docker:** ROS 2, native libraries, apt packages, an exact Linux userspace, or deployment as a container. - **Docker plus uv:** use Docker for the system layer and a project environment for substantial Python dependencies; do not turn the image's system Python into an untracked package set. - **Remote GPU host:** use when the workload has no viable local hardware path. Record this as a deliberate exception to local/remote symmetry. Preflight the actual machine before deciding. Confirm architecture, OS, Docker daemon, accelerator, driver, disk, and available Python tooling. Use the Robium doctor if present; fall back to direct probes when it is not. ## Keep the contract reproducible - Pin Python and dependency resolution with a lockfile, or pin the container base and build inputs. Avoid `latest` for reproducible applications. - Keep dependency declarations in one source of truth; do not preserve manual installation steps as hidden prerequisites. - Match container CUDA/runtime requirements to the target host driver rather than the developer laptop. - Design remote work as headless first. Use web visualization instead of making X forwarding part of the normal workflow. - Test the same entry command in the target environment and prove important hardware, file, device, network, and display assumptions. - Before claiming first-run reproducibility, run the complete setup from a clean project copy with the relevant package, model, and asset caches empty. A warm development checkout can hide broken fetch and bootstrap behavior. ## Go deeper only when needed - Pure Python and lockfiles: [uv patterns](references/uv-patterns.md). - ROS/system images, build layout, and parity: [Docker patterns](references/docker-patterns.md). - NVIDIA passthrough and headless operation: [GPU and remote](references/gpu-and-remote.md). - Real-robot LAN, Wi-Fi, DDS/NAT, and Mac host issues: [robot networking](references/robot-networking.md). - Workloads that can exist only on cloud GPUs: [GPU cloud](references/gpu-cloud.md), then the relevant provider skill for provisioning. - macOS, Apple Silicon, arm64, and cold-build evidence: [platform notes](PLATFORM-NOTES.md). - Use the bundled examples only as starting shapes; verify all tags and install steps against current upstream documentation. Cross into `integration` when multiple modules, containers, or transports must be wired together. Cross into a deployment skill only after the image and runtime contract work locally or in an equivalent target environment. ## Done - A fresh machine can reproduce the environment from committed inputs. - The same documented command starts the workload locally and remotely, except for named hardware flags. - GPU, devices, network, files, and display behavior are verified on the target. - Current details match official [uv](https://docs.astral.sh/uv/), [Docker](https://docs.docker.com/), ROS image, and [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/) documentation.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.