Containers

In the application manager context, a container describes an execution environment for an executable: either an application's binary or its runtime binary, in multi-process mode. The container does not have to be something sophisticated like a Docker container, but can be as simple as a Unix process.

Predefined Containers

Bubblewrap Container

Spawns a process in a kernel namespace using the bubblewrap utility.

Process Container

Spawns a new Unix process to execute the requested binary.

Configuration

A container configuration has three parts:

  1. Configure which containers are available when loading the container plugins
  2. Add specific settings for each container integration available. See the container specific documentation for more information.
  3. Configure which container solution to select to run a specific application

Load Container Plugins

To configure an existing container plugin for use in the application manager, you need to add its full path to the list of plugins to load in the application manager's config file:

plugins:
  container: [ "/full/path/to/softwarecontainers.so", "/another/plugin.so" ]

Plugins installed into Qt's plugin directory into the appman_container folder are picked up automatically, but you still need to enable the usage of the container using the container selection configuration.

Container Selection Configuration

When you start an application from within the application manager, there are multiple ways to control which container integration is used:

  1. If the config file does not contain the containers/selection key, the container integration ID defaults to process.
  2. If the containers/selection key exists, its contents are parsed as a list of maps, where each map has a single mapping only. While this single mapping is awkward, it is necessary to preserve the order of the mappings. Each key is interpreted as a standard Unix wildcard expression that is matched against the application ID. The first match stops the algorithm and the mapping's value is used as the container integration ID. If no matches are found, the resulting container integration ID is an empty string.
    containers:
      selection:
      - com.pelagicore.*: "process"
      - com.navigation: "special-container"
      - '*': "softwarecontainers"  # a single asterisk needs to be quoted
  3. Afterwards, if the System UI did set the ApplicationManager::containerSelectionFunction property to a valid JavaScript function, this function is called with the first parameter set to the application's ID and the second parameter set to the container integration ID that resulted from step 1 and 2.
    ApplicationManager.containerSelectionFunction = function(appId, containerId) {
        var app = ApplicationManager.application(appId)
        if (app.capabilities.indexOf("non-secure") != -1)
            return "process"
        else
            return containerId
    }

Extend with Container Plugins

Custom container solutions can be added via plugins. These plugins need not to be built as part of the application manager, but they need to be built against a private Qt module to get the interface definition.

The SoftwareContainer Plugin Example can be used as a blueprint to either create a customer-specific production version of a SoftwareContainer plugin, or to integrate another container solution.

Following a brief introduction what steps needs to be done to build your own plugin. First you need to configure your build system.

Here's a snippet on how to do this with cmake:

...
find_package(Qt6 COMPONENTS AppManPluginInterfacesPrivate)
qt_add_plugin(mycontainer-plugin)
target_link_libraries(mycontainer-plugin PUBLIC Qt::AppManPluginInterfacesPrivate)
...

Here's the qmake version:

...
TEMPLATE = lib
CONFIG += plugin
TARGET = mycontainer-plugin

QT += appman_plugininterfaces-private
...

Then, you only have to implement two classes that derive from ContainerInterface and from ContainerManagerInterface respectively:

#include <QtAppManPluginInterfaces/containerinterface.h>

class SoftwareContainer : public ContainerInterface
{
    // ...
};

class SoftwareContainerManager : public QObject, public ContainerManagerInterface
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID AM_ContainerManagerInterface_iid)
    Q_INTERFACES(ContainerManagerInterface)

    // ....
};

Be aware that your container plugin has to support a few basic requirements to support UI clients in multi-process mode:

  1. The plugin has to be able to forward Unix local sockets into the container. This is needed for both the Wayland socket as well as for the private peer-to-peer D-Bus connection. If the plugin cannot map these sockets to the correct location within the container, the plugin then needs to modify the environment variables for the respective locations before passing them on to the container. The table below lists the relevant environment variables.
  2. To support hardware OpenGL acceleration, the container needs to have access to the necessary devices. For GPUs that follow Linux standards, such as Intel, make sure to have /dev/dri/* available within the container.
  3. You have to implement PID mapping in your plugin; unless your container solution shares its PID namespace with the rest of the system. This is necessary if you want to make use of the application manager's security features. Each connection coming into the application manager via the Wayland or D-Bus Unix local sockets is queried for the PID of the application that requests the connection. The application manager verifies these PIDs against the PIDs of all running applications via ContainerInterface::processId(). Connections that don't match a PID are not accepted. However, you can disable this behavior via the --no-security command line option.

The application manager uses the following environment variables to communicate various settings to the application. A custom container plugin must forward these variables or adjust them accordingly:

NameDescription
WAYLAND_DISPLAYThe path to the Wayland server socket. If your container uses its own filesystem namespace, make sure that this socket is forwarded accordingly.
QT_QPA_PLATFORMAlways set to wayland.
QT_IM_MODULENot set, but explicitly unset by the application manager. Make sure to leave it unset, to use the automatic Wayland input method implementation.
QT_SCALE_FACTOREmpty (unset), to prevent scaling of wayland clients relative to the compositor. Otherwise running the application manager on a 4K desktop with scaling would result in double-scaled applications within the application manager.
QT_WAYLAND_SHELL_INTEGRATIONSet to xdg-shell. This is the preferred wayland shell integration.
DBUS_SESSION_BUS_ADDRESSThe standard D-Bus session bus.
AM_CONFIGA YAML, UTF-8 string encoded version of the amConfig map.
AM_NO_DLT_LOGGINGTells the application to not use DLT for logging, if set to 1.

© 2023 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.