Reference
Runtime & Environment
This page describes what happens when an Airfield container runs: which container engine is used, how the container is wired to the host, what gets mounted where, and the environment variables that change any of it. The CLI Reference covers commands and file schemas; this page covers behavior.
Container engines
Airfield targets Docker and works with engines that present a Docker-compatible CLI:
- Docker is the default on Linux.
- Podman, Apptainer, and Singularity are detected when the
dockercommand onPATHresolves to one of them. Podman and Buildah do not support BuildKit cache mounts, so builds on those engines use a compatibility-mode Dockerfile automatically. - Apple Silicon Macs use Apple’s
containertool instead of Docker.airfield system setupinstalls and starts it.
If a build fails with invalid mount type "cache", Airfield regenerates the
Dockerfile without cache mounts and retries on its own.
Networking and IPC
On Docker, every run, cmd, and shell container starts with
--network=host and --ipc=host. This is deliberate: ROS 2 nodes in different
containers, and nodes running directly on the host, discover each other over
DDS exactly as if they were plain processes on one machine. You do not need to
configure discovery servers, port mappings, or a shared Docker network, and
tools on the host (ros2 topic echo, rviz2) see topics published from any
container.
The trade-off is standard for host networking: containers share the host’s network namespace, so two containers cannot bind the same port.
What gets mounted
Each container receives:
- The package source, mounted at
/home/<user>/workspace/src/<package-name>. Edits on the host appear in the container immediately; nothing is copied into the image. - The shared colcon workspace —
build/,install/, andlog/from<project>/.airfield/workspaceon the host, mounted at~/workspacein the container. Containers run with--rm, so without this the workspace would be discarded every time a pane exits. Sharing it is what makes build once, launch many work, and it is also what gives the build lock below a file that every container can see. The workspace is per project, so two projects that each contain a package of the same name never share oneinstall/. This is automatic and needs no configuration; see Shared colcon workspace for the host path, the fallback for standalone packages, and how to relocate or disable it. - Peer package sources. A dependency with no manifest that exists as a
package under
packages/is treated as a source dependency: its source is mounted alongside yours socolcon build --packages-up-tocan compile it first. Message packages shared between components typically resolve this way. .airmounts. Extra host paths from the project’s.airfile, then the package’s, each mounted at the same absolute path inside the container. Missing paths are skipped with a warning.- Devices and groups declared in
airfield.yaml(devices,group_add). A declared device that is absent on the host is skipped with a warning rather than failing the run, so the same config works on machines with and without the hardware attached.
The working directory defaults to the source mount; default_workdir in
airfield.yaml overrides it.
Containers created by one image are independent. Two panes running the same package get two containers from the same image, with the same mounts.
The first-run build
Images built for a ROS package carry a small entry script at
/opt/airfield-entry.sh. Before running your command, it checks whether the
package is already present in the workspace install/ directory:
- If yes (or the package has no colcon source, as with tool-only packages), it goes straight to your command.
- If not, it runs
colcon build --packages-up-to <name>first, with anycolcon_argsfromairfield.yamlappended.
Because install/ lives on the host, that check sees work done by earlier
containers in the same project: the first pane to need a package compiles it,
and every later pane finds it built and goes straight to launching.
Two protections make this safe on small boards:
- Concurrent containers serialize on a file lock in the shared
log/directory, so only one colcon build runs at a time even when a plan starts many panes at once. This works because every container locks the same file; pointAIRFIELD_WORKSPACEatnoneand each container gets a private copy instead, so no pane ever waits for another and a plan’s panes all compile simultaneously. - Compile parallelism defaults to one job per core, capped at roughly one job
per 4 GB of RAM. A Jetson gets fewer parallel jobs than a workstation, which
prevents out-of-memory failures during first launch. Set
MAKEFLAGSorCMAKE_BUILD_PARALLEL_LEVELto override the defaults.
Packages without a ros_distro have no entry script; their commands run in a
plain login shell.
GPU access
GPU passthrough depends on the platform:
- NVIDIA Jetson boards (detected via
/etc/nv_tegra_release) always get the NVIDIA runtime, full driver capabilities, the Argus camera socket, and the/dev/video*nodes. CSI cameras, EGL, and CUDA work inside containers without configuration, on every checkout of the project. - Other hosts opt in by setting
AIRFIELD_TORCH_INSTALL_TARGET=gpu(orTORCH_INSTALL_TARGET=gpu). On Docker this adds--gpus all; on Podman it wires up the OCI hooks instead. When Airfield detects an NVIDIA GPU on the host, it selects GPU mode by itself and suggests a matching CUDA wheel tag for PyTorch installs.
Dependency manifests can declare host_dependencies (an NVIDIA driver with a
minimum version, for example). Airfield evaluates them before the image build:
interactive sessions are prompted when a required host dependency is missing,
and non-interactive runs (CI) fall back to CPU installs rather than failing.
Teardown
Airfield names each container it starts (airfield-run-<pid>-<id>) and runs
it with --rm. When the airfield process receives Ctrl-C, SIGTERM, or SIGHUP
(a closed terminal, or tmux kill-session), it stops its own container
explicitly instead of trusting the in-container process tree to forward
signals. This is why airfield project down leaves no orphaned containers:
killing the tmux session signals each pane, and each pane stops its container
on the way out.
Processes started under nohup keep their SIGHUP immunity; Airfield does not
override it.
After a hard crash (SIGKILL, power loss) where no handler could run, sweep the leftovers:
airfield project down --prune
Environment variables
| Variable | Effect |
|---|---|
AIRFIELD_NO_PULL | Skip docker build --pull, so a package can build FROM a locally built base image that exists in no registry (a custom L4T base, for example). Often exported in a plan’s pre_window or a wrapper script. |
AIRFIELD_PACKAGES_REPO | URL of the shared packages repository. Lets forks, mirrors, and air-gapped sites point at their own manifests. Default: https://github.com/airfield/packages.git. |
AIRFIELD_REPO | GitHub owner/name slug used by system update and the update check. Set it when running a fork. |
AIRFIELD_WORKSPACE | Host location of the shared colcon workspace, overriding the default <project>/.airfield/workspace. Point several projects at one path to share a build tree; set it to none to opt out entirely, giving every container a throwaway workspace and removing the lock that serializes concurrent builds. |
AIRFIELD_TORCH_INSTALL_TARGET | gpu or cpu. Selects GPU passthrough at run time and the wheel flavor for manifests that install PyTorch. TORCH_INSTALL_TARGET is accepted as a fallback name. |
AIRFIELD_TORCH_VERSION, AIRFIELD_TORCH_GPU_WHL_TAG | Version and CUDA wheel tag passed to the image build for PyTorch manifests. Airfield sets them itself from a torch==<version> pin and the detected driver; set them manually to override. |
AIRFIELD_DEP_<NAME>_VERSION | Set automatically for every exact pin (name==1.2.3) in airfield.yaml, for manifests that read a version at install time. |
AIRFIELD_FORCE_DOCKER_CACHE_MOUNTS, AIRFIELD_DISABLE_DOCKER_CACHE_MOUNTS | Force BuildKit cache mounts on or off, overriding engine detection. Useful in CI and when troubleshooting builds. |
MAKEFLAGS, CMAKE_BUILD_PARALLEL_LEVEL | Override the first-run build’s computed compile parallelism. |
IN_AIRFIELD_CONTAINER | Set to 1 inside every Airfield image. The CLI checks it to refuse container-in-container operations. |
Airfield’s own cache and config live under the XDG base directories
(~/.cache/airfield, ~/.config/airfield by default) and honor
XDG_CACHE_HOME and XDG_CONFIG_HOME.