ROS 2 Getting Started Guide 2026
Complete setup from zero to autonomous navigation. Covers Ubuntu 22.04/24.04, first node, Nav2, Docker DevContainer, and real hardware integration.
1. Installation
Set locale and add ROS 2 GPG key
Ensure UTF-8 locale and add the ROS 2 apt repository.
# Set UTF-8 locale locale # check for UTF-8 sudo apt update && sudo apt install locales sudo locale-gen en_US en_US.UTF-8 sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 export LANG=en_US.UTF-8 # Add ROS 2 GPG key sudo apt install software-properties-common curl -y sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg # Add ROS 2 repository echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
Install ROS 2 Jazzy (Ubuntu 24.04) or Humble (22.04)
Install the full desktop version including RViz2, simulation tools, and demos.
sudo apt update && sudo apt upgrade -y # Ubuntu 24.04 → ROS 2 Jazzy (LTS, supported until May 2029) sudo apt install ros-jazzy-desktop -y # Ubuntu 22.04 → ROS 2 Humble (LTS, supported until May 2027) # sudo apt install ros-humble-desktop -y # Development tools sudo apt install python3-colcon-common-extensions python3-rosdep python3-vcstool -y # Initialize rosdep sudo rosdep init && rosdep update
💡 Always install the latest LTS distro for your Ubuntu version. Jazzy on Ubuntu 24.04 is recommended for new projects in 2026.
Source the setup file and add to .bashrc
Source ROS 2 environment variables in every terminal session.
# Source for current terminal source /opt/ros/jazzy/setup.bash # Auto-source in every new terminal echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc source ~/.bashrc # Verify installation ros2 --version # should print: ros2 2.x.x (Jazzy)
Test with demo_nodes
Run the canonical talker/listener demo to verify your installation.
# Terminal 1: start talker ros2 run demo_nodes_cpp talker # Terminal 2: start listener ros2 run demo_nodes_cpp listener # Expected output in listener terminal: # [INFO] [1718000000.000000000] [listener]: I heard: [Hello World: 0] # [INFO] [1718000000.000000000] [listener]: I heard: [Hello World: 1]
2. First Package & Node
Create a colcon workspace, scaffold a Python package, and write your first publisher node.
Create a workspace
ROS 2 uses colcon workspaces. Create the standard src/build/install layout.
mkdir -p ~/ros2_ws/src cd ~/ros2_ws # Build empty workspace to initialize colcon build source install/setup.bash
Create your first package
Use ros2 pkg create to scaffold a Python package with a publisher node.
cd ~/ros2_ws/src ros2 pkg create my_robot_pkg \ --build-type ament_python \ --dependencies rclpy std_msgs
Write a publisher node
Replace the placeholder in my_robot_pkg/my_robot_pkg/my_node.py:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class MinimalPublisher(Node):
def __init__(self):
super().__init__('minimal_publisher')
self.publisher_ = self.create_publisher(String, 'robot_status', 10)
self.timer = self.create_timer(0.5, self.timer_callback)
self.i = 0
def timer_callback(self):
msg = String()
msg.data = f'Robot status: {self.i}'
self.publisher_.publish(msg)
self.get_logger().info(f'Publishing: "{msg.data}"')
self.i += 1
def main(args=None):
rclpy.init(args=args)
node = MinimalPublisher()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()Register the entry point and build
Add the console_scripts entry to setup.py, then build and run.
# In setup.py, add to entry_points:
entry_points={
'console_scripts': [
'my_publisher = my_robot_pkg.my_node:main',
],
},
# Build and run
cd ~/ros2_ws
colcon build --packages-select my_robot_pkg
source install/setup.bash
ros2 run my_robot_pkg my_publisher4. Docker & VS Code DevContainer
Containerize your ROS 2 development for reproducible, shareable environments.
Dockerfile for ROS 2 development
Use the official OSRF ROS 2 base image for reproducible environments.
FROM osrf/ros:jazzy-desktop # Install development dependencies RUN apt-get update && apt-get install -y \ python3-colcon-common-extensions \ python3-rosdep \ ros-jazzy-nav2-bringup \ ros-jazzy-slam-toolbox \ && rm -rf /var/lib/apt/lists/* # Initialize rosdep RUN rosdep update # Source ROS 2 in all shells RUN echo "source /opt/ros/jazzy/setup.bash" >> /root/.bashrc WORKDIR /ros2_ws COPY src/ src/ RUN bash -c "source /opt/ros/jazzy/setup.bash && \ rosdep install --from-paths src --ignore-src -r -y && \ colcon build"
.devcontainer/devcontainer.json for VS Code
Open your ROS 2 project in a VS Code DevContainer for one-command setup.
{
"name": "ROS 2 Jazzy Dev",
"image": "osrf/ros:jazzy-desktop",
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-iot.vscode-ros",
"ms-vscode-remote.remote-containers"
],
"settings": {
"python.pythonPath": "/usr/bin/python3",
"ros.distro": "jazzy"
}
}
},
"postCreateCommand": "rosdep update && pip install pytest-ros",
"runArgs": [
"--network=host",
"--pid=host",
"--ipc=host"
],
"mounts": [
"source=/tmp/.X11-unix,target=/tmp/.X11-unix,type=bind"
],
"containerEnv": {
"DISPLAY": "${localEnv:DISPLAY}",
"ROS_DOMAIN_ID": "0"
}
}💡 --network=host is required for DDS discovery across containers and the host machine.
5. Troubleshooting
❌ ros2: command not found
Cause: ROS 2 environment not sourced in current terminal.
Fix: Run: source /opt/ros/jazzy/setup.bash — or add it to ~/.bashrc
❌ No nodes found / ros2 topic list is empty
Cause: ROS_DOMAIN_ID mismatch between publisher and subscriber.
Fix: Export the same domain: export ROS_DOMAIN_ID=0 in both terminals.
❌ colcon build fails with 'package not found'
Cause: rosdep not initialized or dependencies missing.
Fix: Run: rosdep install --from-paths src --ignore-src -r -y
❌ Nav2 crashes with 'transform timeout'
Cause: Missing or delayed TF transforms — usually map→odom→base_link chain.
Fix: Check: ros2 run tf2_tools view_frames.py — ensure transform chain is continuous.
❌ RViz2 shows blank map / no lidar scan
Cause: Sensor topic names or frame IDs don't match RViz2 configuration.
Fix: ros2 topic list to find actual topic names, then update Fixed Frame and topic subscriptions in RViz2.