ROS 2 robotics with nix-ros-overlay
ROS 2 robotics with nix-ros-overlay
Goal: Stand up a reproducible ROS 2 development environment with nix-ros-overlay—flakes, binary caches, distro selection (Humble/Jazzy/…), project devShells, and a path toward packaging your own nodes—without being locked to Ubuntu’s deb repos.
This chapter is about the package manager + overlay, not a full robotics curriculum. It assumes you know (or will learn elsewhere) ROS 2 concepts: nodes, topics, DDS, workspaces, colcon. Here you learn how Nix makes those tools pinable and portable on NixOS, Fedora, or any Linux with Nix.
Why this chapter exists
Classic ROS 2 setup:
Ubuntu LTS → apt ROS repo → /opt/ros/<distro> → source setup.bash
That works—and couples your OS, ROS distro, and system Python forever. Teams then invent Docker stacks so CI can still build.
nix-ros-overlay answers a different question:
| Want | Approach |
|---|---|
| Same ROS graph on Fedora / NixOS / CI | Overlay + flake lock |
| Multiple ROS distros without dual-boot | Separate devShells |
| Honest dependency graph | Nix store + hydra status badges |
Avoid apt pollution on daily driver |
Project shell only |
It is an overlay on a specific nixpkgs pin (the overlay follows its own nixpkgs). Treat it like a specialized ecosystem helper—related to language ecosystems and overlays, not like environment.systemPackages = [ ros-desktop-full ] on every laptop forever.
Mental model
nix-ros-overlay flake
│
┌───────────────┼───────────────┐
▼ ▼ ▼
overlays.default pinned nixpkgs rosPackages.<distro>
│ │ │
└──────► pkgs = import nixpkgs { overlays = [ … ]; }
│
▼
buildEnv { underlay = true; paths = [ ros-core … ]; }
│
▼
mkShell / nix develop (+ colcon, clang, …)
| Piece | Role |
|---|---|
| Overlay | Injects rosPackages.* and ROS build helpers into pkgs |
rosPackages.<distro> |
Attrset of packages for Humble, Jazzy, Kilted, Lyrical, Rolling, … |
buildEnv { underlay = true; … } |
Composes a ROS underlay environment (AMENT/COLCON paths) for shells |
colcon |
Workspace build tool (non-ROS nixpkgs package, usually) |
| Binary caches | Avoid compiling the world; still expect occasional local builds |
ROS 1 / Gazebo Classic were removed from master after EOL (2025). Historical work lives on branches such as ros1-25.05—this chapter is ROS 2 only.
Status and expectations (be honest)
Upstream tracks build health per distro on Hydra (see the README badge table on lopsided98/nix-ros-overlay).
What generally works:
- Large fraction of distro packages on x86_64-linux / aarch64-linux
- Functional dev environments via
nix-shell/nix develop
- Generated package defs via superflore
What still hurts:
- Not every package builds (thousands of packages; 80–90% targets are aspirational and drop as distros age)
- Free ros.cachix.org capacity is limited—packages may be missing or evicted
- Graphics (RViz, Gazebo/Gz) on non-NixOS needs nixGL or nix-system-graphics
- macOS is not a first-class story
Budget time and disk. First entry into a desktop-class shell can pull tens of gigabytes when substitutes miss. Prefer a slim ros-core shell before desktop / desktop-full.
Binary caches (do this first)
Official community cache (Cachix)
# Option A
cachix use ros
# Option B — nix.conf (user or system)
# substituters = https://cache.nixos.org https://ros.cachix.org
# trusted-public-keys = cache.nixos.org-1:… ros.cachix.org-1:dSyZxI8geDCJrwgvCOHDoAfOm5sV1wCPjBkKL+38Rvo=Public key commonly documented by the project:
ros.cachix.org-1:dSyZxI8geDCJrwgvCOHDoAfOm5sV1wCPjBkKL+38Rvo=
Experimental Attic cache (when Cachix is thin)
Upstream documents an experimental Hydra-fed cache (availability not guaranteed). Example from project docs (verify current values on the README before trusting):
extra-substituters = https://attic.iid.ciirc.cvut.cz/ros
extra-trusted-public-keys = ros:JR95vUYsShSqfA1VTYoFt1Nz6uXasm5QrcOsGry9f6Q=
On NixOS, prefer declarative:
# modules/common/nix-ros-cache.nix (sketch)
{
nix.settings = {
substituters = [
"https://cache.nixos.org"
"https://ros.cachix.org"
# "https://attic.iid.ciirc.cvut.cz/ros" # optional experimental
];
trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"ros.cachix.org-1:dSyZxI8geDCJrwgvCOHDoAfOm5sV1wCPjBkKL+38Rvo="
# "ros:JR95vUYsShSqfA1VTYoFt1Nz6uXasm5QrcOsGry9f6Q="
];
};
}Multi-user: trusted substituters belong in the daemon config (system), not only ~/.config/nix/nix.conf.
Fast path: try upstream examples (no project yet)
Flake develop (recommended)
# ROS 2 Jazzy desktop-class example (name may track upstream flake outputs)
nix develop \
--option extra-substituters 'https://ros.cachix.org' \
--option extra-trusted-public-keys 'ros.cachix.org-1:dSyZxI8geDCJrwgvCOHDoAfOm5sV1wCPjBkKL+38Rvo=' \
github:lopsided98/nix-ros-overlay/master#example-ros2-desktop-jazzyInside the shell:
ros2 --help
# Classic demo (if packages present in that env)
ros2 launch demo_nodes_cpp talker_listener_launch.xmlClassic nix-shell form
nix-shell \
-I nix-ros-overlay=https://github.com/lopsided98/nix-ros-overlay/archive/master.tar.gz \
--option extra-substituters 'https://ros.cachix.org' \
--option extra-trusted-public-keys 'ros.cachix.org-1:dSyZxI8geDCJrwgvCOHDoAfOm5sV1wCPjBkKL+38Rvo=' \
'<nix-ros-overlay/examples/ros2-desktop.nix>' --argstr rosDistro jazzyPrefer flakes + lock for anything you keep.
# Scaffold from upstream template
mkdir -p ~/lab/nixos-book/ros2-env && cd ~/lab/nixos-book/ros2-env
nix flake init --template github:lopsided98/nix-ros-overlayProject flake: ROS 2 underlay in a devShell
Critical rule: nixpkgs.follows = "nix-ros-overlay/nixpkgs".
If you pin book baseline nixos-26.05 independently, you will fight ABI/version skew. The overlay chooses a nixpkgs it builds against—follow it.
Minimal flake.nix
{
description = "ROS 2 lab environment via nix-ros-overlay";
inputs = {
nix-ros-overlay.url = "github:lopsided98/nix-ros-overlay/master";
# IMPORTANT: do not replace this with a free-floating nixos-26.05 input
nixpkgs.follows = "nix-ros-overlay/nixpkgs";
};
outputs = { self, nix-ros-overlay, nixpkgs }:
nix-ros-overlay.inputs.flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [ nix-ros-overlay.overlays.default ];
};
# Pick ONE distro per shell (or define multiple shells)
rosDistro = pkgs.rosPackages.jazzy; # or .humble / .kilted / .lyrical / .rolling
rosEnv = rosDistro.buildEnv {
underlay = true;
paths = with rosDistro; [
ros-core
# Grow deliberately:
# ros-base
# desktop
# demo-nodes-cpp
# rviz2
# teleop-twist-keyboard
];
};
in {
devShells.default = pkgs.mkShell {
name = "ros2-jazzy-lab";
packages = [
pkgs.colcon
pkgs.cmake
pkgs.gcc
pkgs.python3
rosEnv
# optional quality-of-life
# pkgs.rosPackages.jazzy.ros2cli # often already via env
];
shellHook = ''
echo "ROS 2 shell (nix-ros-overlay) — distro attrs under pkgs.rosPackages.*"
# AMENT/COLCON hooks are provided by buildEnv underlay; if a var is missing,
# check `env | rg -i 'ament|colcon|ros'` and upstream examples.
'';
};
# Optional second shell: minimal CI-ish
devShells.ci = pkgs.mkShell {
name = "ros2-ci";
packages = [
pkgs.colcon
(rosDistro.buildEnv {
underlay = true;
paths = with rosDistro; [ ros-core ];
})
];
};
});
# Flake-level nixConfig is honored by many CLIs; system trusted-keys still matter for multi-user.
nixConfig = {
extra-substituters = [ "https://ros.cachix.org" ];
extra-trusted-public-keys = [
"ros.cachix.org-1:dSyZxI8geDCJrwgvCOHDoAfOm5sV1wCPjBkKL+38Rvo="
];
};
}cd ~/lab/nixos-book/ros2-env
git init
# paste flake.nix
nix flake lock
nix develop
ros2 --helpdirenv
echo 'use flake' > .envrc
direnv allowSame direnv discipline as any other project shell—plus larger GC roots. Prune unused roots when disk hurts.
Choosing a ROS 2 distro attr
Names track ROS releases exposed by the overlay (check pkgs.rosPackages in nix repl when unsure):
| Distro attr (typical) | Role |
|---|---|
humble |
Older LTS-class; good for legacy robots |
jazzy |
Common current teaching/desktop target |
kilted / lyrical |
Newer releases as overlay adds them |
rolling |
Bleeding edge; expect breakage |
nix develop -c nix eval --raw --impure --expr \
'let pkgs = import (builtins.getFlake "git+file://'"$PWD"'").inputs.nixpkgs { system = "x86_64-linux"; overlays = [ (builtins.getFlake "git+file://'"$PWD"'").inputs.nix-ros-overlay.overlays.default ]; }; in builtins.concatStringsSep " " (builtins.attrNames pkgs.rosPackages)'
# Simpler once inside a shell that has pkgs: use nix repl with the overlay loadedPractical approach:
nix repl
# :lf github:lopsided98/nix-ros-overlay
# then explore outputs / pkgs construction from the flakeOr open upstream Hydra badges before pinning a distro for a class.
Workspace workflow (overlay underlay + your packages)
Nix supplies the underlay (system ROS packages). Your robot code often still uses a colcon workspace on top:
~/lab/robot_ws/
flake.nix # nix-ros-overlay shell
.envrc
src/
my_robot_bringup/
my_robot_desc/
nix develop
mkdir -p src && cd src
# git clone your packages …
cd ..
colcon build --symlink-install
source install/setup.bash
ros2 pkg list | headWhy not put everything in Nix immediately?
| Phase | Reasonable default |
|---|---|
| Week 1 | Overlay underlay + colcon for your packages |
| Later | Package stable internal libs with ROS Nix builders / overlays |
| CI | nix develop -c colcon build with lockfile |
Building every teammate’s WIP branch as pure Nix is optional maturity—not day-one tax.
Custom ROS packages (direction of travel)
Patterns used in the ecosystem (details evolve; check overlay helpers when you implement):
- Overlay on the overlay — add your package set on top of
nix-ros-overlay.overlays.default
buildRosPackage/ distro helpers — when exported by the overlay version you pin
- Upstream first — prefer nixpkgs for non-ROS deps; fix rosdep → nix name mappings when evaluation complains about
_unresolved_<dep>
Evaluation error pattern from upstream FAQ:
Function called without required argument "_unresolved_<dependency>"
Usually means rosdep lacks a nixos key for that dependency—map it to a nixpkgs package or package the dep.
Do not start by forking thousands of generated expressions; override the few packages you need.
Graphics: RViz, Gz, tools on non-NixOS
On NixOS, OpenGL/Vulkan integration is usually straightforward if drivers are set up.
On Fedora / Ubuntu + Nix, Nix-built GL apps often need a wrapper:
| Tool | Idea |
|---|---|
| nixGL | Wrap rviz2 with host GL |
| nix-system-graphics | System-oriented graphics story |
# Conceptual — install nixGL per its docs, then:
# nixGL rviz2Put the wrapper in shellHook or a small script in the flake so students don’t invent flags.
NixOS host integration (what belongs where)
| Layer | Recommendation |
|---|---|
| devShell (this chapter) | Daily robot development |
| HM packages | Maybe colcon/clangd—not full desktop ROS |
environment.systemPackages |
Avoid multi-GB ROS desktops system-wide |
| Robot appliance image | Separate nixosConfigurations.robot with only runtime nodes |
Runtime robot image sketch (conceptual):
# hosts/robot/configuration.nix — illustration only
{ pkgs, ... }: {
# Use same overlayed pkgs in the flake’s nixosSystem
environment.systemPackages = [
# minimal runtime env or specific nodes—not desktop-full
];
# networking, users, systemd services that launch ros2 launch …
}Wire the overlay once at flake level so devShell and robot image share the same pin.
Fedora dual-stack note
If you followed Nix on Linux:
- Keep desktop/GPU on Fedora
- Keep ROS in a project flake shell
- Add ROS caches to the daemon config
- Do not
dnf installROS debs and Nix ROS for the same workspace—pick one story per project
Troubleshooting
| Symptom | Direction |
|---|---|
| Massive local compiles | Caches not trusted by daemon; check nix show-config \| rg substituter |
nixpkgs version chaos |
Forgot nixpkgs.follows = "nix-ros-overlay/nixpkgs" |
_unresolved_* eval error |
Missing rosdep→nix mapping; package or map dependency |
| Package fails to build | Check Hydra; open issue; override; try another distro |
ros2 not found in shell |
buildEnv paths incomplete; compare upstream example env |
| RViz black window / GL error | nixGL / drivers; try non-NixOS graphics wrappers |
| Disk full | nix store gc; drop desktop-full shells; limit direnv roots |
| DDS / multicast weirdness | Host firewall/network; ROS_DOMAIN_ID; not always a Nix bug |
# Useful inspections
nix flake metadata
nix path-info -Sh .#devShells.x86_64-linux.default 2>/dev/null || true
env | rg -i 'ament|colcon|ros|rmw' | headSecurity and supply chain
- Overlay + ROS sources are a large trust surface—pin flake.lock and review updates
- Cachix/Attic keys: only add caches you intend to trust for substitutes
- Robot network: treat DDS discovery like any open LAN service
- Don’t bake robot secrets into store paths (same rule as HM)
Lab A — Hello underlay
- Enable
ros.cachix.orgon your Nix install.
nix develop github:lopsided98/nix-ros-overlay/master#example-ros2-desktop-jazzy(or current equivalent).
- Run talker/listener launch; capture a screenshot or log snippet in lab notes.
- Record closure size: how painful was the first download?
Lab B — Project flake
nix flake init --template github:lopsided98/nix-ros-overlayin~/lab/nixos-book/ros2-env.
- Trim env to
ros-core(+ one demo package if available).
- Add
direnv+ commitflake.lock.
colcon buildan empty package skeleton;ros2 pkg listshows it after source.
Lab C — Distro compare (optional)
- Define
devShells.humbleanddevShells.jazzy.
- Document one package that exists in one and fails in the other.
- Write a team policy: “default teaching distro = …”.
Lab D — Robot appliance sketch (optional)
- In a monorepo flake, add
nixosConfigurations.ros-botsharing the overlay.
- System packages only runtime nodes; develop still uses
devShells.default.
- Explain why the robot image is smaller than the desktop shell.
Checkpoint
- You can explain why
nixpkgs.followsthe overlay
- Caches configured for multi-user Nix
- A project flake enters
ros2successfully
- You know underlay vs colcon workspace
- Graphics plan for RViz on your host OS
- You did not dump
desktop-fullintoenvironment.systemPackageswithout reason
Further depth (this book)
| Topic | Chapter |
|---|---|
| Overlays mechanism | Overlays (light) |
| Project shells | direnv and devShells |
| Language packaging patterns | Ecosystems A/B |
| Caches | Binary caches |
| Fedora dual-stack | Nix on Linux |
| Reproducibility honesty | Reproducibility |
Upstream
- lopsided98/nix-ros-overlay — README, examples, Hydra badges
- ros.cachix.org — community substitutes
- ROS 2 docs — nodes/launch/DDS semantics
When this notebook and upstream disagree, trust the overlay README and your flake.lock.