How to Run Rootless Containers with Podman
Run OCI containers without root or a daemon. Learn Podman installation, user namespace setup, rootless networking, and systemd Quadlet unit files for production use.
Before you start
- ▸A Linux host with a kernel version 5.11 or later (earlier kernels may lack full user namespace support)
- ▸A non-root user account with sudo access for initial setup steps
- ▸Basic familiarity with the command line and container concepts (images, containers, volumes)
Podman lets you build and run OCI-compatible containers without a root-level daemon. Each container runs inside your user's own namespace, so a breakout can't trivially escalate to root on the host. This guide walks through installing Podman, configuring the user namespace stack, pulling and running images as a normal user, and wiring containers into systemd so they survive reboots — all without touching the Docker socket or granting extra privileges.
Podman vs Docker: What Actually Differs
Docker relies on a persistent daemon (dockerd) running as root. Podman is daemonless — each podman invocation is a direct fork/exec. The practical consequences:
- No shared socket. There is no
/var/run/docker.sockto protect or leak. - Rootless by default. Images and container state live under
~/.local/share/containers, not/var/lib/docker. - Drop-in CLI compatibility. Most
dockerflags work unchanged withpodman. The project ships an alias script if needed. - Compose support.
podman-compose(community) or Podman's nativepodman compose(v4.7+) handles Compose files without Docker.
The trade-off: rootless containers can't bind ports below 1024 without extra sysctl configuration, and some images that assume root inside the container require namespace mapping tweaks.
Install Podman
Debian / Ubuntu
sudo apt update
sudo apt install -y podman
Ubuntu 22.04 LTS and Debian 12+ ship Podman 3.x or 4.x in their default repos. For the latest 5.x series on Ubuntu, add the Kubic repo or use the upstream Debian unstable backport — check podman.io/docs/installation for the current PPA.
Fedora / RHEL 9 / Rocky Linux 9
sudo dnf install -y podman
Fedora ships a very current Podman. On RHEL/Rocky 9 the default stream is Podman 4.x; enable the container-tools:rhel9 module if you need a specific version.
Arch Linux
sudo pacman -S podman
Configure User Namespaces
Rootless containers depend on the kernel's user namespace feature and a range of subordinate UIDs/GIDs mapped to your user account.
Verify the kernel supports user namespaces
cat /proc/sys/kernel/unprivileged_userns_clone
The output should be 1. On Debian (not Ubuntu), this is sometimes 0 by default. Enable it persistently:
echo 'kernel.unprivileged_userns_clone=1' | sudo tee /etc/sysctl.d/99-userns.conf
sudo sysctl --system
Set up subordinate UID/GID ranges
Check whether your user already has entries in /etc/subuid and /etc/subgid:
grep "$(whoami)" /etc/subuid /etc/subgid
If nothing is returned, add ranges (replace alice with your username):
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 alice
Then tell Podman to re-read the mapping:
podman system migrate
Pull and Run Your First Rootless Container
No sudo needed from here on for day-to-day container work.
podman pull docker.io/library/nginx:alpine
podman run -d --name web -p 8080:80 docker.io/library/nginx:alpine
Port 8080 on the host maps to port 80 inside the container. Rootless Podman cannot bind ports below 1024 unless you lower the kernel's port range limit:
echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/99-unpriv-port.conf
sudo sysctl --system
After that change, -p 80:80 works for your user without sudo.
Verify the container is running
podman ps
Expected output (shortened — yours will show actual IDs and timestamps):
CONTAINER ID IMAGE COMMAND STATUS PORTS
a3f8c2d1e9b0 docker.io/library/nginx:alpine nginx -g daemon o... Up 2 minutes 0.0.0.0:8080->80/tcp
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080
Returns 200 if nginx is serving correctly.
Manage Container Lifecycle with systemd
Podman integrates tightly with systemd through Quadlet (Podman 4.4+), which replaces the older podman generate systemd approach. Quadlet reads .container unit files and generates the actual systemd units automatically.
Create the Quadlet unit file
User-level Quadlet files live in ~/.config/containers/systemd/.
mkdir -p ~/.config/containers/systemd
nano ~/.config/containers/systemd/web.container
Paste the following:
[Unit]
Description=Nginx web container
After=network-online.target
[Container]
Image=docker.io/library/nginx:alpine
PublishPort=8080:80
Volume=%h/www:/usr/share/nginx/html:Z
Environment=NGINX_ENTRYPOINT_QUIET_LOGS=1
[Service]
Restart=always
[Install]
WantedBy=default.target
The :Z label on the volume mount sets the correct SELinux context on Fedora/RHEL systems. It is harmless on non-SELinux hosts. %h expands to your home directory.
Enable and start the unit
Reload the user daemon so it picks up the new Quadlet file:
systemctl --user daemon-reload
systemctl --user enable --now web.service
Enable lingering so the container starts at boot without a login session
loginctl enable-linger "$(whoami)"
Without lingering, your user's systemd instance is killed when the last session closes, taking all user services with it.
Check service status
systemctl --user status web.service
journalctl --user -u web.service -n 30 --no-pager
Useful Day-to-Day Commands
- Stop and remove:
podman stop web && podman rm web - Inspect a running container:
podman inspect web - Execute a command inside:
podman exec -it web sh - List images:
podman images - Prune stopped containers:
podman container prune - Auto-update images: Add
AutoUpdate=registryto the[Container]block, then enablepodman-auto-update.timerwithsystemctl --user enable --now podman-auto-update.timer.
Troubleshooting
"permission denied" on volume mounts (SELinux hosts)
Always append :Z (private relabeling) or :z (shared relabeling) to volume flags on Fedora/RHEL. If the error persists, check ausearch -m avc -ts recent for AVC denials.
Container can't reach the internet
Rootless Podman uses slirp4netns or pasta for networking. Verify one is installed:
which slirp4netns pasta
Install the missing package (slirp4netns on Debian/Ubuntu/Fedora; passt on newer Podman defaults). Podman 5.x prefers pasta — install the passt package.
"rootlesskit" or namespace errors on Debian
Double-check /proc/sys/kernel/unprivileged_userns_clone is 1 (see the sysctl step above). Also confirm /etc/subuid and /etc/subgid have your user listed and re-run podman system migrate.
Quadlet unit file not picked up
Quadlet requires Podman 4.4+. Check your version with podman --version. On older installs, fall back to podman generate systemd --name web --files --new and copy the generated file to ~/.config/systemd/user/.
Frequently asked questions
- Can I use Docker Compose files with rootless Podman?
- Yes. Podman 4.7+ includes a native 'podman compose' subcommand, and the community podman-compose tool works with most Compose v3 files. Complex Compose features like build caching may behave slightly differently than with Docker.
- Is rootless Podman less capable than running containers as root?
- For most workloads, no. The main limitations are ports below 1024 (adjustable via sysctl) and some network modes that require CAP_NET_ADMIN. Applications that explicitly require root inside the container and on the host are rare and usually avoidable.
- What is the difference between Quadlet and podman generate systemd?
- podman generate systemd produces a static unit file snapshot tied to a specific container ID. Quadlet is declarative — you describe the desired container state and Podman regenerates the unit on each daemon-reload, making updates and image changes cleaner. Quadlet is the recommended approach on Podman 4.4+.
- Does rootless Podman work inside a VM or WSL2?
- Yes, with caveats. WSL2 supports user namespaces as of recent kernel versions. Nested virtualization (running containers in a VM that's already inside a hypervisor) works as long as the guest kernel exposes user namespace support. Check /proc/sys/kernel/unprivileged_userns_clone inside the VM.
- How do I migrate from Docker to Podman without breaking existing scripts?
- Podman ships a docker compatibility alias: on most distros, installing the podman-docker package creates a /usr/bin/docker symlink and a stub socket. Scripts calling 'docker' will transparently invoke Podman. Review any usage of docker.sock-based volume mounts, as those won't translate directly.
Related guides
Build an Intranet Server on Linux
Set up a complete small-office intranet on one Linux box: Nginx web server, dnsmasq local DNS, Samba file sharing, and a Wiki.js team wiki.
How to Change the Webmin Port
Move Webmin off its default port 10000 by editing miniserv.conf, updating your firewall (ufw, firewalld, or nftables), and restarting the service.
Configure a UPS on Linux with NUT
Install and configure Network UPS Tools (NUT) on Linux to detect your UPS, load the right driver, and trigger a safe automatic shutdown when battery runs low.
How to Configure the SMTP HELO/EHLO Name
Set the correct SMTP HELO/EHLO hostname in Postfix and Sendmail, configure FCrDNS records, and verify your mail server won't be rejected or spam-flagged.