Shakila Praveen Rathnayake

shakilar.com
~/ blog / Robotics / ros2-package-vs-node
#ROS 2 #Robotics #C++ #Python

ROS 2 Packages vs Node: Don't Do the Same Mistakes I Did

February 5, 2026 | 8 min read

You probably know what ROS2 nodes are. But if doesn’t, ROS2 nodes are the basic building blocks of a ROS2 system. They are independent processes that communicate with each other through topics, services, and actions. But you don’t need separate folders for each node. One of the mistake I did when I started with ROS2. I created a separate folder for each node. But you can have multiple nodes in a package.

A package is an organizational unit that contains your ROS 2 code, while a node is an executable process that performs computation. In ROS 2, nodes are organized into packages, meaning you must create a package before you can create a node.

Understanding the Core Concepts

Packages

Packages are the fundamental units for organizing, building, and distributing ROS 2 code. They allow you to release your work so others can build and use it easily. A single workspace can contain multiple packages, each in their own folder, and packages can use different build types (CMake for C++, Python, or mixed). roboticsunveiled

Nodes

Nodes are executable processes that perform specific computational tasks. Multiple nodes within packages communicate with each other through topics, services, actions, and parameters. Each node should have a single, well-defined purpose in your robot application. docs.ros

Creating ROS 2 Packages

Prerequisites

Before creating packages, ensure you have ROS 2 installed, your environment sourced, and a workspace created. Navigate to the src directory of your workspace before running package creation commands. roboticsbackend

Python Package Creation

To create a Python package, use:

cd ~/ros2_ws/src/
ros2 pkg create --build-type ament_python my_python_pkg

You can also add a node during creation:

ros2 pkg create --build-type ament_python --node-name my_node my_package

C++ Package Creation

For C++ packages:

cd ~/ros2_ws/src/
ros2 pkg create --build-type ament_cmake my_cpp_pkg

With a node:

ros2 pkg create --build-type ament_cmake --node-name my_node my_package

Adding Dependencies

Specify dependencies during creation:

ros2 pkg create --build-type ament_python my_package --dependencies rclpy std_msgs

Python Package File Structure

Complete Directory Layout

my_python_pkg/
├── my_python_pkg/
│   ├── __init__.py
│   └── my_python_node.py
├── resource/
│   └── my_python_pkg
├── test/
│   ├── test_copyright.py
│   ├── test_flake8.py
│   └── test_pep257.py
├── package.xml
├── setup.py
└── setup.cfg

Key Files Explained

package.xml: Contains metadata and dependencies for the package. You must edit the description, maintainer, and license fields before publishing: docs.ros

<?xml version="1.0"?>
<package format="3">
  <name>my_python_pkg</name>
  <version>0.0.0</version>
  <description>Your package description</description>
  <maintainer email="your@email.com">Your Name</maintainer>
  <license>Apache License 2.0</license>
  
  <buildtool_depend>ament_python</buildtool_depend>
  <depend>rclpy</depend>
  
  <export>
    <build_type>ament_python</build_type>
  </export>
</package>

setup.py: The Python equivalent of CMakeLists.txt, defining what to install and how to link dependencies. This file contains entry points that make your nodes executable: roboticsbackend

from setuptools import setup

package_name = 'my_python_pkg'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='Your Name',
    maintainer_email='your@email.com',
    description='Package description',
    license='Apache License 2.0',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'node_name = my_python_pkg.my_python_node:main'
        ],
    },
)

setup.cfg: Specifies where scripts will be installed. No modifications needed for basic usage. docs.ros

my_python_pkg/ folder: Contains all your Python nodes. The folder name must match your package name. docs.ros

resource/ folder: Required for ROS 2 to find your package. roboticsbackend

C++ Package File Structure

Complete Directory Layout

my_cpp_pkg/
├── include/
│   └── my_cpp_pkg/
├── src/
│   └── my_node.cpp
├── CMakeLists.txt
└── package.xml

Key Files Explained

CMakeLists.txt: Defines how the package is built and linked. Essential components include: ros-learnings.hashnode

cmake_minimum_required(VERSION 3.5)
project(my_cpp_pkg)

# Find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)

# Add executable
add_executable(my_node src/my_node.cpp)
ament_target_dependencies(my_node rclcpp)

# Install targets
install(TARGETS
  my_node
  DESTINATION lib/${PROJECT_NAME})

ament_package()

include/ folder: Contains public headers for your package. docs.ros

src/ folder: Contains C++ source code files. docs.ros

Creating Nodes Within Packages

Python Node Example

Create a node file in the package directory:

cd ~/ros2_ws/src/my_python_pkg/my_python_pkg/
touch my_python_node.py

Basic node structure:

import rclpy
from rclpy.node import Node

class MyPythonNode(Node):
    def __init__(self):
        super().__init__("my_node_name")
        self.get_logger().info("Node has been created")

def main(args=None):
    rclpy.init(args=args)
    node = MyPythonNode()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == "__main__":
    main()

Add the entry point to setup.py:

entry_points={
    'console_scripts': [
        'test = my_python_pkg.my_python_node:main'
    ],
},

Add dependencies to package.xml:

<depend>rclpy</depend>

C++ Node Example

The --node-name argument automatically creates a basic C++ node in the src/ directory. Nodes inherit from the rclcpp::Node class. docs.ros

Building and Running

Compile Your Package

From the workspace root:

cd ~/ros2_ws
colcon build --packages-select my_package

Compiling is necessary even for Python to install scripts where they can be found by ros2 run, pass parameters, and work with launch files. roboticsbackend

Source the Workspace

Open a new terminal and source:

source ~/ros2_ws/install/setup.bash

Run a Node

ros2 run my_package node_name

Adding Additional Files

Launch Files

Create a launch/ folder at the package root and modify setup.py:

import os
from glob import glob

data_files=[
    ('share/ament_index/resource_index/packages',
        ['resource/' + package_name]),
    ('share/' + package_name, ['package.xml']),
    (os.path.join('share', package_name, 'launch'), 
        glob('launch/*.launch.py')),
],

Configuration Files

Create a config/ folder for YAML files and add to setup.py:

(os.path.join('share', package_name, 'config'), 
    glob('config/*.yaml')),

Package Organization Best Practices

Naming Conventions

Structural Guidelines

Mixed C++ and Python Packages

For packages with both languages, use ament_cmake build type and add ament_cmake_python as a buildtool dependency in package.xml. This allows configuring Python setup through CMake. roboticsbackend

Key Differences Summary

AspectPackageNode
DefinitionOrganizational unit for codeExecutable process performing computation
ContainsOne or more nodes, config files, launch filesSingle executable with specific purpose
Created withros2 pkg create commandPython/C++ code within package
Build systemament_cmake or ament_pythonCompiled/installed as part of package
PurposeOrganize and distribute codePerform specific computational tasks

Understanding this hierarchy - packages contain nodes - is fundamental to ROS 2 development. Proper organization ensures scalable, maintainable robot applications. husarion

Back to all posts