Installing ROS in LXD Containers


on 29 June 2020

It’s the season for updates. The last few weeks have ushered in ROS 1 Noetic and ROS 2 Foxy, both of which target the recently released Ubuntu 20.04 Focal Fossa. As always, new releases come with trepidation: how can I install new software and test compatibility, yet keep my own environment stable until I know I’m ready to upgrade? This is one of the many good reasons to dive into containers

In this blog post we’ll create a base LXD profile with the ROS software repositories and full graphical capabilities enabled. Launch containers to meet your robotics needs: everything from software development and system testing through robot operations can be covered within containers.

Check out our youtube video to see these instructions in action. Also see the full installation instructions for both ROS 1 and ROS 2 available at

Getting started

All you need to get started is a Linux workstation with LXD installed. The installation of LXD is not covered here as there are a number of great instructions online. See the getting started guide at for more information.

We’ll cover hereafter the three basic steps to getting set up:

  • Creating a LXD container profile
  • Launching and connecting to the container
  • Installing ROS

Create a profile

All LXD containers have a defined profile. A default profile was created when LXD was first set installed, but we will create a ROS specific profile. This will support running ROS (either version 1 or 2) on an Ubuntu image.

The profile contains four specific configuration features:

  • ROS software repositories
  • Run X apps within the container
  • Networking
  • A disk storage device

Gather data

In order to set up the profile properly, we must first collect your workstation’s network adapter, and the user/group ID for your account.  Find your network adapter using the ip addr show command:

~:$ ip addr show
1: lo:  mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: wlp2s0:  mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether a8:00:0b:c0:88:e7 brd ff:ff:ff:ff:ff:ff
3: enx001a98a552d4:  mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:1a:98:a5:52:d4 brd ff:ff:ff:ff:ff:ff
    inet brd scope global noprefixroute enx001a98a552d4
       valid_lft forever preferred_lft forever

The above example shows three network adapters: loopback, wireless and ethernet (respectively). Select the adapter to use for the container; for this case we will use the wired adapter enx001a98a552d4

To find the id of your non-root account, use the id command:

~:$ id
uid=1001(sid) gid=1001(sid) groups=1001(sid),4(adm),24(cdrom),27(sudo), ...

In the example above my user id “sid” has a uid and gid of 1001.

Create the ROS Profile

Armed with these two key facts, we can create and edit the LXD profile for our ROS containers:

lxc profile create ros
lxc profile edit ros

This brings up a default profile template for editing within vi. Update the file as follows, then save and exit vi:

  environment.DISPLAY: :0
  raw.idmap: both [your group id] 1000
  user.user-data: |
      - "apt-key adv --fetch-keys ''"
      - "apt-add-repository ''"
      - "apt-add-repository ''"
description: ROS
    path: /tmp/.X11-unix/X0
    source: /tmp/.X11-unix/X0
    type: disk
    name: eth0
    nictype: macvlan
    parent: [your network adapter]
    type: nic
    path: /
    pool: default
    type: disk
name: ros

Let’s break down the configuration file and look at each part individually.

Configure the environment

The following line sets the DISPLAY environment variable required by X. The display within the container is always mapped to ”’:0”’.

  environment.DISPLAY: :0

Our linux containers will run under the security context of the current user, but all work within the Ubuntu containers will be done under the default ubuntu user account. The raw.imap setting maps our workstation’s user and group ID (1001 in this example) into the container’s user and group ID (always 1000 for the default user):

  raw.idmap: both 1001 1000

Configure ROS repositories

The ROS software repositories can be added to the container every time a new container is launched. The simplest way to achieve this is to use cloud-init, and add a runcmd to the user-data section of the profile. Each of these commands will be executed whenever a new container is initialized using this profile.

The apt-key command pulls the ROS distribution signing key from github, while the two add-repository commands add the ROS 1 and ROS 2 software repositories.

  user.user-data: |
      - "apt-key adv --fetch-keys ''"
      - "apt-add-repository ''"
      - "apt-add-repository ''"

Configure devices

Adding the X0 device to the profile allows X data to flow between the container (“path”) and the host (“source”). If you have multiple graphics cards check the contents of /tmp/.X11-unix to make sure you’re mapping to the correct source; the source should mirror the $DISPLAY environment variable from your host.

    path: /tmp/.X11-unix/X0
    source: /tmp/.X11-unix/X0
    type: disk

If you have a separate graphics card with a discrete GPU, you may also find it necessary to add the GPU as a device:

    type: gpu
   name: gui

To learn more about running graphical apps in LXD containers, including some caveats when working with an NVidia GPU, take a look at this blog post by Simos Xenitellis.

Multiple options exist for networking. Although a container can use LXD’s built in address translation or a bridge device, our profile will use the macvlan network driver. Macvlan creates a simple bridge between the parent network adapter and the container so the container receives its own IP address on the host network. With this setup no additional configuration (e.g., port forwarding) is required for either ROS 1 or ROS 2.

The network interface (nic) device configured here uses macvlan to connect the parent network adapter enx001a98a552d4 to the container’s eth0 interface:

    name: eth0
    nictype: macvlan
    parent: enx001a98a552d4
   type: nic

Take a look at this post if you need more information about LXD networking with macvlan.

A disk image must be connected to the container. This disk device simply uses the lxd default data pool:

path: /
pool: default
type: disk

Launch a container

The lxc launch command is used to launch a container for the first time. Use the following command to create and run an Ubuntu 20.04 container named “ros-foxy”:

lxc launch -p ros ubuntu:20.04 rosfoxy

Once the container is running, logging in is achieved by simply executing a shell within the container:

lxc exec rosfoxy -- bash

This connects to a shell on the container under the root userid; however, our configuration uses the ubuntu user account. In order to connect to a shell as the ubuntu user, execute the su command within the container:

lxc exec ros -- su --login ubuntu

This tends to be a bit cumbersome to type for every connection to the container. LXD aliases provide an easy way to simplify the command, and also make it more generally applicable. Shorten the command and make it more robust by creating an LXD alias using this command:

lxc alias add ubuntu 'exec @ARGS@ --user 1000 --group 1000 --env HOME=/home/ubuntu/ -- /bin/bash --login'

For more information about the options used with this alias, see this blog post from Simos Xenitellis.

Now connecting to an Ubuntu container with the proper context is as simple as the following command:

lxc ubuntu rosfoxy

Install ROS

Since the ROS repositories have been set up, installation is as simple as an apt-install command for the correct software bundle:

sudo apt install ros-foxy-ros-desktop

Since this container will always run ROS, we can source the ROS environment upon every login by adding it to our .bashrc startup script.

echo "source /opt/ros/foxy/setup.bash" >> ~/.bashrc
source ~/.bashrc

Most ROS commands support context-sensitive tab completion. Although not required, installing python3-argcomplete will make typing commands much easier:

sudo apt install python3-argcomplete

Now that the installation is complete, try it out by running rqt or a similar graphical application.


Using this ROS profile, building environments to work with different releases becomes trivial:

lxc launch -p ros ubuntu:20.04 rosnoetic
lxc ubuntu rosnoetic
sudo apt install ros-noetic-desktop-full

LXD provides a number of handy commands for working with containers. For instance we can clone a container by simply using the lxc copy command:

lxc copy rosfoxy rosfoxy-2

When work with the container is complete, simply remove it:

lxc delete rosfoxy-2

Share files between the host and the container, map USB devices–think robotics hardware–to the container with additional LXD configurations.

The possibilities for containers are only limited by your imagination. For some ideas on how to use LXD containers for ROS development, check out this blog post by Ted Kern. We’d appreciate any comments you have on your experiences using LXD containers with ROS!

Ubuntu cloud

Ubuntu offers all the training, software infrastructure, tools, services and support you need for your public and private clouds.

Newsletter signup

Select topics you're
interested in

In submitting this form, I confirm that I have read and agree to Canonical's Privacy Notice and Privacy Policy.

Are you building a robot on top of Ubuntu and looking for a partner? Talk to us!

Contact Us

Related posts

Migrating to Ubuntu LTS: six facts for CentOS users

Considering migrating to Ubuntu from other Linux platforms, such as CentOS? Think Ubuntu- the most popular Linux distribution on public clouds, data centre...

Use Amazon ECR Public and EKS-D to deploy LTS Docker Images

It’s re:invent season already, and we had exciting news to announce with Amazon this year. With all these remote sessions, what’s better than a quick lab to...

The State of Robotics – November 2020

Goodbye Thanksgiving (well, for some of us), hello Christmas! The holiday season really is the best, and it always brings interesting robotics news, which we...