ROS 2 robotics with nix-ros-overlay

Updated

September 4, 2026

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.

Note

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
Warning

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)

Classic 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 jazzy

Prefer 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-overlay

Project 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 --help

direnv

echo 'use flake' > .envrc
direnv allow

Same 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 loaded

Practical approach:

nix repl
# :lf github:lopsided98/nix-ros-overlay
# then explore outputs / pkgs construction from the flake

Or 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 | head

Why 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):

  1. Overlay on the overlay — add your package set on top of nix-ros-overlay.overlays.default
  2. buildRosPackage / distro helpers — when exported by the overlay version you pin
  3. 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 rviz2

Put 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 install ROS 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' | head

Security 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

  1. Enable ros.cachix.org on your Nix install.
  2. nix develop github:lopsided98/nix-ros-overlay/master#example-ros2-desktop-jazzy (or current equivalent).
  3. Run talker/listener launch; capture a screenshot or log snippet in lab notes.
  4. Record closure size: how painful was the first download?

Lab B — Project flake

  1. nix flake init --template github:lopsided98/nix-ros-overlay in ~/lab/nixos-book/ros2-env.
  2. Trim env to ros-core (+ one demo package if available).
  3. Add direnv + commit flake.lock.
  4. colcon build an empty package skeleton; ros2 pkg list shows it after source.

Lab C — Distro compare (optional)

  1. Define devShells.humble and devShells.jazzy.
  2. Document one package that exists in one and fails in the other.
  3. Write a team policy: “default teaching distro = …”.

Lab D — Robot appliance sketch (optional)

  1. In a monorepo flake, add nixosConfigurations.ros-bot sharing the overlay.
  2. System packages only runtime nodes; develop still uses devShells.default.
  3. Explain why the robot image is smaller than the desktop shell.

Checkpoint

  • You can explain why nixpkgs.follows the overlay
  • Caches configured for multi-user Nix
  • A project flake enters ros2 successfully
  • You know underlay vs colcon workspace
  • Graphics plan for RViz on your host OS
  • You did not dump desktop-full into environment.systemPackages without 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

When this notebook and upstream disagree, trust the overlay README and your flake.lock.