Author: Tobit Flatscher (2021 - 2023)
When working with hardware that use the network interface such as EtherCAT slaves one might have to share the network with the host or remap the individual ports manually. One can automate the generation of the entries in the /etc/hosts
file inside the container as follows:
9 network_mode: "host"
10 extra_hosts:
11 - "${REMOTE_HOSTNAME}:${REMOTE_IP}"
In this case the two environment variables are defined inside a .env
file that is automatically passed to Docker Compose.
When sharing external devices such as USB devices one will have to share the /dev
directory with the host system as well as use device whitelisting as follows (for some applications Docker's --device
flag might be sufficient). An overview over the IDs of the Linux allocated devices can be found here. You will have to specify the Linux device drivers major and minor number. Inserting a *
will give it access to all major/minor numbers. See e.g. what this would look like for the Intel Realsense depth cameras:
9 volumes:
10 - /dev:/dev
11 device_cgroup_rules:
12 - 'c 81:* rmw'
13 - 'c 189:* rmw'
The association of these numbers and the devices is given in /proc/devices
.
You might also selectively mount only some USB devices:
9 volumes:
10 - /dev/some_device:/dev/some_device
11 - /dev/another_device:/dev/another_device
In case you are not quite sure which group a device belongs to you might also run the container with extended privileges as privileged
. Note that this is in generally not recommended as this basically removes all types of isolation and as such might pose a security risk in particular when being root
inside the container. In some rare cases this can't be avoided though (e.g. in the case of a Realsense camera with IMU that currently needs write access to /sys
which is not possible in an unprivileged container).
If we have no idea what device_cgroup_rules
we might need for a particular device but we have the device at hand we might do the following to find out.
In Linux differentiates between busses (the busses on the motherboard that a device is attached to, e.g. the output of $ lsusb
) and device files (located inside /dev
that are used to abstract standard devices and communicate with the corresponding physical or virtual devices). Depending on the type of the data that is exchange one differentiates between block b
and character c
devices (for more information see e.g. here). For the busses there exist readily available tools like lsusb
that are able to translate the vendor and product codes into human-readable strings while I am not aware of such a tool for device files.
So in order for finding out which device files (and as a consequence which device drivers major and minor numbers) belong to a device, you either have to plug the device in an out comparing the changes in device files or find out which device files belong to which bus.
The easier way (that also works in some corner cases like virtual devices) is to simply read all devices files, plug the device in or out and read again all devices files and compare them to the ones prior:
$ ls -l /dev > before.txt # After this command plug the device in
$ ls -l /dev > after.txt
$ diff before.txt after.txt
...
crw-rw---- 1 root dialout 188, 0 Feb 4 16:00 ttyUSB0
...
This outputs directly the major (in this case 188) and minor (in this case 0) device driver numbers (in this case for a Life Performance Research LPMS-IG1 IMU).
Sometimes you can already guess from the given date which of the device files belong to your device.
This means I will add a device_cgroup_rule
that looks as follows:
9 volumes:
10 - /dev:/dev
11 device_cgroup_rules:
12 - 'c 188:* rmw'
Do not consider the minor version as it might change depending on what other devices are connected.
Another way of doing so is to connect the information from lsusb
to the device file using the vendor and product information. There are a couple of ways to associate a given device and its /dev
path.
The probably easiest way is to simply run the following script
#!/bin/bash
for sysdevpath in $(find /sys/bus/usb/devices/usb*/ -name dev); do
(
syspath="${sysdevpath%/dev}"
devname="$(udevadm info -q name -p $syspath)"
[[ "$devname" == "bus/"* ]] && exit
eval "$(udevadm info -q property --export -p $syspath)"
[[ -z "$ID_SERIAL" ]] && exit
echo "/dev/$devname - $ID_SERIAL"
)
done
This should already output something like
...
/dev/ttyUSB0 - Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_IG1232600680056
...
I normally do the following manually: I first check the connected devices with lsusb
, potentially unplugging and plugging it in again to see which is which:
$ lsusb -tvv
...
/: Bus 03.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/15p, 480M
ID 1d6b:0002 Linux Foundation 2.0 root hub
/sys/bus/usb/devices/usb3 /dev/bus/usb/003/001
|__ Port 5: Dev 2, If 0, Class=Hub, Driver=hub/4p, 480M
ID 17aa:1034
/sys/bus/usb/devices/3-5 /dev/bus/usb/003/002
|__ Port 4: Dev 8, If 0, Class=Vendor Specific Class, Driver=cp210x, 12M
ID 10c4:ea60 Silicon Labs CP210x UART Bridge
/sys/bus/usb/devices/3-5.4 /dev/bus/usb/003/008
...
You might be interested in more details about the device $ lsusb -d 10c4:ea60 -v
.
To see which /dev
path is associated with it I will check the output of:
$ ls -l /sys/bus/usb-serial/devices # And /sys/bus/usb/devices
total 0
lrwxrwxrwx 1 root root 0 Feb 4 16:00 ttyUSB0 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-5/3-5.4/3-5.4:1.0/ttyUSB0
$ grep PRODUCT= /sys/bus/usb-serial/devices/ttyUSB0/../uevent
PRODUCT=10c4/ea60/100
This means that ttyUSB0
is assocated to 10c4:ea60
which is the device we are looking for.
Now I will check the associated major and minor number with:
$ ls -l /dev
...
crw-rw---- 1 root dialout 188, 0 Feb 4 16:00 ttyUSB0
...
In my case major 188
, minor 0
for ttyUSB0
, which is my device 10c4:ea60
. Then proceed to add the major number to your docker-compose.yml
like outlined above.
For mounting storage devices such as USBs and external hard drives it is sufficient to mount /media
or one of its specific subfolders as a volume:
9 volumes:
10 - /media:/media