---
title: ROS orchestration with snaps
description: Let's explore the orchestration features that snap offers to automate
  and synchronize processes for your ROS and ROS 2 applications
url: https://canonical.com/blog/ros-orchestration-with-snaps?format=md
---

1. [Blog](https://canonical.com/blog)
2. Article

---

[gbeuzeboc](https://canonical.com/blog/author/gbeuzeboc "More about gbeuzeboc")

22 September 2022

# ROS orchestration with snaps

[Orchestration](https://canonical.com/blog/tag/orchestration)
[robotics](https://canonical.com/blog/tag/robotics)
[ROS](https://canonical.com/blog/tag/ros)
[ROS2](https://canonical.com/blog/tag/ros2)
[Snap](https://canonical.com/blog/tag/snap)

---

Share the article

Application orchestration is the process of integrating applications together to automate and synchronise processes. In robotics, this is essential, especially on complex systems that involve a lot of different processes working together. But, ROS applications are usually launched all at once from one top-level launch file.

With orchestration, smaller launch files could be launched and synchronised to start one after the other to make sure everything is in the right state. Orchestration can also hold processes and insert some process logic. This is what ROS orchestration should be about.

This way, for instance, you could make your `localisation` node start only once your `map_server` made the map available.

Snaps offer orchestration features that might come handy for your ROS orchestration.

In this post, we will demonstrate how to start a snap automatically at boot and how to monitor it. Then, through some examples, we will explore the different orchestration features that snaps offer. We thus assume that you are familiar with snaps for ROS; if you aren’t, or need a refresher, head over to the [documentation page](https://snapcraft.io/docs/ros2-applications).

## Let’s get started

Let us first build and install the snap we will use in this step-by-step tutorial

```
git clone https://github.com/ubuntu-robotics/ros-snaps-examples.git -b ros_orchestration_with_snaps_blog
cd ros-snaps-examples/orchestration_humble_core22
snapcraft
sudo snap install talker-listener_0.1_amd64.snap --dangerous
```

Note that all the steps described hereafter are already implemented in this git repository. However, they are commented for you to easily follow along.

## Start a ROS application automatically at boot

Once you have tested and snapped your robotics software, you can start it from the shell. For an autonomous robot, starting your applications automatically at boot is preferable than starting manually every single time. It obviously saves time and most importantly makes your robot truly autonomous.

Snaps offer a simple way to turn your snap command into services and daemons, so that they will either start automatically at boot time and end when the machine is shut down, or start and stop on demand through socket activation.

Here, we will work with a simple ROS 2 Humble talker-listener that is already snapped (strictly confined). If you want to know how the talker-listener was snapped, you can visit the [How to build a snap using ROS 2 Humble](https://ubuntu.com/blog/how-to-build-a-snap-using-ros-2-humble) blog post.

### Turn your snap command into a daemon

Once you have snapped your application, you can not only expose commands, but also create [daemons](https://snapcraft.io/docs/services-and-daemons).

* Daemons are commands that can be started automatically at boot, which is a must-have for your robot software.

For now, our snap is exposing two commands – `talker` and `listener`. They respectively start the node publishing message and the node subscribing and listening to the message.

You can test the snap by launching each of the following commands in their own terminal:

```
$ talker-listener.talker
$ talker-listener.listener
```

In order to start them both automatically in the background, we must turn them into daemons. Snap daemons can be of different types, but the most common one is “`simple`”. It will simply run as long as the service is enabled.

To turn our application into daemons, we only have to add ‘`daemon: simple`’ to both our snap applications:

```
apps:
  listener:
    command: opt/ros/humble/bin/ros2 run demo_nodes_cpp listener
    + daemon: simple
    plugs: [network, network-bind]
    extensions: [ros2-humble]

  talker: command: opt/ros/humble/bin/ros2 run demo_nodes_cpp talker
    + daemon: simple
    plugs: [network, network-bind]
    extensions: [ros2-humble]
```

All there’s left to do is to rebuild and re-install the snap. Upon installation, both daemons are going to be automatically started in no particular order

Now we can build and install this snap.

### Check your daemons

Now our `talker` and `listener` are running in the background. Snaps offer a way to monitor and interact with your snap daemons.

Snap daemons are actually plain [SystemD daemons](https://www.freedesktop.org/wiki/Software/systemd/) so if you are familiar with SystemD commands and tools (`systemctl, journalctl, etc.`) you can use them for snap daemons too.

For this post, we are going to focus on `snap` commands to interact with our daemons and monitor them.

#### Check our service status

The very first thing would be to verify the status of our daemons, making sure they are running. The snap info command gives us a summary of the status of our daemons,

```
$ snap info talker-listener

name: talker-listener
summary: ROS 2 Talker/Listener Example
publisher: –
license: unset
description: |
 This example launches a ROS 2 talker and listener.
Services:
 talker-listener.listener: simple, enabled, active
 talker-listener.talker: simple, enabled, active
refresh-date: today at 18:00 CEST
installed: 0.1 (x35) 69MB -
```

Here we see our two services listed. They are both `simple`, `enabled` and `active`.

`Simple` is the type of daemon we specified. `Enabled`, means that our service is meant to start automatically (at boot, upon snap install etc). `Active`, means that our service is currently running.

So here, both our `talker` and `listener` services are up and running. Let’s browse the logs.

#### Browsing the logs

The snap command also offers a way to browse our service logs.

Since our services are already running in the background, we can type:

```
$ sudo snap logs talker-listener

2022-08-23T11:13:08+02:00 talker-listener.listener [2833606]: [INFO] [1661245988.120676423] [talker]: Publishing: 'Hello World: 123'
[...]
2022-08-23T11:13:12+02:00 talker-listener.talker[2833607]: [INFO] [1661245992.121411564] [listener]: I heard: [Hello World: 123]
```

This command will fetch the logs of our services and display the last 10 lines by default. In case you want the command to continuously run and print new logs as they come in, you can use the “`-f`” option:

```
sudo snap logs talker-listener -f
```

Note that so far we have been fetching the logs of our whole snap (both services). We can also get the logs of a specific service. To continuously fetch the `listener` logs, type:

```
sudo snap logs talker-listener.listener -f
```

### Interact with snap daemons

The snap command also offers ways to control services. As we saw, our services are currently `enabled` and `active`.

`Enabled`, means our service will start automatically at boot. We can change this by “disabling” it, so it won’t start automatically any more

```
sudo snap disable talker-listener.talker
```

Note that disabling the service won’t stop the current running process.

We can also stop the current process altogether with:

```
sudo snap stop talker-listener.talker
```

Conversely, we can enable/start a service with:

```
sudo snap enable talker-listener.talker
sudo snap start talker-listener.talker
```

Make sure to re-enable everything to keep following this post along:

```
sudo snap enable talker-listener
```

## ROS orchestration

So far, our talker and listener start up without any specific orchestration; or in layman’s terms, in no specific order. Fortunately, snaps offer different ways to orchestrate services.

To spice up our experience, let’s add some script to our snap to showcase the orchestration features:

```
Parts:
[...]
  + # copy local scripts to the snap usr/bin
  + local-files:
    + plugin: dump
      + source: snap/local/
      + organize: '*.sh': usr/bin/
```

This is a collection of bash scripts that have been conveniently prepared to demonstrate orchestration hereafter.

We will also add another app:

```
Apps:
  [...]
  + listener-waiter:
    + command: usr/bin/listener-waiter.sh
    + daemon: oneshot
    + plugs: [network, network-bind]
    + extensions: [ros2-humble]
    + # Necessary for python3 ROS app
    + environment: "LD_LIBRARY_PATH": "$LD_LIBRARY_PATH:$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/blas:$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/lapack"
```

This app is simply waiting for the node `/listener` to be present. This daemon is created as a “`oneshot`”, another type of daemon that is meant to only be run once at start and then exit after completion.

### After and before for ROS orchestration

The very first thing we can do is to change the start order. The `after/before` keywords are valid only for daemons and allow us to specify if a specific daemon should be started `after` or `before` one (or several) other service(s). Note that for `oneshot` daemons, the `before/after` keywords are waiting for the completion of the `oneshot` service.

The scenario here goes as follows: start the `listener`, make sure it’s properly started, then and only then, start the `talker`. To make sure our `listener` is properly started, we will use the `listener-waiter` app we introduced in the previous section. Remember, it waits for a node to be listed.

Here, we define the orchestration only at the `listener-waiter` application level to keep it simple. So, we want it to start after the `listener` and before the `talker` so the `talker` will start only once the `listener` is ready.

To do so, let’s use the `before/after` keywords:

```
listener-waiter:
      command: usr/bin/listener-waiter.sh
      daemon: oneshot
      + after: [listener]
      + before: [talker]
      plugs: [network, network-bind]
      extensions: [ros2-humble]
```

This is rather explicit, it must be started `after` the `listener` but `before` the `talker`. After rebuilding the snap, we can reinstall it and look at the log again. Here is a shortened version of the output logs:

```
systemd[1]: Started Service for snap application talker-listener.listener.
systemd[1]: Starting Service for snap application talker-listener.listener-waiter... talker-listener.listener-waiter[76329]: Making sure the listener is started
systemd[1]: snap.talker-listener.listener-waiter.service: Succeeded.
systemd[1]: Finished Service for snap application talker-listener.listener-waiter. systemd[1]: Started Service for snap application
talker-listener.talker. [talker]: Publishing: 'Hello World: 1'
talker-listener.listener[76439]: [INFO] [1661266809.685248681] [listener]: I heard: [Hello World: 1]
```

We can see in this log that everything went as expected. The `talker` has been started only once the `listener` was available.

In this example, we specified the `before/after` field within the `listener-waiter` for the sake of simplicity. However, any daemon can specify a `before/after` as long as the specified applications are from the same snap, allowing for pretty complex orchestration.

### Stop-command for ROS orchestration

Another interesting feature for snap orchestration is the `stop-command`. It allows one to specify a script, or a command, to be called right before the stop signal is sent to a program when running `snap stop`. With this, we could make sure, for instance, that everything is synchronised or saved before exiting. Let’s look at a quick example: running an echo of a string.

A script called `stop-command.sh` has already been added to the snap `usr/bin`.

All we need to do here is to specify the path to the said script as a `stop-command`.

```
talker:
  command: opt/ros/humble/bin/ros2 run demo_nodes_cpp talker
  plugs: [network, network-bind]
  daemon: simple
  + stop-command: usr/bin/stop-command.sh
  extensions: [ros2-humble]
```

After rebuilding and reinstalling the snap, we can trigger a stop manually with the `snap stop` command:

```
sudo snap stop talker-listener # stop all the services of this snap
sudo snap logs talker-listener -f # visualize the logs
```

We should see an output similar to:

```
systemd[1]: Stopping Service for snap application talker-listener.listener...
systemd[1]: snap.talker-listener.listener.service: Succeeded.
systemd[1]: Stopped Service for snap application talker-listener.listener.
systemd[1]: Stopping Service for snap application talker-listener.talker...
talker-listener.talker[86863]: About to stop the service
systemd[1]: snap.talker-listener.talker.service: Succeeded. 2022-08-23T17:23:57+02:00 systemd[1]: Stopped Service for snap application talker-listener.talker.
```

From the logs, we can see that before exiting the service `talker`, the `stop-command` script was executed and printed a message: “`About to stop the service`”. Then, only after the `stop-command` script finished, was the `talker` terminated.

### Post-stop-command for ROS orchestration

Similarly to the `stop-command` entry, the `post-stop-command` is also calling a command, but this time, only after the service is stopped.

The use case could be to run some data clean-up or even notify a server that your system just stopped. Again, let us try this feature with a conveniently pre-baked script logging a message

```
talker:
  command: opt/ros/humble/bin/ros2 run demo_nodes_cpp talker
  plugs: [network, network-bind]
  daemon: simple
  stop-command: usr/bin/stop-command.sh
  + post-stop-command: usr/bin/post-stop-command.sh
  extensions: [ros2-humble]
```

Rebuild, re-install, and without much surprise, we get the following output:

```
systemd[1]: Stopping Service for snap application talker-listener.talker...
talker-listener.talker[90660]: About to stop the service
talker-listener.talker[90548]: [INFO] [1661269138.094854527] [rclcpp]: signal_handler(signum=15)
talker-listener.talker[90710]: Goodbye from the post-stop-command!
systemd[1]: snap.talker-listener.talker.service: Succeeded.
systemd[1]: Stopped Service for snap application talker-listener.talker.
```

From the logs we can see that our `talker` application executed the `stop-command` script then received the termination signal and only after our `post-command` script logged the message: ”`Goodbye from the post-stop-command!`”.

### Command-chain

So far, we have seen how to call additional commands around the moment we stop our service. The `command-chain` keyword allows us to list commands to be executed before our main command. The characteristic use case is to set up your environment. The `ros2-humble` extension that we are using in our snap example is actually using this mechanism. Thanks to it, we don’t have to worry about sourcing the ROS environment in the snap. If you are curious, [here](https://github.com/snapcore/snapcraft/blob/main/extensions/ros2/launch) is the said command-chain script. The best part is that the `command-chain` entry is not only available for daemons, but also for services and regular commands.

The scripts listed in the `command-chain` are not called one by one automatically. Instead, they are called as arguments of each others, resulting in a final command similar to:

```
./command-chain-script1.sh command-chain-script2.sh main-script.sh
```

So you must make sure that your `command-chain-scripts` are calling passed arguments as executables. For example, here, `command-chain-script1.sh` is responsible for calling `command-chain-script2.sh`.

Let’s see what our command-chain-talker.sh script looks like

```
#!/usr/bin/sh
echo "Hello from the talker command-chain!"

# Necessary to start the main command
exec $@
```

The only thing to pay attention to is the `exec $@` which is simply calling the next command. If we don’t specify this, our main snap command won’t be called.

Let’s add yet another script to our `talker` `command-chain`:

```
talker:
  + command-chain: [usr/bin/command-chain-talker.sh]
  command: opt/ros/humble/bin/ros2 run demo_nodes_cpp talker
  plugs: [network, network-bind]
  daemon: simple
  stop-command: usr/bin/stop-command.sh
  post-stop-command: usr/bin/post-stop-command.sh
  extensions: [ros2-humble]
```

After building and installing, we can see that now the logs are:

```
systemd[1]: Starting Service for snap application talker-listener.listener-waiter... talker-listener.listener-waiter[96438]: Making sure the listener is started
systemd[1]: snap.talker-listener.listener-waiter.service: Succeeded.
systemd[1]: Finished Service for snap application talker-listener.listener-waiter.
systemd[1]: Started Service for snap application talker-listener.talker.
talker-listener.talker[96538]: Hello from the talker command-chain!
talker-listener.talker[96594]: [INFO] [1661271361.139378609] [talker]: Publishing: 'Hello World: 1'
```

We can see from the logs that once our `listener` was available, the `talker` part was started. The `command-chain-talker.sh` script was called and printed the message:  “`Hello from the talker command-chain!`”, and only after that our talker started publishing.

## Conclusion

I hope that reading this article helps you understand the snap daemon features a bit more and inspires you to use them for ROS orchestration. For now, orchestration can only be done within the same snap, since strictly confined snaps are not allowed to launch other applications outside their sandbox. Of course, you could also combine the snap orchestration features with other orchestration software. Most notably, [ROS 2 nodes lifecycle](https://github.com/ros2/demos/blob/humble/lifecycle/README.rst) allows you to control the state of your nodes, so that you can orchestrate your node’s initialisation, for instance.
If you have any feedback, questions or ideas regarding ROS snap orchestration with snaps, please join [our forum](https://forum.snapcraft.io/) and let us know what you think. Furthermore, have a look at the [snap documentation](https://snapcraft.io/docs/robotics) if you want to learn more about snaps for [robotics applications](https://ubuntu.com/robotics).

[Get in touch

Interested in running Ubuntu in your organization?](https://ubuntu.com/about/contact-us/form)

## Sign up for our newsletter

Get the latest Canonical news and updates in your inbox.

Work email:

\*I agree to receive information about Canonical's
products and services.

By submitting this form, I confirm that I have read and agree to [Canonical's Privacy Policy](https://canonical.com/legal/dataprivacy).

Sign up

## Share on

---

## Related posts

[### Extending ROS Noetic Support with ESM-Enabled Content Snaps](https://canonical.com/blog/extending-ros-noetic-support-with-esm-enabled-content-snaps)

Canonical has now extended its ESM (Expanded Security Maintenance) for ROS coverage to ROS Noetic content-sharing snaps. With ESM for ROS now available in both deb and snap...

[Florencia Cabral Berenfus](https://canonical.com/blog/author/flor-cabral)

17 December 2025

[### Discover your fully open source robotics observability at ROSCon 2025](https://canonical.com/blog/roscon-2025)

Another year, another ROSCon! This year we’re heading to Singapore, and Canonical is once again thrilled to sponsor this important community event. Just like last year in...

[Gabriel Aguiar Noury](https://canonical.com/blog/author/g-aguiar-noury)

22 October 2025

[### Canonical is now a platinum member in the Open Source Robotics Alliance](https://canonical.com/blog/osra-membership)

Ubuntu is the home of ROS. The very first ROS distribution, Box Turtle, launched on Ubuntu 8.04 LTS, Hardy Heron, and since then, Ubuntu and ROS have grown hand in hand. With...

[Gabriel Aguiar Noury](https://canonical.com/blog/author/g-aguiar-noury)

20 August 2025

[### ROS Noetic is EOL – take action to maintain fleet security](https://canonical.com/blog/ros-noetic-is-eol-take-action-to-maintain-fleet-security)

As of May 2025, the Robot Operating System (ROS) Noetic Ninjemys officially reached its end of life (EOL). First released in 2020 as the final ROS (1) distribution, ROS Noetic...

[Florencia Cabral Berenfus](https://canonical.com/blog/author/flor-cabral)

8 August 2025
