Install and Use Podman (Docker without a daemon)
Run OCI containers without a daemon using Podman. Covers rootless setup, podman-compose, and systemd Quadlet units for production-grade container management.
Before you start
- ▸A 64-bit Linux system with kernel 5.11+ (for full rootless namespace support)
- ▸A non-root user account with sudo privileges
- ▸systemd 239+ (standard on all supported distros)
Podman is a daemonless container engine that runs OCI-compatible containers without requiring a root-level background service. Every container you run is a direct child of your shell or systemd unit — no central daemon, no single point of failure. Rootless mode means a compromised container cannot escape to root on the host. If you've used Docker, most commands are a drop-in replacement; alias docker=podman genuinely works for day-to-day use.
Installing Podman
Debian / Ubuntu
Podman 4.x is available in Ubuntu 22.04+ and Debian 12+ official repos. For older releases, add the Kubic repository.
sudo apt update && sudo apt install -y podman
Fedora / RHEL / Rocky Linux
Podman ships by default on Fedora and is in the AppStream repo on RHEL 8+.
sudo dnf install -y podman
Arch Linux
sudo pacman -S podman
Confirm the version after install:
podman --version
# podman version 5.x.x (output will vary)
Enabling Rootless Containers
Rootless Podman runs containers entirely within your user's namespace. The kernel needs subordinate UID/GID ranges assigned to your account. Modern distros configure these automatically at user creation, but verify:
grep "$(whoami)" /etc/subuid /etc/subgid
If your username is absent, add the ranges manually (replace alice with your username):
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 alice
Then reload the user namespace mappings:
podman system migrate
Enable and start the rootless socket for your user (needed by some tools, including podman-compose):
systemctl --user enable --now podman.socket
Running Your First Rootless Container
Pull and run a container as your normal user — no sudo:
podman run --rm -it docker.io/library/alpine:latest sh
Inside the container, id reports uid=0(root), but that maps to your unprivileged UID on the host. To confirm this from a second terminal while the container runs:
podman top <container-id> huser user
# HUSER USER
# alice root
HUSER is the real host user — never root. That's the security boundary rootless gives you.
Persistent Containers and Volumes
Run a named container with a bind-mounted volume:
podman run -d \
--name my-nginx \
-p 8080:80 \
-v "$HOME/www:/usr/share/nginx/html:Z" \
docker.io/library/nginx:stable-alpine
The :Z label relabels the volume for SELinux. Use :z for shared volumes on SELinux-enforcing systems (Fedora, RHEL, Rocky). On non-SELinux systems it is ignored safely.
podman ps
podman logs my-nginx
podman stop my-nginx && podman rm my-nginx
Multi-Container Apps with podman-compose
podman-compose reads the same compose.yaml format as Docker Compose and wraps Podman calls under the hood.
Install podman-compose
# Debian/Ubuntu
sudo apt install -y podman-compose
# Fedora/RHEL
sudo dnf install -y podman-compose
# Arch
sudo pacman -S podman-compose
# Any distro via pip
pip install --user podman-compose
Example compose.yaml
cat > compose.yaml <<'EOF'
services:
web:
image: docker.io/library/nginx:stable-alpine
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html:Z
db:
image: docker.io/library/postgres:16-alpine
environment:
POSTGRES_PASSWORD: example
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
EOF
podman-compose up -d
podman-compose logs -f
podman-compose down
Systemd Integration with Quadlet
Quadlet (merged into Podman 4.4+) lets you define containers in declarative .container unit files that systemd manages directly. This is the modern, production-grade replacement for podman generate systemd, which is now deprecated.
Creating a Quadlet Unit
For a rootless user service, place unit files in ~/.config/containers/systemd/. For a system-wide service, use /etc/containers/systemd/.
mkdir -p ~/.config/containers/systemd/
cat > ~/.config/containers/systemd/my-nginx.container <<'EOF'
[Unit]
Description=My nginx container
After=network-online.target
[Container]
Image=docker.io/library/nginx:stable-alpine
PublishPort=8080:80
Volume=%h/www:/usr/share/nginx/html:Z
AutoUpdate=registry
[Service]
Restart=always
TimeoutStartSec=60
[Install]
WantedBy=default.target
EOF
The %h specifier expands to your home directory. AutoUpdate=registry enables automatic image updates via podman auto-update.
Loading and Starting the Unit
Quadlet generates the actual systemd unit at daemon-reload time:
systemctl --user daemon-reload
systemctl --user start my-nginx.service
systemctl --user status my-nginx.service
Enable it to start on login (or at boot with lingering enabled):
systemctl --user enable my-nginx.service
# Allow the service to run even when you're not logged in:
sudo loginctl enable-linger "$(whoami)"
System-Wide Quadlet Units
For root-level services drop the --user flag and use /etc/containers/systemd/:
sudo systemctl daemon-reload
sudo systemctl enable --now my-nginx.service
Verification
Check your rootless setup end-to-end:
# Confirm no daemon is running
systemctl status podman 2>/dev/null || echo "No system daemon — expected"
# List running user containers
podman ps
# Confirm the Quadlet service is active
systemctl --user is-active my-nginx.service
# Hit the nginx port
curl -s http://localhost:8080 | head -5
Troubleshooting
"slirp4netns" or "pasta" not found
Rootless networking requires a userspace network helper. Install the appropriate package:
# Debian/Ubuntu
sudo apt install -y slirp4netns
# Fedora/RHEL
sudo dnf install -y slirp4netns
# Arch
sudo pacman -S slirp4netns
Podman 5+ prefers pasta (from the passt package) over slirp4netns for better performance. Install passt if available on your distro.
Permission denied on bind mounts with SELinux
Always add :Z (private) or :z (shared) to volume mounts on SELinux-enforcing systems. Forgetting this is the most common cause of silent permission failures inside containers on Fedora and RHEL.
Quadlet unit not appearing after daemon-reload
Check for syntax errors in your .container file:
/usr/lib/systemd/system-generators/podman-system-generator --user --dryrun
Look for parse warnings in the output. Common mistakes: wrong section headers, missing Image= key, or placing the file in the wrong directory.
Container works but dies after logout
You need lingering enabled. Without it, your user's systemd instance is terminated on logout, killing all user services:
sudo loginctl enable-linger "$(whoami)"Frequently asked questions
- Is Podman a drop-in replacement for Docker?
- For most CLI usage yes — the command syntax is nearly identical and aliasing docker to podman works for everyday tasks. Differences appear with Docker Compose (use podman-compose), Docker Desktop GUI tooling, and features that assume a persistent daemon like docker events streaming.
- What is the difference between rootless and rootful Podman?
- Rootless runs all container processes inside a user namespace mapped to your unprivileged UID, so a container escape cannot grant host root access. Rootful (run with sudo) gives containers more kernel capabilities and easier bind-mount permissions but carries greater security risk.
- Why use Quadlet instead of podman generate systemd?
- podman generate systemd is deprecated as of Podman 4.4 and may be removed in a future release. Quadlet is the official replacement: you write a small declarative unit file and systemd generates the full service, rather than maintaining a sprawling auto-generated unit.
- Can I use Docker Hub images with Podman?
- Yes, but you must specify the full registry path: docker.io/library/nginx rather than just nginx. Configure /etc/containers/registries.conf to add docker.io as an unqualified search registry if you want short names to resolve automatically.
- How do I automatically update container images managed by Quadlet?
- Add AutoUpdate=registry to the [Container] section of your .container file, then enable the podman-auto-update systemd timer with systemctl --user enable --now podman-auto-update.timer. It checks for newer images and restarts affected services.
Related guides
Configure Prometheus Alertmanager
Configure Prometheus Alertmanager with routing trees, receivers, inhibition rules, grouping, Go templates, and PagerDuty/Slack on-call integrations.
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.
Build an nftables Firewall Script
Build a complete nftables firewall from scratch: tables, chains, sets, default-deny input policy, service allowlisting, and persistent systemd configuration.
Caddy as a Reverse Proxy
Set up Caddy as a reverse proxy with automatic HTTPS, load balancing, WebSocket passthrough, reusable snippets, and header control — no certbot required.