Using Podman
Run RF Swift with rootless, daemonless Podman: why you’d pick it, how to set it up, and what rootless mode changes.
Podman is a container engine that runs without a background service and without root rights. RF Swift supports it as a full alternative to Docker: the same toolboxes, the same commands and the same Workbench. RF Swift detects Podman automatically and adapts to its rootless mode for you.
Why pick Podman
- Rootless by default: your labs run as you, not as root. Even if a container escape occurred, the attacker would only get unprivileged user access, not root.
- No daemon: nothing runs in the background when you are not using it, which also helps on small boards with little memory.
- Good for sensitive setups: shared or company machines, security-focused environments, and air-gapped networks.
- Open source everywhere: on Windows, Podman Desktop is the open-source alternative to Docker Desktop.
For most RF work, rootless Podman is enough. You only need sudo for USB hot-plug (plugging a device in while the lab runs) or for WireGuard/OpenVPN VPNs.
Comparing all four engines: Choose your engine.
Docker and Podman at a glance
| Feature | Docker | Podman |
|---|---|---|
| Architecture | Client-server (daemon) | Daemonless (fork-exec) |
| Root required | Yes (daemon) | No (rootless by default) |
| Device passthrough | Native | Supported |
| Cgroup rules | Full support | Not supported in rootless |
| Privileged mode | Full host access | User-namespace scoped |
| USB hotplug | With cgroup rules | Limited in rootless |
| Networking | Host/bridge | slirp4netns/pasta |
Getting started
1. Install Podman
sudo apt install podmansudo dnf install podmansudo pacman -S podmanbrew install podman
podman machine init
podman machine startOn macOS, Podman runs in a VM (podman machine) that cannot receive USB devices. For radios on a Mac, use the Lima or Nix engine: see Choose your engine.
2. One-time setup (Linux)
# Ensure subordinate UID/GID ranges are configured
grep $USER /etc/subuid /etc/subgid
# If empty, add them
sudo usermod --add-subuids 100000-165535 $USER
sudo usermod --add-subgids 100000-165535 $USER
# Enable lingering (containers survive logout)
sudo loginctl enable-linger $USER
# Configure default registry to avoid prompts
echo 'unqualified-search-registries = ["docker.io"]' | sudo tee -a /etc/containers/registries.conf
# Let your user open RF hardware (rules inside a container are never evaluated)
rfswift host udev3. Create a lab
# Auto-detected (if Podman is the only engine)
rfswift container create -i sdr_full -n my_sdr
# Explicit engine selection (if both Docker and Podman are installed)
rfswift --engine podman container create -i sdr_full -n my_sdrThe legacy spelling rfswift run still works.
Rootless mode
By default Podman runs rootless, so no sudo is required. RF Swift adapts automatically and tells you, before creating anything, what it had to adjust.
| Works rootless | Needs root or privileged mode |
|---|---|
| Container creation and management | Device cgroup rules (USB hotplug) |
| Image pulling and building | Some device nodes (/dev/tty, /dev/console, /dev/vhci, /dev/uinput) |
| Volume bindings (user-accessible paths) | WireGuard/OpenVPN VPN |
| X11 forwarding | Full TUN/TAP networking |
| Audio (PulseAudio/PipeWire) | Raw hardware access |
| Session recording | |
| Remote desktop (VNC/noVNC) | |
| Network modes (slirp4netns/pasta) | |
| Tailscale/Netbird VPN (userspace mode) |
What RF Swift adjusts for you
-
Host udev rules: rootless Podman runs containers as your user, so a USB device is only reachable once the host grants you access.
rfswift host udevinstalls RF Swift’s rules (groupplugdev, seat ACL); rules inside a container are never evaluated. -
Your groups are kept: with the crun runtime,
dialoutandplugdevfollow you into the container, so a device you may open on the host is usable inside. -
Inaccessible devices are dropped: root-only device nodes are left out, while devices that remain accessible (for example
/dev/bus/usb,/dev/snd,/dev/dri) are kept.[!] Dropping 5 inaccessible device(s) for rootless mode: - /dev/vhci - /dev/console - /dev/tty0 - /dev/tty1 - /dev/uinput -
Cgroup device rules are dropped with a warning, since rootless Podman doesn’t support them:
[!] Rootless Podman does not support device cgroup rules. [!] Rules that will be dropped: c 189:* rwm, c 166:* rwm, ... [i] Device hotplug (USB, SDR dongles) may not work without cgroup rules. [i] To use cgroup rules, run RF Swift with sudo. -
Realtime ulimits above your host hard limits are skipped instead of failing the start.
-
Serial ports must be present at creation (no
mknod, no cgroup rules); the hot-plug works on rootful Podman and Docker. -
Configuration changes (
rfswift config ...) commit the container and re-create it, since Podman has no editable store; one snapshot image per change remains. -
Images:
rfswift image pullworks the same; usedocker.io/penthertz/rfswift_resolute:...or setunqualified-search-registriesto avoid the short-name prompt. -
Audit:
rfswift image auditruns trivy as a container through Podman when no host trivy is installed.
rfswift container create and the Workbench list what will be dropped before creation.
Your devices still work
You can use USB devices that are plugged in before the container starts. What doesn’t work without cgroup rules is hotplug: plugging or unplugging devices while the container is running.
Running with root (Podman + sudo)
For full hardware access, run Podman with sudo. This gives you the same capabilities as Docker with root, including cgroup rules and full device access:
sudo rfswift --engine podman container create -i sdr_full -n my_sdr \
-s /dev/bus/usb:/dev/bus/usb \
-g "c 189:* rwm"VPN with Podman
Tailscale and Netbird work rootless, using userspace networking:
rfswift --engine podman container create -i sdr_full -n my_sdr --vpn tailscaleWireGuard and OpenVPN need root and privileged mode, because they create a tunnel:
sudo rfswift --engine podman container create -i sdr_full -n my_sdr \
-u 1 \
--vpn wireguard:./wg0.confMore in VPN inside containers.
Networking
Rootless Podman uses slirp4netns or pasta for networking instead of a real bridge:
# Host network (default), works with slirp4netns
rfswift --engine podman container create -i sdr_full -n my_sdr
# Bridge network
rfswift --engine podman container create -i sdr_full -n my_sdr -t bridge
# Port forwarding with bridge
rfswift --engine podman container create -i sdr_full -n my_sdr \
-t bridge \
-w 8080:80/tcpPort forwarding works in rootless mode, but only for ports above 1024. To bind to low ports (for example 80 or 443), use sudo or configure sysctl net.ipv4.ip_unprivileged_port_start=0.
Remote desktop with Podman:
rfswift --engine podman container create -i sdr_full -n sdr_desktop \
--desktop \
--desktop-config "http:0.0.0.0:6080" \
--desktop-pass "mypassword"Managing labs and images
All RF Swift commands work with Podman:
rfswift --engine podman container last # list containers
rfswift --engine podman container shell -c my_sdr # enter a container
rfswift --engine podman container stop -c my_sdr # stop it
rfswift --engine podman container rm -c my_sdr # remove it
# Export/import
rfswift --engine podman image export container -c my_sdr -o backup.tar.gz
rfswift --engine podman image import container -i backup.tar.gzImage names: Podman may prompt for a registry when it sees a short image name. RF Swift normalizes image names automatically, but you can also use full names:
# Short name (RF Swift resolves it)
rfswift --engine podman container create -i sdr_full -n my_sdr
# Full name (no resolution needed)
rfswift --engine podman container create -i docker.io/penthertz/rfswift_resolute:sdr_full -n my_sdrCommon workflows
Rootless SDR analysis
# Pull image
rfswift --engine podman image pull -i sdr_full
# Create container (rootless)
rfswift --engine podman container create -i sdr_full -n analysis \
-b ~/captures:/root/captures \
-s /dev/bus/usb:/dev/bus/usb
# Enter later
rfswift --engine podman container shell -c analysisFull hardware setup (with sudo)
sudo rfswift --engine podman container create -i sdr_full -n hw_work \
-s /dev/bus/usb:/dev/bus/usb \
-g "c 189:* rwm,c 166:* rwm" \
--realtime \
-b ~/captures:/root/capturesAir-gapped environment
Podman is ideal for air-gapped systems since it has no daemon:
# On connected machine: export image
rfswift --engine podman image export image -i sdr_full -o sdr_full.tar.gz
# Transfer to air-gapped machine (USB, etc.)
# On air-gapped machine: import and run
rfswift --engine podman image import image -i sdr_full.tar.gz
rfswift --engine podman container create -i sdr_full -n offline_work -t noneSee also Air-gapped installation.
Troubleshooting
“error creating device nodes”
Error: error during container init: error creating device nodes: create device inode /dev/tty: no such device or address
Cause: rootless Podman can’t create certain device nodes.
Solution: RF Swift filters these automatically; if you see this error, update to the latest RF Swift version. As a workaround, avoid mapping tty devices:
rfswift --engine podman container create -i sdr_full -n my_sdr -s /dev/bus/usb:/dev/bus/usb“Rootless Podman does not support device cgroup rules”
Not an error, just information. Your container still works; only USB hotplug is affected. Plug in devices before starting the container. For full cgroup support:
sudo rfswift --engine podman container create ...Containers not visible across engines
Containers created with Docker are invisible to Podman, and the other way round. Use the engine that created them:
rfswift --engine docker container shell -c my_container # created with Docker
rfswift --engine podman container shell -c my_container # created with PodmanImages not found after pulling with sudo
Problem: you pulled an image with sudo podman pull, but RF Swift can’t find it when running rootless.
Why: Podman stores images separately for root and rootless users. An image pulled with sudo goes to the root image store, which rootless Podman doesn’t see by default.
Solutions:
# Option 1: Pull without sudo (rootless)
podman pull penthertz/rfswift_resolute:sdr_full
# Option 2: Configure additionalimage stores to see root images
# Add to ~/.config/containers/storage.conf:
# [storage]
# [storage.options]
# additionalimagestores = ["/var/lib/containers/storage"]RF Swift includes an ImageInspectCompat layer that automatically resolves image names across both local and remote registries, handling Podman’s short-name resolution transparently.
Podman asks which registry to use
Configure default registries:
echo 'unqualified-search-registries = ["docker.io"]' | sudo tee -a /etc/containers/registries.confPermission denied on /dev/bus/usb
Install RF Swift’s udev rules with rfswift host udev, or add your user to the right group:
sudo usermod -aG plugdev $USER
newgrp plugdevOr use sudo for full access.
Related
- Choose your engine: Podman compared with Docker, Lima and Nix
engine: engine selection and comparisoncontainer create: create and run containers- VPN inside containers: VPN support with Podman
- Install RF Swift: installation and setup
- Air-gapped installation: offline deployment