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 getting your first robot simulation running with Turtlesim. I wrote this after going through 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 — 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 |
Check 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 out of the box — required for Turtlesim’s graphical window.
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 the authenticity of ROS 2 packages.
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 key is stored in /usr/share/keyrings/ for secure apt verification.
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
Breaking this down:
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 it was created:
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 confirms 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’s included in ros-humble-desktop:
| 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 up ~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 — 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 forward/backward speed, angular.z is 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 /spawn from the dropdown, fill in parameters, and click Call. A visual interface for calling services, viewing topics, and plotting data — without memorizing 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 --update in PowerShell, then restart WSL with wsl --shutdown.
“Failed to get old console mode” in turtle_teleop_key
This happens when running in a non-interactive shell (script or 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 mastering turtlesim, the natural progression is:
- Understanding Nodes — deep dive into node lifecycle
- 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 every pattern it demonstrates — pub/sub, services, actions, remapping, multi-node communication — is exactly what you’ll use when controlling a real robot arm or running a fleet of autonomous agents.
More recent articles
- OpenUSD: Advanced Patterns and Common Gotchas. - 28th March 2026
- OpenUSD Mastery: From Composition to Pipeline — A SO-101 Arm Journey - 25th March 2026
- Learning OpenUSD — From Curious Questions to Real Understanding - 19th March 2026