Using USB with Docker for Mac

If you’ve been struggling with this issue and just want an answer, skip to the bottom for the TL;DR. I won’t fault you for it.


Docker is a great tool for deploying web services, but one of my favorite uses for it is standardizing toolchains. Instead of everyone on the team needing to setup their development environments identically, and them keeping them in sync, you define the build tools in a single place, a docker image. Then everyone, including your build server, uses that single pre-packaged image.

Not only is this great for teams, but it’s also fantastic when you have side projects that you only periodically revisit. Without fail, when I come back to some old project, I’ve seen updated my tools for something newer, and I can’t build that old project without either upgrading the project or degrading my tools. Leaving a build tools image behind means I can just pick it up and work on it without spending a day getting back up and running.

It’s not all sunshine and roses though. I went on quite an adventure today. Last year I put on a TDD for Arduino workshop. I had started to create a Docker image for AVR development,  but ran into problems when it came time to flash the program to the board. Exposing a USB port to a docker container on Mac isn’t exactly a trivial task (until you know how at least!). For that session we mobbed, so I only had to setup one machine. I just stopped fighting with it and went with a regular install of the tools on my machine.

Recently though, I’ve taken a renewed interest in getting this to work properly. First, I’ve been playing with ARM development, but there’s a bug in AVR and ARM’s compiler packaging that means you can’t have both toolchains installed at the same time. Having these toolchains containerized means I can easily keep both readily available. Secondly, I’m now beginning to build on that workshop to turn it into an “Intro to Bare Metal Programming” course. For that, I really need to be able to hand folks an environment I know works, so we’re not spending more time working kinks out of dev setups than learning. Also, in order to standardize embedded toolchains for a team or client at work, I really need to know how to get USB working on Mac.

If you’re running Linux, this is as simple as adding --device /dev/ttyUSBx to the docker run command. Needless to say, it’s not that simple on OSX or Windows. That’s because the docker daemon only runs natively on Linux. For other operating systems it’s run in a hypervisor or virtual machine. In order to expose the port to the container, you first have to expose it to the virtual machine where Docker is running. Unfortunately, hyperkit, the hypervisor that Docker-For-Mac uses doesn’t support USB forwarding.

Since we can’t expose a USB port to the native Mac Docker hypervisor, we have to fallback onto docker-machine, which uses a Virtualbox VM to host the dockerd daemon. There are great instructions for setting up docker-machine with a USB filter, but I was getting a lot of mysterious segfaults from docker-machine that would leave my VM running, but also unable to recover a connection to it through docker-machine. It turns out that several versions of VirtualBox had a bug causing the segfaults. Upgrading to v6.06 solved that problem, but I still couldn’t see the device. It took me too long to remember that I had the same trouble with USB 3.0 a few months ago with a Windows guest OS. Dropping down to USB 2.0 fixed that issue.

Okay, let’s get down to business and get a fully containerized embedded toolchain running on Mac.

First, download and install VirtualBox version 6.06 or greater. Again, this must be version 6.06 or greater or you’ll see segfaults when trying to create a USB filter later. You can optionally install the VirtualBox extension pack. I recommend it though, because it enables USB 2.0, which results in faster programming times.

Next, we can create and setup our docker-machine (virtual machine).

#! /bin/bash
#create and start the machine
docker-machine create -d virtualbox default

#We must stop the machine in order to modify some settings
docker-machine stop

#Enable USB
vboxmanage modifyvm default --usb on

# OR, if you installed the extension pack, use USB 2.0
vboxmanage modifyvm default --usbehci on

# Go ahead and start the VM back up
docker-machine start

# Official Arduinos and many clones use an FTDI chip.
# If you're using a clone that doesn't, 
# or are setting this up for some other purpose
# run this to find the vendor & product id for your device.
# vboxmanage list usbhost

# Setup a usb filter so your device automatically gets connected to the Virtualbox VM.
vboxmanage usbfilter add 0 --target default --name ftdi --vendorid 0x0403 --productid 0x6015

#setup your terminal to use your new docker-machine
#(you must do this every time you want to use this docker-machine or add it to your bash profile)
eval $(docker-machine env default)

Now go ahead and plug in your device and run this command to verify that containers can see it.

docker run --rm -it --device /dev/ttyUSB0 ubuntu:18.04 bash -c "ls /dev/ttyUSB0"

If the command fails, make sure your device is plugged in and visible to the VM. You may have mistyped the vendor and product ids, or the tty may be attached under a different number.

docker-machine ssh default "ls /dev/tty*"

That’s it. Like I said, it’s really easy once you know how. Unfortunately, there’s not official documentation and, considering that both docker-machine and boot2docker are in maintenance mode, I’m hoping we get official support for USB on hyperkit in the future. Containerizing build tools is a great way for teams to take advantage of the technology even if you’re not using them to deploy services. Now, if you’ll excuse me, I need to go update my AVR toochain image with a script to do this and add in avrdude for uploading programs.

Until next time,
Semper Cogitet

, , , ,

  1. #1 by Ravichandra Hadagali on May 20, 2020 - 9:35 am

    Hi,
    Did all the configuration but still the docker is not able to access the deice. Connected usb camera to a mac machine, running ubuntu image, Can i get the command to run container?
    using command
    docker run -it –name=con_name –device=/dev/:/dev/ ubuntu /bin/bash

    Like

    • #2 by Christopher J. McClellan on May 20, 2020 - 11:13 am

      First off, you’re not specifying a device, you’re giving it all of the devices. I’m not sure that will work, but if it does, it’s really insecure. Secondly, I mostly abandoned this approach. It only works with specific versions of VirtualBox and then it only works with *some* devices. FTDI serial chips seem to work consistently, but I’ve never gotten official Arduinos to work with this method and have never tried a USB camera with this method. I have worked with USB cameras in virtual box with a MacOS host though and that in and of itself was a nightmare. There is some process running on Mac that tries to grab the camera all the time. I would consistently get connected to the camera once, then only ever get a black screen after that. Your best bet is to run Linux as your host OS.

      Like

  2. #3 by Ravichandra Hadagali on May 21, 2020 - 3:11 pm

    Hi @christopher thank you for the response. Just want know, when we select a particular device in virtualbox, where I can see the reflection of it, can i see it in a docker container file system? Because i need to give a absolute device path to –device flag. And what exactly i need to give to –device flag? I thought we need to give device file in /dev/. If so, how to see what device file for a particular device?

    Like

  3. #4 by KWC Coin on August 9, 2020 - 8:19 am

    Thanks. One major issue not resolved

    First,

    a) one has to install docker-machine first.

    see [](https://stackoverflow.com/questions/60078434/docker-machine-command-not-found)

    b) after the command and run yours have a problem as macOS got the serial:

    VendorId: 0x0403 (0403)
    ProductId: 0x6010 (6010)
    Revision: 7.0 (0700)
    Port: 2
    USB version/speed: 0/High
    Manufacturer: Lattice
    Product: Lattice FTUSB Interface Cable
    Address: p=0x6010;v=0x0403;s=0x00004f5056206675;l=0x14200000
    Current State: Busy

    and hence whilst macOS can see it as

    /dev/tty.usbserial-14200
    /dev/tty.usbserial-14201

    but not the docker-machine

    Any help?

    Like

    • #5 by Christopher J. McClellan on August 9, 2020 - 11:45 am

      Did you set up a USB device filter for virtual box?

      vboxmanage usbfilter add 0 –target default –name ftdi –vendorid 0x0403 –productid 0x6010

      Note that your device has a different product id than the one I used in the article.

      Like

      • #6 by mauricemccabe on November 13, 2022 - 1:37 am

        I have a similar problem. I used the correct ‘vboxmanage usbfilter’ for my device, but it still does not show-up in the VM.

        On the host the device shows up as two devices:
        /dev/cu.usbmodem1A111 /dev/tty.usbmodem1A111

        Like

      • #7 by Christopher J. McClellan on November 13, 2022 - 10:42 am

        I’ve ultimately found this to be unreliable at best.

        Like

  4. #8 by nucutusula on March 9, 2023 - 5:16 pm

    How would you do this on the latest Macs with M1 Chip where Virtualbox is not supported?

    Like

    • #9 by Christopher J. McClellan on March 9, 2023 - 9:08 pm

      No idea to be honest. We’ve had nothing but trouble with the M1s at work. I use a *nix box these days.

      Like

Leave a comment