Rootless Podman + USB Zigbee dongle: “Permission denied” even with correct group membership — here’s what actually fixed it
Setup: Fedora Server, Home Assistant running as a plain Container install (not HAOS/Supervised), managed via Podman Quadlet. Dongle is a Sonoff ZBDongle-P (Silicon Labs CP2102N USB-UART bridge — shows up as /dev/ttyUSB0 on the host, mapped in via the stable /dev/serial/by-id/... path to /dev/ttyACM0 inside the container).
The symptom
Passed the device in with --device (or AddDevice= in a Quadlet .container file) and got Permission denied opening it from inside the container — despite:
- The device being owned by
root:dialouton the host - The container’s own
/etc/groupdefining adialoutentry - The host user genuinely being a member of
dialout
By every normal Unix-permissions expectation, this should have worked immediately. It didn’t, and chasing why took a while — documenting the dead ends too, since ruling each one out is useful information on its own.
What I tried, in order
1. Add host user to dialout, log out/in. No change.
2. Podman’s own documented rootless recipe (--group-add keep-groups, per Zigbee2MQTT’s Podman-specific install docs). No change. Checked id inside the container afterward — every one of the host’s real supplementary groups showed up as nobody instead of its actual name/GID, which was the first sign something deeper was going on.
3. Explicit numeric --group-add 18 (18 being dialout’s real GID on this host, bypassing the keep-groups keyword entirely as a genuinely different code path). Still denied — even though id inside the container now correctly showed the raw GID present in the group list.
4. Combined --user 1000:1000 --group-add 18 (matching Docker’s own documented rootless recipe, which pairs an explicit non-root UID with group-add, rather than Podman’s root+keep-groups variant). Still denied.
5. Suspected SELinux. Genuinely worth checking early rather than late — on a different container in the same setup, this turned out to be real:
sudo ausearch -m avc -ts recent
Important: run this immediately after triggering the failing access, in the same breath — if the container restarts or gets recreated (e.g. --rm + a crash loop) before you check, you’ll miss the event entirely and wrongly conclude SELinux isn’t involved.
Found a genuine denial on one container (avc: denied { getattr } ... tcontext=...usbtty_device_t... permissive=0) — fixed via:
sudo setsebool -P container_use_devices on
plus --security-opt label=disable on that specific container. This was a real, worthwhile fix — but it turned out to be a separate issue from the main one, not the root cause of the group-access failures happening everywhere else.
The actual root cause
Rootless Podman maps container UIDs/GIDs through a fixed range reserved specifically for your user (check /etc/subuid / /etc/subgid — typically something like youruser:100000:65536, not starting from 0). Real host system groups like dialout (commonly GID 18 or 20 depending on distro) live outside that reserved range entirely.
This mapping works fine for regular bind-mounted files — root-inside-a-rootless-container reliably maps 1:1 to your real host user, which is the whole mechanism that makes rootless Podman “just work” for normal volume mounts. It does not extend the same way to raw character devices. The kernel’s device-permission check happens against real, non-namespaced credentials, and none of the group-based mechanisms above — keep-groups, explicit numeric GID, or paired with an explicit --user — could bridge that gap, despite each being a documented, “should work” approach.
The actual fix: stop using group membership, use ownership instead
Set the device’s owner directly to your host user via a udev rule, matched by USB vendor/product ID (so it’s stable across replugging into a different port, unlike raw /dev/ttyUSB0 numbering):
udevadm info -a -n /dev/ttyUSB0 | grep -E 'idVendor|idProduct'
sudo nano /etc/udev/rules.d/99-zigbee-dongle.rules
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", OWNER="youruser", MODE="0660"
sudo udevadm control --reload-rules
sudo udevadm trigger
This works because the owner mapping (root-in-container ↔ your real host user) is the one identity translation rootless Podman handles reliably — sidestepping the group-translation problem entirely rather than fighting it.
One gotcha: if the device was already plugged in before the rule existed, udevadm trigger doesn’t always reliably re-apply ownership retroactively for serial devices. A physical unplug/replug forces a clean device-add event and made it take effect immediately in my case.
Applies regardless of which container needs the access — Home Assistant’s Zigbee integration, Zigbee2MQTT, or anything else needing raw serial access. This is a rootless-Podman-level limitation, not specific to any one image.
Hope this saves someone else the rabbit hole.