Building Containerized GUIs on LXC with Pantavisor

Pantavisor Linux is an embedded framework for building, maintaining, and managing LXC containers. It presents an infinite number of possibilities for running applications at the smart device edge.

In this use case, we’ll describe how to run containerized GUI applications that communicate via a socket to a containerized version of Wayland Compositer.

Use Case

We will run two different applications for this example:

Both qt and flutter-example are built with Ubuntu containers that run in another Ubuntu container.

You can see the source code for the application containers here:

For the Wayland protocol and compositor, we selected weston. Weston can be described as the reference implementation of a Wayland compositor. It is also a very useful environment in and of itself.

With this, we now have the three base containers that will do the work of running the application and then displaying it on the monitor.

Before You Begin

You need a Raspberry PI 4, a display, an HDMI cable, and a mouse and keyboard connected to the Raspberry PI. By default, Weston does not start without a connected keyboard and mouse.

You can download a Pantavisor image and claim it at hub.pantacor.com. If you don’t have an account on hub.pantacor.com, create one and then claim the device by following this Getting started guide.

How this works

Because this demo is fairly straightforward, and also to make things simpler, you can clone and deploy my reference device directly to your device with:

pvr clone https://pvr.pantahub.com/highercomve/gui_rpi64
cd gui_rpi64
pvr post -m "Add weston, flutter example and qt example" YOUR_DEVICE_CLONE_URL

Those commands will push all the parts needed to your device so that you can see everything running. Inside the reference device is the following folder structure:

  • bsp
  • awconnect
  • pv-avahi
  • weston
  • flutter-example
  • qt

With all those containers we are going to build a communication system for the Wayland components that will work in this way:

You can read more about how Wayland works in their documentation about the architecture. But let’s dig into how this container works from the pantavisor perspective and who every container is built and how it works.

BSP

The bsp folder contains the Board Support Package. It is also where Pantavisor runs.

awconnect

This is the main container and platform for the system. It is very similar to the base OS for all running containers. The awconnect container is also the central point in the example app architecture with weston. Weston will use any sockets configured inside the awconnect container. This container will then pass the info down to the kernel, etc.

In this image, we can assume that BSP + awconnect forms the bottom layers.

Sharing sockets in the awconnect container

To share sockets running in the main container, you must configure the storage parameters inside the awconnect/src.json file.

First, mount a permanent volume inside the /var/run/dbus folder of awconnect. This allows you to read and write from any container that wants to send its data to the monitor.

To do that the awconnect/src.json should look as follows:

{
  "#spec": "service-manifest-src@1",
  "args": {},
  "config": {},
  "docker_digest": "registry.gitlab.com/pantacor/pv-platforms/wifi-connect@sha256:5c889720c6243408049b7a2f9ec75b9f4e376f6cbc28e39e091420a1e19df2aa",
  "docker_name": "registry.gitlab.com/pantacor/pv-platforms/wifi-connect",
  "docker_source": "remote,local",
  "docker_tag": "arm32v5",
  "persistence": {
    "/var/run/dbus": "boot"
  },
  "template": "builtin-lxc-docker"
}

How the Weston container is built

The Weston container is built with alpine and also includes all of the packages to make the Wayland Compositor work with Weston.

You can see the source of this container here

The Dockerfile generates the container. It will look exactly like any other normal Dockerfile, since there is nothing different that needs to be done to make a container run in Pantavisor.

However, the most important part is not the container itself, but rather the Pantavisor configuration inside the src.json file. It is here that we need to be able to read and write to the socket folder kept inside the awconnect container.

{
  "#spec": "service-manifest-src@1",
  "args": {
    "PV_LXC_EXTRA_CONF": "lxc.mount.entry = /volumes/awconnect/docker--var-run-dbus run/dbus none bind,rw,create=dir 0 0\n",
    "PV_RUNLEVEL": "platform"
  },
  "config": {},
  "docker_digest": "sha256:116cc7d42d3f7e513fad55688b618e61dcdae2dd37f8164a86eea80af00c03d9",
  "docker_name": "weston",
  "docker_source": "remote,local",
  "docker_tag": "latest",
  "persistence": {},
  "template": "builtin-lxc-docker"
}

All the docker_* parts of this file are generated using:

pvr app add --from=registry.gitlab.com/pantacor/pv-platforms/alpine-weston:arm64v8 weston

Adding Docker apps to Pantavisor

You can use the pvr app add command to add more Docker applications, and to basically convert any Docker container into a Pantavisor container (just a normal LXC container but with our tooling to make it easy to manage). See: Adding Apps from Docker for more information.

Communicating with the awconnect

To enable the weston container to talk to the awconnect system, the following two arguments were added:

1.- PV_LXC_EXTRA_CONF

This configuration allows the pvr commands to extend the default LXC generated configuration. It is necessary to add extra LXC configuration in this way because when pvr updates or installs the container it also needs to generate the LXC configuration file.

Here we configure the lXC container to mount the same volume of the /var/run/dbus on awconnect inside a /run/dbus folder with read/write permissions.

The route of the awconnect volume is defined in the run.json file for that container. We will mount it using the LXC configuration parameter lxc.mount.entry

You can read more about the LXC configuration in the documentation

The end result of that configuration will be:

lxc.mount.entry = /volumes/awconnect/docker--var-run-dbus run/dbus none bind,rw,create=dir 0 0

2.- PV_RUNLEVEL

The PV_RUNLEVEL argument configures the level of priority used to run the container. By default, all applications run in RUNLEVEL: app and all apps in that run level will wait for the platforms to start.

With this configuration, the container should run correctly and it will act as the wayland composer.

flutter-example and qt

And lastly, we have the containers that will run the GUI applications. As I mentioned before, they are built with this source code:

And the configuration for both of them will be similar to the weston platform where we are going to modify the args value of the src.json in order to mount the awconnect volume with the sockets and configure both containers to run on RUNLEVEL platform.

Display the results

After you’ve posted everything to your device and the device consumed the revision, the Raspberry Pi will restart. You will then see the System monitor application written on QT and the Flutter example of a counter clicker running on the Raspberry PI display.

Is important to know that you can install to a container any available application from your favorite Linux distribution (example: Ubuntu) with support for wayland. That means you will install more packages than it needs, but it is possible.

I recommend that you look inside of the flutter-example and the QT system monitor containers to see the dependencies needed for the applications to run.

Final Thoughts

If you have any questions or you want to know more about Pantavisor and hub.pantacor.com you can reach out to us on @pantahub on Twitter or join our Pantavisor Slack channel. We’d be happy to help.