No Root, No Problem: Rootless Podman + SELinux on Fedora
Series: You Don’t Need a VPC | Post 3 of 5
Why Rootless?
Running containers as root is the default but it’s a bad habit. If a container escapes, it has root on the host. With rootless Podman, container processes run as your user. The blast radius of a compromise is limited to your home directory.
On Fedora with SELinux enforcing, you get another containment layer on top. It requires a bit more setup but the security posture is meaningfully better.
The Key Config Changes
Unprivileged Port Binding
By default, only root can bind ports below 1024. nginx needs port 80. Fix:
echo "net.ipv4.ip_unprivileged_port_start=80" | sudo tee /etc/sysctl.d/99-unprivileged-ports.conf
sudo sysctl --systemSELinux Bind Mounts
Podman with SELinux enforcing will deny container access to bind-mounted host directories unless the context is right. The :Z flag relabels the directory for the container:
volumes:
- ./nginx/conf:/etc/nginx/conf.d:Z
- ./data/obsidian:/data:ZWithout :Z, you get silent permission denials that look like missing files.
Podman Socket for docker-gen
docker-gen needs to watch the container runtime socket to detect container changes. With rootless Podman, the socket path is different:
volumes:
- /run/user/1000/podman/podman.sock:/tmp/docker.sock:roThe 1000 is your user’s UID. This gives docker-gen read access to container events without any root involvement.
Systemd User Units
The whole stack starts on boot via a systemd user unit, not a root service:
[Unit]
Description=Home Services Stack
After=network-online.target
[Service]
ExecStart=/home/mhase/dev/services/deploy.sh
Restart=on-failure
[Install]
WantedBy=default.targetEnable it with:
systemctl --user enable deploy-boot.service
loginctl enable-linger mhase # keep user session alive after logoutenable-linger is the critical step — without it, user units stop when you log out.
The Exception: Home Assistant
Home Assistant needs mDNS and direct LAN device discovery, which requires a macvlan network interface with its own IP address. Rootless containers can’t create macvlan interfaces — that requires root.
The solution: Home Assistant runs as a root systemd unit (not in the compose stack), with its own macvlan interface at 192.168.50.249. The oauth2-proxy in the main compose stack points upstream to that IP.
# /etc/systemd/system/homeassistant-container.service
[Service]
ExecStart=podman run --network=macvlan-net ...Everything else stays rootless. Home Assistant is the one justified exception.
SELinux Gotchas
A few specific issues worth documenting:
- CouchDB data directory: needs
chcon -Rt container_file_tafter creation,:Zalone isn’t enough - SSH key mounts for ttyd: same issue —
chconon the key file before mounting - Podman socket: the
:roflag matters — docker-gen only needs read access
When something silently fails to find files or connect to sockets, check ausearch -m avc -ts recent before assuming it’s a config problem.
Next Up
Post 4: Getting secrets out of .env files and into 1Password CLI.