Docker Bluetooth and Bluez without --privileged --net=host

Hello,

This seems to be a fairly common topic, but I’ve not found a good answer.

I’m trying to use a Docker container in a CI/CD pipeline for some Hardware In Loop testing.

I have a raspberry pi running Docker, the Pi has a serial connection to a Bluetooth wearable type device and there are some test scripts that run inside the docker container that send serial commands to the bluetooth device which then communicates back to the raspberry pi via Bluetooth Low Energy.

I have built a python:3.9.13-bullseye container, this has the bluez python library, which when I run locally on my machine using the --priviledged and --net=host flags is able to communicate perfectly with the wearable device and my python scripts run and testing is all good.

I would now like to use this in our CI/CD pipeline, but I need to remove the --priviledged and --net=host flags to run it.

When I remove the --net=host option, the Bluez library spits the following error:

Traceback (most recent call last):
  File "/scanner.py", line 17, in <module>
    devices = scanner.scan(5.0)
  File "/usr/local/lib/python3.9/site-packages/bluepy/btle.py", line 852, in scan
    self.start(passive=passive)
  File "/usr/local/lib/python3.9/site-packages/bluepy/btle.py", line 790, in start
    self._mgmtCmd("le on")
  File "/usr/local/lib/python3.9/site-packages/bluepy/btle.py", line 309, in _mgmtCmd
    rsp = self._waitResp('mgmt')
  File "/usr/local/lib/python3.9/site-packages/bluepy/btle.py", line 366, in _waitResp
    raise BTLEManagementError("Management not available (permissions problem?)", resp)
bluepy.btle.BTLEManagementError: Management not available (permissions problem?)

scanner.py source is below.

from bluepy.btle import Scanner, DefaultDelegate

class ScanDelegate(DefaultDelegate):
    def __init__(self):
        DefaultDelegate.__init__(self)

    def handleDiscovery(self, dev, isNewDev, isNewData):
        if isNewDev:
            print ("Discovered device ", dev.addr)
        elif isNewData:
            print ("Received new data from ", dev.addr)

# prepare scanner
scanner = Scanner().withDelegate(ScanDelegate())

# scan for 5 seconds
devices = scanner.scan(5.0)

for dev in devices:
    print ("Device %s (%s), RSSI=%d dB" % (dev.addr, dev.addrType, dev.rssi))
    for (adtype, desc, value) in dev.getScanData():
        print ("  %s = %s" % (desc, value))

I’ve not been able to find a way to do this and was wondering if anyone can help? It seems to be a relatively common topic.

TIA!

This is one of the typical topics where I have an idea about the solution, but can’t verify the result myself. Normaly I avoid to post a response in such a case as I can’t realy help with follow up questions.

I still want to share what I think you should do next (2a and 2b are alternatives, pick one!):

  1. research the capabilities you python code needs

2a. either start your container with --cap-add folllowed by the capabilites it needs

2b. install the setcap package in your image and set the required capabilites in the entrypoint script when the container is starting (e.g. you could take a look at how the image pihole/pihole uses this approach)