ROS 2 Humble: Complete Installation Guide with Turtlesim from Zero to First Node
2nd March 2026
This is a complete walkthrough for installing ROS 2 Humble on Ubuntu 22.04 and running your first robot simulation with Turtlesim. I wrote this after I completed the process myself. The official docs are thorough but scattered across many pages. This puts everything in one place, from locale setup to writing your first Python node.
What is ROS 2?
ROS 2 (Robot Operating System 2) is not an operating system. It’s an open-source robotics middleware framework. In other words, it is a set of libraries and tools that help you build robot applications.
The core concepts:
- Nodes — Independent processes that perform computation
- Topics — Named buses for nodes to exchange messages (publish/subscribe)
- Services — Request/response communication between nodes
- Actions — Long-running tasks with feedback
- DDS — The underlying middleware (Data Distribution Service) that handles communication
ROS 2 Humble Hawksbill is a Long-Term Support (LTS) release targeting Ubuntu 22.04 (Jammy Jellyfish) with support until May 2027.
Prerequisites
| Requirement | Details |
|---|---|
| OS | Ubuntu 22.04 LTS (native or WSL2 on Windows 11) |
| Architecture | amd64 (x86_64) |
| Disk Space | ~3-5 GB for ros-humble-desktop |
| Internet | Required for downloading packages |
Verify your OS version:
cat /etc/os-release
# Expected:
# PRETTY_NAME="Ubuntu 22.04.5 LTS"
# VERSION_CODENAME=jammy
If you’re on WSL2, Windows 11 includes WSLg, which provides GUI support with no extra configuration. Turtlesim’s graphical window requires this GUI support.
Step 1 — Locale Configuration
ROS 2 requires a UTF-8 locale for proper string handling across all tools.
# Check current locale
locale
# Install and configure UTF-8 locale
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
# Verify
locale
You should see LANG=en_US.UTF-8 in the output. ROS 2 internally uses UTF-8 for message serialization and logging. A misconfigured locale can cause unexpected encoding errors in node communication.
Step 2 — Enable Universe Repository
Ubuntu’s universe repository contains community-maintained packages that ROS 2 depends on.
sudo apt install software-properties-common
sudo add-apt-repository universe
Step 3 — Add the ROS 2 GPG Key
This lets apt verify that ROS 2 packages are authentic.
sudo apt update && sudo apt install curl -y
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \
-o /usr/share/keyrings/ros-archive-keyring.gpg
The -sSL flags mean: silent, show errors, follow redirects. The command stores the key in /usr/share/keyrings/ so that apt can verify packages securely.
Step 4 — Configure the ROS 2 Source 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
What each part does:
arch=$(dpkg --print-architecture)— detects your architecture (e.g.,amd64)signed-by=...— points to the GPG key we just downloaded$(. /etc/os-release && echo $UBUNTU_CODENAME)— resolves tojammyon Ubuntu 22.04
Verify that the command created the file:
cat /etc/apt/sources.list.d/ros2.list
# Expected:
# deb [arch=amd64 signed-by=/usr/share/keyrings/ros-archive-keyring.gpg]
# http://packages.ros.org/ros2/ubuntu jammy main
Step 5 — Update & Upgrade
sudo apt update
sudo apt upgrade -y
After apt update, you should see new entries from packages.ros.org. This shows that apt can now see ROS 2 packages.
Step 6 — Install ROS 2 Humble Desktop
This is the main installation step:
sudo apt install ros-humble-desktop -y
What ros-humble-desktop includes:
| Component | Description |
|---|---|
ros-humble-ros-base | Core ROS 2 libraries, CLI tools, communication layer |
rclcpp / rclpy | Client libraries for C++ and Python |
rviz2 | 3D visualization tool |
rqt | GUI plugin framework |
turtlesim | 2D turtle simulator |
| DDS middleware | FastDDS (default) for node communication |
This downloads approximately 500+ packages (~2-3 GB). For lighter installs:
# Minimal install (no GUI tools)
sudo apt install ros-humble-ros-base
# Bare bones (just libraries)
sudo apt install ros-humble-ros-core
Step 7 — Environment Setup
ROS 2 requires sourcing a setup script to configure environment variables (ROS_DISTRO, AMENT_PREFIX_PATH, PYTHONPATH, etc.).
# Source for the current terminal
source /opt/ros/humble/setup.bash
# Add to .bashrc so every new terminal is ready automatically
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
# Verify
echo $ROS_DISTRO
# Expected: humble
This sets ~15 environment variables that tell ROS 2 where to find packages, libraries, executables, and Python modules. Without it, ros2 commands won’t work.
Step 8 — Verify Installation
ros2
If you see a list of available commands (action, bag, node, topic, service, etc.), ROS 2 Humble is installed and working.
Testing with Turtlesim
Turtlesim is a lightweight 2D simulator. It is the “Hello World” of ROS 2. It teaches core concepts: nodes, topics, services, and actions.
# Install (included in ros-humble-desktop, but just in case)
sudo apt install ros-humble-turtlesim
# Check available executables
ros2 pkg executables turtlesim
| Executable | Description |
|---|---|
turtlesim_node | The simulator GUI window |
turtle_teleop_key | Keyboard controller (arrow keys) |
draw_square | Moves turtle in a square pattern automatically |
mimic | Mirrors one turtle’s movement onto another |
Launch the Simulator
Terminal 1 — Start the simulator:
source /opt/ros/humble/setup.bash
ros2 run turtlesim turtlesim_node
# Output:
# [INFO] [turtlesim]: Starting turtlesim with node name /turtlesim
# [INFO] [turtlesim]: Spawning turtle [turtle1] at x=[5.54], y=[5.54], theta=[0.00]
A window appears with a blue background and a turtle in the center.
Control the Turtle with Keyboard
Terminal 2 — Start the keyboard controller:
source /opt/ros/humble/setup.bash
ros2 run turtlesim turtle_teleop_key
| Key | Action |
|---|---|
| Up Arrow | Move forward |
| Down Arrow | Move backward |
| Left Arrow | Rotate counter-clockwise |
| Right Arrow | Rotate clockwise |
The turtle draws a path as it moves. This is the publish/subscribe pattern in action:
[turtle_teleop_key] ──publishes──▶ /turtle1/cmd_vel ──subscribes──▶ [turtlesim_node]
(your keyboard) (ROS 2 topic) (moves turtle)
Exploring ROS 2 CLI Tools
With turtlesim running, open a third terminal and explore:
List Active Nodes and Topics
# Active nodes
ros2 node list
# /turtlesim
# Active topics
ros2 topic list
# /parameter_events
# /rosout
# /turtle1/cmd_vel
# /turtle1/color_sensor
# /turtle1/pose
| Topic | Description |
|---|---|
/turtle1/cmd_vel | Velocity commands (Twist messages) |
/turtle1/pose | Turtle’s current position and orientation |
/turtle1/color_sensor | Color of the pixel under the turtle |
/parameter_events | Parameter change notifications |
/rosout | Logging output from all nodes |
Watch Live Messages
# See live velocity commands (press arrow keys in the teleop terminal)
ros2 topic echo /turtle1/cmd_vel
# Output when pressing Up:
# linear:
# x: 2.0
# y: 0.0
# z: 0.0
# angular:
# x: 0.0
# y: 0.0
# z: 0.0
Inspect Topic and Message Structure
# Topic metadata
ros2 topic info /turtle1/cmd_vel
# Type: geometry_msgs/msg/Twist
# Publisher count: 1
# Subscription count: 1
# Message structure
ros2 interface show geometry_msgs/msg/Twist
# Vector3 linear
# float64 x
# float64 y
# float64 z
# Vector3 angular
# float64 x
# float64 y
# float64 z
How the Twist message controls the turtle: linear.x is the forward/backward speed, and angular.z is the rotation speed. The other fields are unused in 2D.
List Services and Actions
# Available services
ros2 service list
# /clear
# /kill
# /reset
# /spawn
# /turtle1/set_pen
# /turtle1/teleport_absolute
# /turtle1/teleport_relative
# Available actions
ros2 action list
# /turtle1/rotate_absolute
Advanced Turtlesim: Services, Spawning & More
Spawn a Second Turtle
ros2 service call /spawn turtlesim/srv/Spawn \
"{x: 1.0, y: 1.0, theta: 0.0, name: 'turtle2'}"
# A second turtle appears. New topics are automatically created:
# /turtle2/cmd_vel
# /turtle2/color_sensor
# /turtle2/pose
Control the Second Turtle (Topic Remapping)
By default, turtle_teleop_key publishes to /turtle1/cmd_vel. Use --remap to control turtle2:
ros2 run turtlesim turtle_teleop_key \
--ros-args --remap turtle1/cmd_vel:=turtle2/cmd_vel
Teleport, Clear, Kill, Reset
# Teleport to exact position
ros2 service call /turtle1/teleport_absolute \
turtlesim/srv/TeleportAbsolute "{x: 8.0, y: 8.0, theta: 1.57}"
# Teleport relative to current position
ros2 service call /turtle1/teleport_relative \
turtlesim/srv/TeleportRelative "{linear: 2.0, angular: 0.5}"
# Clear the drawing
ros2 service call /clear std_srvs/srv/Empty
# Kill a turtle
ros2 service call /kill turtlesim/srv/Kill "{name: 'turtle2'}"
# Reset everything
ros2 service call /reset std_srvs/srv/Empty
Publish Velocity from Command Line
# Continuous circle (no teleop node needed)
ros2 topic pub /turtle1/cmd_vel geometry_msgs/msg/Twist \
"{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 1.0}}"
# Single movement (one-shot)
ros2 topic pub --once /turtle1/cmd_vel geometry_msgs/msg/Twist \
"{linear: {x: 1.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"
Using rqt (GUI Tool)
sudo apt install '~nros-humble-rqt*'
rqt
In the rqt window:
- Go to Plugins > Services > Service Caller.
- Select
/spawnfrom the dropdown. - Enter the parameters.
- Click Call.
rqt is a visual interface for calling services, viewing topics, and plotting data. You do not need to memorize CLI commands.
Controlling the Turtle Programmatically (Python)
Circle Movement
Create turtle_circle.py:
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
class TurtleCircle(Node):
def __init__(self):
super().__init__('turtle_circle')
self.publisher = self.create_publisher(Twist, '/turtle1/cmd_vel', 10)
self.timer = self.create_timer(0.1, self.move) # 10 Hz
self.get_logger().info('Turtle circle controller started')
def move(self):
msg = Twist()
msg.linear.x = 2.0 # forward speed
msg.angular.z = 1.0 # rotation speed
self.publisher.publish(msg)
def main():
rclpy.init()
node = TurtleCircle()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Run it:
source /opt/ros/humble/setup.bash
python3 turtle_circle.py
The turtle drives in a continuous circle. This shows the basic pattern:
- Create a node.
- Create a publisher.
- Use a timer to publish messages at a fixed rate.
Square Movement
Create turtle_square.py:
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
import time
class TurtleSquare(Node):
def __init__(self):
super().__init__('turtle_square')
self.publisher = self.create_publisher(Twist, '/turtle1/cmd_vel', 10)
self.get_logger().info('Drawing a square...')
self.draw_square()
def publish_for_duration(self, msg, duration):
start = time.time()
while time.time() - start < duration:
self.publisher.publish(msg)
time.sleep(0.1)
def draw_square(self):
for i in range(4):
# Move forward
forward = Twist()
forward.linear.x = 2.0
self.publish_for_duration(forward, 2.0)
# Stop briefly
stop = Twist()
self.publish_for_duration(stop, 0.5)
# Turn 90 degrees
turn = Twist()
turn.angular.z = 1.5708 # ~pi/2
self.publish_for_duration(turn, 1.0)
# Stop briefly
self.publish_for_duration(stop, 0.5)
self.get_logger().info('Square complete!')
Subscribe to Turtle’s Pose
Create turtle_listener.py:
import rclpy
from rclpy.node import Node
from turtlesim.msg import Pose
class TurtleListener(Node):
def __init__(self):
super().__init__('turtle_listener')
self.subscription = self.create_subscription(
Pose, '/turtle1/pose', self.pose_callback, 10
)
self.get_logger().info('Listening to turtle1 pose...')
def pose_callback(self, msg):
self.get_logger().info(
f'Position: ({msg.x:.2f}, {msg.y:.2f}) | '
f'Theta: {msg.theta:.2f} | '
f'Speed: linear={msg.linear_velocity:.2f}, '
f'angular={msg.angular_velocity:.2f}'
)
def main():
rclpy.init()
node = TurtleListener()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Understanding the Architecture
The ROS 2 Communication Stack
┌─────────────────────────────────────────────────┐
│ Your Application Node │
│ (Python: rclpy / C++: rclcpp) │
├─────────────────────────────────────────────────┤
│ ROS 2 Client Library │
│ (rcl — ROS Client Library in C) │
├─────────────────────────────────────────────────┤
│ ROS 2 Middleware (rmw) │
│ (Abstract middleware interface) │
├─────────────────────────────────────────────────┤
│ DDS Implementation │
│ (FastDDS / CycloneDDS / Connext DDS) │
├─────────────────────────────────────────────────┤
│ UDP/TCP Transport │
└─────────────────────────────────────────────────┘
How Turtlesim Nodes Communicate
Terminal 1: turtlesim_node Terminal 2: turtle_teleop_key
┌──────────────────────┐ ┌──────────────────────┐
│ /turtlesim node │ │ /teleop_turtle node │
│ │ │ │
│ Subscribes to: │ │ Publishes to: │
│ /turtle1/cmd_vel │◄───DDS────│ /turtle1/cmd_vel │
│ │ │ │
│ Publishes: │ │ Reads: │
│ /turtle1/pose │ │ Keyboard input │
│ /turtle1/color_sensor│ │ │
│ │ └──────────────────────┘
│ Services: │
│ /spawn, /kill, etc │ Terminal 3: CLI tools
│ │ ┌──────────────────────┐
│ Actions: │ │ ros2 topic echo │
│ /turtle1/rotate_abs │ │ ros2 service call │
└──────────────────────┘ │ rqt │
└──────────────────────┘
Key Patterns Demonstrated
| Pattern | Example in Turtlesim |
|---|---|
| Publish/Subscribe | teleop_key publishes Twist → turtlesim_node subscribes |
| Services | /spawn — request to create a turtle, response with name |
| Actions | /turtle1/rotate_absolute — long-running rotation with feedback |
| Parameters | Background color, default pen settings |
| Remapping | --remap turtle1/cmd_vel:=turtle2/cmd_vel |
| Multi-node | Multiple turtles, each with independent topics |
Troubleshooting
“ros2: command not found”
source /opt/ros/humble/setup.bash
# Add to .bashrc to make it permanent:
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
Turtlesim Window Doesn’t Appear (WSL2)
# Test WSLg with a simple GUI app
sudo apt install x11-apps -y
xclock
If xclock doesn’t appear:
- Update to Windows 11 (22000+).
- Run
wsl --updatein PowerShell. - Restart WSL with
wsl --shutdown.
“Failed to get old console mode” in turtle_teleop_key
This happens when you run it in a non-interactive shell (a script or a background process). Run it directly in an interactive terminal.
Can’t Control the Right Turtle
# Use topic remapping to control turtle2
ros2 run turtlesim turtle_teleop_key \
--ros-args --remap turtle1/cmd_vel:=turtle2/cmd_vel
Quick Reference Card
# === SETUP ===
source /opt/ros/humble/setup.bash
# === RUN TURTLESIM ===
ros2 run turtlesim turtlesim_node # Start simulator
ros2 run turtlesim turtle_teleop_key # Keyboard control
# === INSPECT ===
ros2 node list # Active nodes
ros2 topic list # Active topics
ros2 service list # Available services
ros2 action list # Available actions
ros2 topic echo /turtle1/cmd_vel # Live message stream
ros2 topic info /turtle1/cmd_vel # Topic metadata
ros2 interface show geometry_msgs/msg/Twist # Message structure
# === CONTROL ===
ros2 topic pub /turtle1/cmd_vel geometry_msgs/msg/Twist \
"{linear: {x: 2.0}, angular: {z: 1.0}}"
# === SERVICES ===
ros2 service call /spawn turtlesim/srv/Spawn \
"{x: 2.0, y: 2.0, theta: 0.0, name: 'turtle2'}"
ros2 service call /clear std_srvs/srv/Empty
ros2 service call /reset std_srvs/srv/Empty
ros2 service call /kill turtlesim/srv/Kill "{name: 'turtle2'}"
# === GUI ===
rqt
What’s Next?
After you master turtlesim, the natural progression is:
- Understanding Nodes — node lifecycle in detail
- Understanding Topics — publisher/subscriber in detail
- Understanding Services — request/response patterns
- Understanding Actions — long-running tasks with feedback
- Creating a Package — build your own ROS 2 package
- Writing a Publisher/Subscriber (Python) — your first custom node
- Gazebo — graduate to 3D robot simulation
The official ROS 2 Humble tutorials cover all of these. The Turtlesim exercise may seem trivial. But it demonstrates pub/sub, services, actions, remapping, and multi-node communication. Every one of these patterns is exactly what you’ll use when you control a real robot arm or run a fleet of autonomous agents.
More recent articles
- pi-durable, from the ground up - 20th October 2026
- Hermes Agent: The Self-Improving Story - 12th June 2026
- Claude Code Dynamic Workflows: Inside Out - 29th May 2026