Select the version of your OS from the tabs below. If you don't know the version you are using, run the command
cat /etc/os-release or
cat /etc/issue on the board.
Remember that you can always refer to the Torizon Documentation, there you can find a lot of relevant articles that might help you in the application development.
Torizon, as explained on the TorizonCore Technical Overview, provides the container runtime and Debian Containers for Torizon, among others listed on the List of Container Images for Torizon, to simplify the developer's life and keep the development model close to one of desktop or server applications. Even though we follow the desktop and server application standards as much as possible, some requirements are inherent to embedded systems development that are divergent. One typical example is hardware access.
Torizon also has its architecture-specific aspects of the system, like graphics and UI, that you should consider when developing your application or porting to an existing one. In this chapter, we will discuss some of those potential issues and how to handle it.
Developing applications for Torizon can be done using command-line tools or, as we recommend, the Visual Studio Extension For Torizon and Visual Studio Code Extension for Torizon. This article contains best practices that may apply in all situations using Torizon. Nevertheless, we focus on explaining how to do things on the Visual Studio Code Extension for Torizon.
This article complies to the Typographic Conventions for Torizon Documentation.
Running your application in a container means that it will run in a "sandboxed" environment.
This fact may limit its interaction with other components in the system. See below some examples of things that may not work when your application is running inside a container:
There are solutions for many of those scenarios and, usually, those aren't too hard to apply. In this article, we'll discuss some of them, keeping in mind best practices.
Containers are transient by nature. Since a container can be destroyed and re-created, storing data inside the container's filesystem is not a good idea. It is also impossible to share such data with other containers, making it unsafely stored for all the containers(It disappear after removing the container) and hard to manage. See how to overcome this in the next sub-sections.
You can mount a directory from the host filesystem inside the container to store permanently non-volatile data on the SoM's flash memory. This technique is known as bind mount.
You may need to change or configure your application to store data in that location (or mount the folder where the application expects it).
Also, consider that the files' UID/GID will be shared with the underlying host OS, so using coherent IDs may help. Debian Containers for Torizon use the same users/UIDs and groups/GIDs from the base OS, keeping file permissions always clear.
You can also use docker volumes instead of providing an explicit path on the local filesystem. The runtime manages volumes created via the
docker volume command or the
In the Visual Studio Code extension, you can add additional bind mounts and volumes:
+icon next to the
volumeslist in the
,roafter the container's path.
Adding Bind Mounts and Volumes
By default, containers connect to the Docker's bridge" network. This configuration allows containers to communicate with the outside with no restrictions. However, it prevents them from being accessible from the outside. We cover three possible networking configuration, each recommended for specific situations:
It's possible to enable communication on specific ports using the
ports setting. To expose a port, you have to:
+sign next to
<port number>/<tcp/udp>, for example 8080/tcp.
There are scenarios where you may want your containers communicating only to each other on the same device. You can do it by creating a private docker network.
Containers on a private network are accessible with no restrictions, without needing to explicitly enable ports, but only if the containers are on the same network. This remark is important because you can create as many networks as you want and have one container on more than one private network.
For example, you may have a container exposing a REST API (backend) to a container that implements a web UI (frontend). The backend and frontend will be on the same private network. The frontend will also be on the bridge network, exposing the port used to serve webpages.
To use a private network, you have to create it on the device using the
docker network create command or defining it in docker-compose. Then you can add your container to that network:
+sign next to
docker network create <network>, type the
#%application.id%#_to the network name you set in the compose file.
Creating Private Networks
For some kinds of applications, typically those that need to use low-level UDP-based protocols, you may need to access the network using the same IP and configuration used by processes running natively on the host OS.
In this case, you should enable host network mode.
+sign next to
network_modeas the key and
hostas the value.
Using the Host Network
All the ports exposed by applications running in your container will be exposed directly on the host network interfaces in host mode. This also means that you won't be able to expose services on ports already used by the host (for example, port 22 for SSH).
Container technology is very popular in domains where direct access to the hardware is usually forbidden, like servers and cloud-based solutions. On the other side, software running on an embedded device will probably need to access hardware devices, for example, to collect data from sensors or to drive external machinery.
Docker provides ways to access specific hardware devices from inside a container, and often one needs to grant access from both a permission and a namespace perspective.
From a permission perspective, there are two ways to grant access to a container. You will hardly ever worry about those since they are either well documented or abstracted by our IDE Extensions. They are presented below:
Warning: It's strongly advised to avoid at any cost using Privileged mode for the execution of your container. That can lead to security flaws. Please search for ways to expose the required resource through cgroup, bind mounts or devices. If you are facing issues in setting up your resource from a container, feel free to contact us at Toradex Community.
From a namespace perspective, there are also two ways to grant access to a container. You must give them access on a per-peripheral basis.
Torizon uses a coherent naming for the most commonly used hardware interfaces. For instance, a family-specific interface will have a name corresponding to the family name used in the datasheet and tables across our other articles. This helps you write containers and applications that are pin-compatible in software - if you switch the computer on module for another.
Inside Torizon base containers, there is a user called
torizon mapped to several groups associated with hardware devices, including
input. That means using the
torizon user, it's not necessary to be root to access different hardware interfaces like sound cards, displays, serial ports, gpio controllers, etc. So when developing your application to run inside a container, run it with the
torizon user so the access to most hardware interfaces will work without requiring any additional privileges.
To share a device from the host OS into a container, you can:
+icon next to devices
Adding a Device
The device will be mapped to the same path inside the container and use the same access rights specified for the host. Since the default user on the Toradex containers is
torizon, and you should avoid using
root as much as possible to limit potential security issues, you may have to add your user to specific groups to enable access for different kinds of devices. Those groups are mirrored between the host OS and our Debian-based container images, making things more intuitive.
The groups that are currently supported are listed in the table below:
|gpio||allow access to the GPIO character device (/dev/gpiochip*), used by libgpiod|
|dialout||allow access to UART interfaces|
|i2cdev||allow access to I2C interfaces|
|spidev||allow access to SPI interfaces|
|audio||allow access to audio devices|
|video||allow access to graphics and backlight interfaces|
|input||allow access to input devices|
To add the
torizon user - or any other user - to groups, you can:
Editbutton on the property
RUN usermod -a -G <group 1>,<group 2>,... torizon
RUN usermod -a -G gpio,dialout torizon
Adding a User to Groups
Suppose your application needs to access devices that may be plugged/unplugged at runtime. In this situation, the static mapping will not work. There is no way, at the moment, to map a device into a running container. If you need to access this kind of device, the only solution is to mount the
/dev folder as a volume.
For devices that are not exposed through user groups, you can add its access through cgroup by the addition of device_cgroup_rules as
extraparms in the Torizon Extension.
Each device that is handled through cgroup is referenced (or whitelisted) by the following fields:
You can check more details about Device Whitelist Controller in the Kernel Documentation.
extraparms takes a key-value pair, which syntax shall match the Python API as specified in the Docker SDK for Python.
See for example the following addition for
extraparms for GPU access:
In case you want to check the major and minor numbers of a given device at /dev, you can do that by typing
ls -l /dev/<device>.
See below a few examples:
# ls -l /dev/tty0 crw--w---- 1 root tty 4, 0 Oct 6 09:32 /dev/tty0 # ls -l /dev/tty7 crw--w---- 1 root root 4, 7 Feb 23 19:43 /dev/tty7 # ls -l /dev/input/ total 0 drwxr-xr-x 2 root root 80 Oct 6 09:32 by-path crw-rw---- 1 root input 13, 64 Oct 6 09:32 event0 crw-rw---- 1 root input 13, 65 Oct 6 09:32 event1
Taking for example the devices presented above, you could register them in the
extraparms at device_cgroup_rules as:
There may be situations that cannot be easily worked around, and you need to run the application inside the container as
root. However, notice that you should still avoid running the container as privileged, especially in this scenario. Here are some examples:
The default choice for Graphic User Interface (GUI) in Torizon is to use the Wayland protocol. This requires a compositor that manages the screen and input devices and allows clients to access "surfaces" on the screen. Torizon provides a container with the Weston compositor and clear instructions about how to run it on Debian Containers for Torizon.
Suppose you want to run it automatically when debugging your application. In that case, you can create a docker-compose file inside your
appconfig folder and add its name to the
dockercomposefile configuration property.
You can find a sample of such a file on Debian Containers for Torizon.
Your container must communicate with the Weston compositor. This is done using a socket that, by default, is created under
/tmp as a volume in your container will let your application interact with Weston.
Many commonly used GUI-toolkits support Wayland as a rendering back end. Usually, you need to set an environment variable that configures rendering mode.
In the following table, you can find configuration settings for some popular toolkits:
To set an environment variable:
Custom Propertiesin the configuration view
Adding an Environment Variable
By using the Weston-based Debian Containers for Torizon, you can enable VNC or RDP remote access by simply adding an environment variable. Learn more on Remote Access the TorizonCore GUI Using VNC or RDP.