Docker How to set up USB hotplug for a USB device

eye-catch Docker

I needed to configure Docker container that can recognize a USB device. I used --device option for the first release. It mounts a specific path device to the container. However, it will never recognize it again when the USB device is disconnected/connected again while the container is running.

I needed to fix this problem. Namely, I have to set up Docker container to let the container recognize the USB that is connected while the container is running.

It took me a whole day to find out the solution. I hope this post helps you out. By the way, the Docker container runs on a Linux system. If you need to try a USB device in a container on Linux system but your host system is Windows, you need to use WSL or VM.

This might help if you use WSL but some devices are not available. Use VirtualBox if your device is not available in WSL.

See this post to set up USB2.0/3.0 in VirtualBox.

Sponsored links

Combination of mount and device-cgroup-rule

Let’s look at the solution first. Add the following two options at the same time.

--mount=type=bind,source=/dev/bus/usb,target=/dev/bus/usb
--device-cgroup-rule=a *:* mrw

Setting --device-cgroup-rule=a *:* mrw means that it allows all resources to Docker. It’s not a good idea. This will be explained later.

Sponsored links

Device option can’t be used for USB hotplug

The device that I used was assigned under /dev/bus/usb. It’s a scanner. When it’s connected, it’s assigned to /dev/bus/usb/001/002 for example. However, it’s assigned to /dev/bus/usb/001/003 when it’s disconnected and connected again.

When configuring --device=/dev/bus/usb:/dev/bus/usb, it can use USB devices that are already connected before the container starts. When a USB is newly connected, it can’t be opened due to a permission error. It shows an error like below. Privileged is added to for the existing devices but not for the newly added devices. That’s why permission error occurs.

open /dev/bus/usb/001/001: operation not permitted

Actually, --device option is not recommended to use for ephemeral devices.

The –device option cannot be safely used with ephemeral devices. You shouldn’t add block devices that may be removed to untrusted containers with –device.

https://docs.docker.com/engine/reference/commandline/run/#device

Automatic binding by mount option when USB is connected

To bind a new path to the container, the target path /dev/bus/usb needs to be bound.

I first used –volume but it’s better to use –mount because it doesn’t write anything there, doesn’t have to be shared with another container, and doesn’t need a backup. More detail for the good use case of volume/mount is described in the official page.

I tried all Linux capabilities by adding --cap-add option but it didn’t work.

Allow the target device by device-cgroup-rule

To make it work, the device needs to be an allowed device. It can be added by using --device-cgroup-rule.

--device-cgroup-rule=a *:* mrw

The first character is type. The possible values are as follows.

  • a: all
  • c: char
  • b: block

The next *:* is major:minor of a device number. Details including type are here.

The last one is rights.

  • m: mknod
  • r: read
  • w: write

It’s not a good idea to allow all types and devices because it leads to security risks. It’s necessary to find out which type the desired device is.

I executed ls -l /dev. Then, I disconnected the device followed by ls -l /dev again. Comparing the two results, I found the target device.

# when the device is connected
crw-rw-rw-  1 root tty       5,   2 Oct 25 08:43 ptmx
# after it's disconnected
crw-rw-rw-  1 root tty       5,   2 Oct 25  2023 ptmx

As you can see the numbers 5, 2 in the result above, they are the major and minor numbers for the device.

Since the device that I needed was a scanner, it should be enough to set read right with char --device-cgroup-rule=c 5:2 r but it actually didn’t work for some reason. It worked only when the type was set to all --device-cgroup-rule=a 5:2 r.

Overview

To bind the newly created path under /dev/bus/usb, the path needs to be bound. Then, the device needs to be added to the cgroup.

--mount=type=bind,source=/dev/bus/usb,target=/dev/bus/usb
--device-cgroup-rule=c 5:2 r

If it’s necessary to use the options in devcontainer in VSCode, the following configurations need to be added to devcontainer.json.

"runArgs": [
    "--device-cgroup-rule=a 5:* r",
],
"mounts": [
    "type=bind,source=/dev/bus/usb,target=/dev/bus/usb"
],

If it still doesn’t work as expected, it might be related to a user that is used in Docker container. Check which user is used in Docker container and which user owns the target path in the host machine.

If you also need a scanner in Golang, check the following post too.

Comments

Copied title and URL