Docker - a way to give access to a host USB or serial device?

266,988

Solution 1

There are a couple of options. You can use the --device flag that use can use to access USB devices without --privileged mode:

docker run -t -i --device=/dev/ttyUSB0 ubuntu bash

Alternatively, assuming your USB device is available with drivers working, etc. on the host in /dev/bus/usb, you can mount this in the container using privileged mode and the volumes option. For example:

docker run -t -i --privileged -v /dev/bus/usb:/dev/bus/usb ubuntu bash

Note that as the name implies, --privileged is insecure and should be handled with care.

Solution 2

With current versions of Docker, you can use the --device flag to achieve what you want, without needing to give access to all USB devices.

For example, if you wanted to make only /dev/ttyUSB0 accessible within your Docker container, you could do something like:

docker run -t -i --device=/dev/ttyUSB0 ubuntu bash

Solution 3

--device works until your USB device gets unplugged/replugged and then it stops working. You have to use cgroup devices.allow get around it.
You could just use -v /dev:/dev but that's unsafe as it maps all the devices from your host into the container, including raw disk devices and so forth. Basically this allows the container to gain root on the host, which is usually not what you want.
Using the cgroups approach is better in that respect and works on devices that get added after the container as started.

See details here: Accessing USB Devices In Docker without using --privileged

It's a bit hard to paste, but in a nutshell, you need to get the major number for your character device and send that to cgroup:

189 is the major number of /dev/ttyUSB*, which you can get with 'ls -l'. It may be different on your system than on mine:

root@server:~# echo 'c 189:* rwm' > /sys/fs/cgroup/devices/docker/$A*/devices.allow  
(A contains the docker containerID)

Then start your container like this:

docker run -v /dev/bus:/dev/bus:ro -v /dev/serial:/dev/serial:ro -i -t --entrypoint /bin/bash debian:amd64

without doing this, any newly plugged or rebooting device after the container started, will get a new bus ID and will not be allowed access in the container.

Solution 4

I wanted to extend the answers already given to include support for dynamically connected devices that aren't captured with /dev/bus/usb and how to get this working when using a Windows host along with the boot2docker VM.

If you are working with Windows, you'll need to add any USB rules for devices that you want Docker to access within the VirtualBox manager. To do this you can stop the VM by running:

host:~$ docker-machine stop default

Open the VirtualBox Manager and add USB support with filters as required.

Start the boot2docker VM:

host:~$ docker-machine start default

Since the USB devices are connected to the boot2docker VM, the commands need to be run from that machine. Open up a terminal with the VM and run the docker run command:

host:~$ docker-machine ssh
docker@default:~$ docker run -it --privileged ubuntu bash

Note, when the command is run like this, then only previously connected USB devices will be captures. The volumes flag is only required if you want this to work with devices connected after the container is started. In that case, you can use:

docker@default:~$ docker run -it --privileged -v /dev:/dev ubuntu bash

Note, I had to use /dev instead of /dev/bus/usb in some cases to capture a device like /dev/sg2. I can only assume the same would be true for devices like /dev/ttyACM0 or /dev/ttyUSB0.

The docker run commands will work with a Linux host as well.

Solution 5

The Safe and Proper way of accessing tty devices without --privileged mode

Just follow the instruction line by line, all steps are explained

Idea is to configure cgroup rules properly. First of all, lets find cgroup properties of your USB device. Run following command:

$ ls -l /dev/ | grep ttyUSB
crw-rw-rw-  1 root  dialout 188,   0 Mar  1 18:23 ttyUSB0 #Example output

Based on the output you can see that the major group of tty devices is 188 in my case, so I will proceed with that.

You can run docker image allowing access to range of devices with specific major number, docker will add required rules for you in your host machine (this will run docker in detached mode, we will attach to it later):

docker run --device-cgroup-rule='c 188:* rmw' -itd --name my_container ubuntu

Now the idea is to add a script which would be run every time your USB device is plugged in or plugged out. Some explanation about custom rules here and here on passing arguments. On ubuntu, you should create file /etc/udev/rules.d/99-docker-tty.rules as superuser (sudo):

ACTION=="add", SUBSYSTEM=="tty", RUN+="/usr/local/bin/docker_tty.sh 'added' '%E{DEVNAME}' '%M' '%m'"
ACTION=="remove", SUBSYSTEM=="tty", RUN+="/usr/local/bin/docker_tty.sh 'removed' '%E{DEVNAME}' '%M' '%m'"

This file adds new entry to your rules, basically saying: Every time tty device is plugged in - add or plugged out - remove run the provided script and pass some parameters. If you want to be more specific, you can use udevadm info --name=<device name> to find other parameters by which you can filter devices. You can test the rules as suggested here. To apply those rules:

root@~$ udevadm control --reload 

Now we need to create following script in /usr/local/bin/docker_tty.sh also as superuser (sudo). You can see it was set to be run in udev rules we created previously.

#!/usr/bin/env bash  
echo "Usb event: $1 $2 $3 $4" >> /tmp/docker_tty.log        
if [ ! -z "$(docker ps -qf name=env_dev)" ]                                     
then                                                                            
if [ "$1" == "added" ]                                                          
    then                                                                        
        docker exec -u 0 env_dev mknod $2 c $3 $4                               
        docker exec -u 0 env_dev chmod -R 777 $2                                
        echo "Adding $2 to docker" >> /tmp/docker_tty.log                
    else                                                                        
        docker exec -u 0 env_dev rm $2                                          
        echo "Removing $2 from docker" >> /tmp/docker_tty.log            
    fi                                                                          
fi 

This script will create the tty device in your running docker container, or delete it depending if the device was plugged in or plugged out (similar to whats happening with the Ubuntu machine - every time the device is plugged in, you can see it under /dev/ directory). Tip: Check the file /tmp/docker_tty.log for some debug output on your host machine, also, debug bash script as suggested here.

Dont forget to make script executable:

root@~$ chmod +x /usr/local/bin/docker_tty.sh

Now attach to the docker and see if the device appears in /dev/ directory when you plug it in and out:

docker exec -it my_container bash
Share:
266,988

Related videos on Youtube

n99
Author by

n99

I tinker... I enjoy coding too I guess.

Updated on November 03, 2021

Comments

  • n99
    n99 over 1 year

    Last time I checked, Docker didn't have any means to give container access to host serial or USB port. Is there a trick which allows doing that?

  • wligtenberg
    wligtenberg over 7 years
    just note that the device cannot be a symlink at the moment. github.com/docker/docker/issues/13840
  • DanCat
    DanCat over 7 years
    using the --device flag, how do I determine which /dev/<device> is the associated Android device on the host machine, especially when using Docker Quickstart Terminal (VirtualBox Host) for Windows or Mac?
  • Brad Grissom
    Brad Grissom about 7 years
    This works well if your device name never changes. But if you are using something dynamic which uses devices within /dev/bus/usb then this won't work because the device name changes when you plug and unplug it. You'll need the above -v (volumes) solution instead.
  • Art
    Art about 7 years
    Do not need -v - privileged already means access to all devices
  • Pascal
    Pascal about 7 years
    Is there any mechanism like this for the Windows docker client?
  • kashesandr
    kashesandr about 7 years
    Using this solution I don't see devices from a docker container... Here are details stackoverflow.com/questions/37213812 of my problem. Appreciate of any help! Thanks.
  • C. Reed
    C. Reed almost 7 years
    @DanCat udev rules can ensure that your device mounts to a static path
  • Franklin Dattein
    Franklin Dattein over 6 years
    Still doesn't work if the USB device is connected after Docker is already running.
  • Franklin Dattein
    Franklin Dattein over 6 years
    I mean, it doesn't map the device under /tty/USBX despite lsusb being able to list it.
  • Franklin Dattein
    Franklin Dattein over 6 years
    --device=/dev/ttyUSB0 only works if the USB device was plugged before Docker started. Even so, if you unplug it doesn't auto detect and crash nodeserial library from node and probably libs in other programming languages.
  • Evgeny
    Evgeny over 6 years
    You can map USB devices in Windows, since you would most likely be using either VirtualBox or HyperV (Docker Windows) there is a possibility to map the USB device into the Docker host - and then follow the run command in this answer.
  • Arturas M
    Arturas M over 5 years
    Why would anyone be interested in having access to just one usb device?? Usb devices are meant to be connected qnd disconnected and that needs to be done during the apps runtime. USB is not SATA or something, you can't expect something to always be there... And I don't think people just start apps via docker for single runs and quit them as soon as the usb devices are disconnected, right? I'd imagine more like service type apps, not single run jars... But thanks, indeed that might help some for which that very limited scenario would suit
  • user482963
    user482963 over 5 years
    how will this work in windows 10? I have usb device that I want to access in docker image
  • jemlifathi
    jemlifathi over 5 years
    @ben-whaley Your first approach worked for me with Ubuntu 14.04 (already tried mapping /dev/bus/usb and it didn't work), but both methods didn't work on Ubuntu 16.04... Can anyone help me figuring it out
  • Ben Whaley
    Ben Whaley over 5 years
    You’d need to give a bit more info about the error for anyone to help!
  • IsaacS
    IsaacS about 5 years
    @arturas-m e.g. Lots of sensors are USB these days. Sensors don't need to be plugged out in many usecases IMO.
  • glerYbo
    glerYbo over 4 years
    It won't work on macOS as the daemon runs inside a VM.
  • Marc Merlin
    Marc Merlin about 4 years
    priviledged is a terrible option to use for security, even though yes, it works.
  • Jose Cabrera Zuniga
    Jose Cabrera Zuniga almost 4 years
    If used for programing stuff like Arduino related stuff this solution is good
  • daniele piscaglia
    daniele piscaglia over 2 years
    Even tought this is the most upvoted answer and easiest solution, it is not proper for a production environment. For a more secure approach I suggest to check the awesome explanetion of Wout_bb just below.
  • CharlesB
    CharlesB over 1 year
    Awesome answer! Thanks a lot for putting this so clearly
  • DimanNe
    DimanNe over 1 year
    Should not there be docker exec -u 0 $id mkdir -p (dirname $usb_path) before docker exec -u 0 $id mknod $usb_path c $argv[3] $argv[4]?

Related