RobotForge
Published·~14 min

Docker and devcontainers: your robot's portable OS

Docker is the standard way to ship a robot's software stack in 2026. The 20 lines of Dockerfile that get ROS 2, your code, and your dependencies running anywhere — laptop, robot, CI.

by RobotForge
#foundations#docker#devcontainer#ros2

Every serious robot in 2026 runs containerized software. The reason is mundane and powerful: dependencies are pinned, the same image runs on the developer's laptop and the robot's onboard computer, and CI tests bit-identical builds. Here's the minimum Docker fluency a roboticist needs.

What Docker actually is

Strip the marketing: Docker is a way to package a Linux filesystem (with your code, dependencies, and OS libs) into an image that any Linux machine can run as a process. The process is called a container. Think of it as "a tarball that boots."

  • Image — the static filesystem. Built from a Dockerfile, immutable.
  • Container — a running instance of an image. Mutable until killed.
  • Volume — a directory on the host mounted into the container, persistent.

The minimum Dockerfile for a ROS 2 robot

FROM osrf/ros:jazzy-desktop

# Install your dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    python3-pip ros-jazzy-rmw-cyclonedds-cpp git \
 && rm -rf /var/lib/apt/lists/*

# Set the default RMW (DDS implementation)
ENV RMW_IMPLEMENTATION=rmw_cyclonedds_cpp

# Workspace
WORKDIR /workspace
COPY . /workspace/src/my_robot

# Build the workspace
RUN /bin/bash -c "source /opt/ros/jazzy/setup.bash && \
    cd /workspace && colcon build --symlink-install"

# Source the overlay on every shell
RUN echo "source /opt/ros/jazzy/setup.bash" >> /root/.bashrc && \
    echo "source /workspace/install/setup.bash" >> /root/.bashrc

CMD ["/bin/bash"]

That's a complete robot environment. Build with:

docker build -t my-robot .
docker run -it --rm --net=host my-robot

Inside the container: ros2 run my_robot ... works. On any Linux host. With identical behavior.

The flags you'll actually need

For robotics, plain docker run rarely suffices. You'll add:

  • --net=host — lets ROS 2 multicast discovery reach other nodes on the same network. Without it, your container can't see the robot.
  • -v $(pwd):/workspace/src/my_robot — mount your source so edits on the host appear inside without rebuilding.
  • --device=/dev/ttyUSB0 — pass a USB serial device through (Arduino, lidar).
  • --gpus all — for ML workloads (requires nvidia-container-toolkit on the host).
  • -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix — show RViz/Gazebo windows on the host's X server (Linux).
  • --privileged — full hardware access. Avoid in production; use for prototypes only.

Devcontainers: the developer experience layer

A devcontainer is a Docker image plus a VS Code config that says "open this folder, but inside the container, with these extensions and these mounts." The dev experience is identical to local, the runtime is identical to production.

Add .devcontainer/devcontainer.json to your repo:

{
  "name": "ROS 2 Jazzy",
  "image": "osrf/ros:jazzy-desktop",
  "remoteUser": "ubuntu",
  "features": {
    "ghcr.io/devcontainers/features/common-utils:2": {}
  },
  "customizations": {
    "vscode": {
      "extensions": ["ms-iot.vscode-ros", "ms-python.python", "ms-vscode.cpptools"]
    }
  },
  "runArgs": ["--net=host"],
  "mounts": [
    "source=${localWorkspaceFolder},target=/workspace,type=bind"
  ],
  "postCreateCommand": "cd /workspace && colcon build --symlink-install"
}

Open VS Code → "Reopen in container." Done. Same on every developer's machine. Same on CI. Same on the robot.

Multi-stage builds for production

Production images should be small. The development image with build tools, headers, and ROS desktop is 6 GB. The runtime image needs only your built binaries and ROS runtime — about 800 MB. Use multi-stage:

FROM osrf/ros:jazzy-desktop AS build
WORKDIR /ws
COPY . src/my_robot
RUN /bin/bash -c "source /opt/ros/jazzy/setup.bash && \
    colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release"

FROM ros:jazzy-ros-base AS runtime
COPY --from=build /ws/install /opt/my_robot
ENV LD_LIBRARY_PATH=/opt/my_robot/lib:${LD_LIBRARY_PATH}
CMD ["/bin/bash", "-lc", "source /opt/ros/jazzy/setup.bash && \
     source /opt/my_robot/setup.bash && \
     ros2 launch my_robot bringup.launch.py"]

One stage to build, one stage to run. Build artifacts copied across, source and tools dropped. The final image ships to the robot.

Networking gotchas (the #1 source of "it works locally")

ROS 2 discovery uses UDP multicast on the local subnet. Inside Docker:

  • Default bridge networking blocks multicast between containers and the host. Use --net=host on Linux for development.
  • On Mac and Windows, Docker Desktop runs Linux in a VM, and --net=host doesn't work the same way. Use ROS_LOCALHOST_ONLY=1 and stick to in-container nodes.
  • Multi-host robotics needs a non-multicast RMW — Cyclone DDS with the right config, or Zenoh — and shared ROS_DOMAIN_ID.

Image registries and CI

Push your image to a registry (Docker Hub, GitHub Container Registry, ECR) so CI and robots can pull the same artifact. Tag with both latest and a SHA so you can roll back:

docker build -t ghcr.io/myorg/my-robot:$(git rev-parse --short HEAD) -t ghcr.io/myorg/my-robot:latest .
docker push ghcr.io/myorg/my-robot --all-tags

The robot's systemd unit pulls and runs the latest image on boot. Updates are docker pull && systemctl restart my-robot. Rollback is one tag change.

When to not use Docker

  • Hard real-time control loops. Docker's overhead is small but nonzero. For 1 kHz+ control, run on bare metal with PREEMPT_RT.
  • GPU-heavy ML inference where every millisecond counts. Container overhead is small, but pinned configs sometimes win.
  • Tiny embedded targets. ESP32 doesn't run Docker. Build native. Containers are for Linux machines (Pi, Jetson, x86 robots).

Exercise

Take any ROS 2 package, add the Dockerfile above, build it, and run a node inside. The first time you watch turtlesim spin up inside a container is the moment "deployment" stops being scary.

Next

Linear algebra refresher (robotics edition) — back to the math foundations. Or jump to the next ROS 2 lesson if you want hands-on momentum.

Comments

    Sign in to post a comment.