dockerdocker-composeusbserial

Mount one of several possible devices with docker compose


I have a containerized application for processing data from a sensor. The sensor has one of several possible serial-to-USB converters, each with their own path (/dev/path/to/deviceA, etc.). I'd like the container to detect which converter is plugged in (and not tied to an existing container) and mount that one (without the user explicitly telling it). Only the mount path changes for different converters; the rest of the application behaves the same.

Desired behavior

When starting the container, check each possible device mount to see if it finds a device at /dev/path/to/device. Mount to the first one in the list found and ignore the rest. If no devices are found, build error. In other words,

if (/dev/path/to/deviceA exists and available) {

    mount "/dev/path/to/deviceA:/dev/tty/USB0"

} else if (/dev/path/to/deviceB exists and available) {

    mount "/dev/path/to/deviceB:/dev/tty/USB0"

} else if (...

...

} else { // No known devices are plugged in

    fail to build

}

What I've tried

Current docker-compose.yml has

devices:
  - "/dev/path/to/deviceA:/dev/ttyUSB0"
  - "/dev/path/to/deviceB:/dev/ttyUSB0"
  - "/dev/path/to/deviceC:/dev/ttyUSB0"

However, my use case is for detecting one of several possible devices. In such a case, attempting to build the container gives error gathering device information while adding custom device because deviceA, deviceB, and deviceC are not all plugged in at the same time.


It seems like one possibility is to volume mount the entire /devdirectory:

If you want to allow the container to manage devices, you can use the volumes sestion and mount /dev and also add privileged: true or set proper capabilities.

but this is only a partial solution. How would I get it to "select" a correct device from the entire /dev once it's mounted?


Another possibility might putting a path (according to the devices present) into an environment variable, then passing that variable to devices. But when/how would one manipulate the environment variable before it is used with docker compose build name-of-service/docker compose run name-of-service?

Context

Ubuntu 22.04 (host and container) (would like to be deployable to other OS's)

Client/Server: Docker Engine - Community Version: 27.3.1

Docker Compose version v2.29.7


Solution

  • Compose has no ability to run dynamic checks, and extremely limited ability to do any sort of conditional logic. The best you'll be able to do here is to use its environment-variable substitution to fill in the device path

    devices:
      - ${DEVICE_PATH}:/dev/ttyUSB0
    

    You'll need some sort of script or program outside of Compose to detect this. I might write a shell script like

    #!/bin/sh
    
    is_device_available() {
      # not a character-special device
      if [ ! -c "$1" ]; then return 1; fi
    
      # ... your logic here ...
      return 0
    }
    
    for d in /dev/path/to/deviceA /dev/path/to/deviceB /dev/path/to/deviceC; do
      if is_device_available "$d"; then
        DEVICE_PATH="$d"
        break
      fi
    done
    
    if [ ! "$DEVICE_PATH" ]; then
      echo could not detect a device
      exit 1
    fi
    
    export DEVICE_PATH
    exec "$@"
    

    The end of this script will run the command it's given, so you could run it like

    detect-device docker compose up
    

    You could also use it to change the current shell's environment, like

    . detect-device
    docker compose up
    

    Note that each time it's run it will try to detect devices again, and if running a container makes one of the devices be "in use" then it will change the device path, which will recreate the container.

    detect-device docker-compose up -d
    # If that makes the first device be "in use" then this will
    # recreate the container on the second device
    detect-device docker-compose up -d