}

Podman Rootless Containers: Docker Without Root or Daemon (2026)

Podman Rootless Containers: Docker Without Root or Daemon (2026)

Docker's architecture has a fundamental security property that often goes unmentioned: the Docker daemon runs as root. Every container you run, every image you pull, every volume you mount — all of it flows through a root-owned process. If an attacker escapes a container, they arrive at a root process. This is manageable with proper hardening, but it is an inherent risk.

Podman takes a different approach. It has no daemon. Each container is a direct child process of the user who starts it. And in rootless mode, containers run entirely within an unprivileged user's namespace — no root privileges required anywhere. On RHEL 8+, Fedora, and Ubuntu 20.04+, rootless Podman is production-ready.


What Rootless Actually Means

When Podman runs rootless, it uses Linux user namespaces to create a private mapping between the user IDs inside the container and user IDs on the host. The root user inside the container (UID 0) maps to your regular user account on the host.

The key properties:

  • No daemon: Podman forks a container process directly. pkill podman cannot take down all running containers.
  • No root on host: A container escape gives an attacker at most your user account privileges — not root.
  • User namespace isolation: Processes inside the container see a full UID range (0-65535) but the host sees them as your UID and the subuid range assigned to you.
  • Reduced attack surface: No long-running privileged daemon to compromise.

Rootless does have trade-offs. Binding to ports below 1024 requires extra configuration (or net.ipv4.ip_unprivileged_port_start sysctl). Some networking features work differently. But for the vast majority of workloads, rootless works transparently.


Podman vs Docker Architecture

Feature Docker Podman
Daemon dockerd runs as root No daemon
Container ownership Root process Your user process
Rootless mode Possible but secondary Default on modern Linux
Compose docker-compose podman-compose or podman compose
Systemd integration Docker engine handles it Quadlet (.container files)
Image format OCI + Docker format OCI (same, compatible)
Kubernetes YAML Limited podman generate kube

Podman is command-line compatible with Docker. The same flags, same subcommands, same Dockerfile format. The difference is the underlying architecture.


Installation

RHEL 9 / Fedora

Podman ships by default on RHEL 8+ and Fedora. Verify:

podman --version
# podman version 5.x.x

If needed:

sudo dnf install podman podman-compose

Ubuntu 22.04 / 24.04

sudo apt update
sudo apt install podman

Ubuntu's repositories have Podman. For the latest version, add the Kubic repository:

. /etc/os-release
echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_${VERSION_ID}/ /" \
  | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list
curl -L "https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_${VERSION_ID}/Release.key" \
  | sudo apt-key add -
sudo apt update && sudo apt install podman

Setting Up subuid and subgid

Rootless containers need a range of subordinate UIDs and GIDs mapped to your user. On modern systems this is configured automatically, but verify:

grep $USER /etc/subuid
# username:100000:65536
grep $USER /etc/subgid
# username:100000:65536

The format is username:start:count. This means your user gets UIDs 100000 through 165535 to use inside containers. If the entry is missing, add it:

sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER

Then migrate the storage:

podman system migrate

Basic Podman Commands

The CLI is deliberately Docker-compatible:

# Pull an image
podman pull nginx:alpine

# Run a container (detached, port mapping, name)
podman run -d -p 8080:80 --name webserver nginx:alpine

# List running containers
podman ps

# List all containers including stopped
podman ps -a

# List images
podman images

# View logs
podman logs webserver

# Execute a command inside a running container
podman exec -it webserver sh

# Stop and remove
podman stop webserver
podman rm webserver

# Remove an image
podman rmi nginx:alpine

# System cleanup (remove unused images, containers, volumes)
podman system prune -a

The docker=podman Alias

For systems where you have scripts or habits built around docker:

alias docker=podman

Add to ~/.bashrc or ~/.zshrc. Most docker commands work identically. What does not work:

  • docker swarm — no equivalent in Podman (use K8s or Podman pods)
  • docker plugin — not supported
  • Docker Compose files work via podman compose, not the docker compose plugin

Rootless Networking with pasta

Podman 5.x uses pasta (part of the passt project) as the default rootless network backend. It replaces the older slirp4netns. pasta provides better performance and supports more networking scenarios.

Verify it is installed:

which pasta
# or
rpm -q passt      # RHEL/Fedora
dpkg -l passt     # Ubuntu

With pasta, port publishing works the same as with Docker:

# Publish container port 80 to host port 8080
podman run -d -p 8080:80 nginx:alpine

# Bind to a specific host interface
podman run -d -p 127.0.0.1:8080:80 nginx:alpine

For ports below 1024, either run as root or adjust the kernel parameter:

sudo sysctl net.ipv4.ip_unprivileged_port_start=80
# Make it permanent:
echo "net.ipv4.ip_unprivileged_port_start=80" | sudo tee /etc/sysctl.d/99-unprivileged-ports.conf

Podman Compose: Running docker-compose.yml Files

podman compose (built into recent Podman versions) and the standalone podman-compose both parse Docker Compose YAML files. For most docker-compose.yml files, no changes are needed.

# Install podman-compose if not bundled
pip install podman-compose
# or
sudo dnf install podman-compose

# Use it exactly like docker-compose
podman compose up -d
podman compose ps
podman compose logs app
podman compose down

A typical compose file for a web app with a database:

version: "3.9"
services:
  app:
    image: myapp:latest
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://user:pass@db:5432/mydb
    depends_on:
      - db
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    volumes:
      - pgdata:/var/lib/postgresql/data
volumes:
  pgdata:

Pods: The Kubernetes Analogy

Podman has a native Pod concept that mirrors Kubernetes pods. Containers in the same pod share a network namespace — they communicate over localhost.

# Create a pod with a shared port
podman pod create --name mypod -p 8080:80

# Add containers to the pod
podman run -d --pod mypod --name webserver nginx:alpine
podman run -d --pod mypod --name sidecar myapp:latest

# Containers talk to each other via localhost
# The nginx container can reach sidecar on localhost:3000

# List pods
podman pod ps

# Stop the whole pod
podman pod stop mypod
podman pod rm mypod

This pod can be exported to Kubernetes YAML:

podman generate kube mypod > mypod.yaml

Quadlet: Running Containers as systemd Services

Quadlet is the modern way to run Podman containers as systemd user services in production. You write a .container file and systemd manages the lifecycle.

Create ~/.config/containers/systemd/webserver.container:

[Unit]
Description=Nginx Web Server
After=network-online.target

[Container]
Image=nginx:alpine
PublishPort=8080:80
Volume=%h/webroot:/usr/share/nginx/html:ro
Environment=NGINX_ENVSUBST_TEMPLATE_DIR=/etc/nginx/templates
Label=app=webserver

[Service]
Restart=always
TimeoutStartSec=30

[Install]
WantedBy=default.target

Reload systemd and start the service:

systemctl --user daemon-reload
systemctl --user start webserver.service
systemctl --user enable webserver.service
systemctl --user status webserver.service

Logs go through journald:

journalctl --user -u webserver.service -f

For system-wide containers (not per-user), place the .container file in /etc/containers/systemd/ and use systemctl without --user.


Building Images with Existing Dockerfiles

Podman builds from Dockerfiles without modification:

# Build an image
podman build -t myapp:1.0 .

# Build with a specific Dockerfile
podman build -f Dockerfile.prod -t myapp:prod .

# Multi-stage builds work the same
podman build --target production -t myapp:latest .

# Tag and push to a registry
podman tag myapp:latest ghcr.io/username/myapp:latest
podman push ghcr.io/username/myapp:latest

Pull from Docker Hub, GitHub Container Registry, and Quay.io:

podman pull docker.io/library/postgres:16
podman pull ghcr.io/owner/repo:tag
podman pull quay.io/prometheus/prometheus:latest

Skopeo: Inspect Without Pulling

Skopeo inspects images and copies them between registries without a full pull:

# Install
sudo dnf install skopeo    # RHEL/Fedora
sudo apt install skopeo    # Ubuntu

# Inspect image metadata without pulling
skopeo inspect docker://nginx:alpine

# List available tags
skopeo list-tags docker://docker.io/library/python

# Copy image between registries
skopeo copy docker://docker.io/library/nginx:alpine docker://quay.io/myuser/nginx:alpine

Skopeo is invaluable in CI pipelines where you want to check whether an image exists or get its digest before deciding to build or deploy.


When to Choose Podman vs Docker

Choose Podman when: - You are on RHEL, Fedora, or a security-conscious enterprise environment - You want rootless containers with minimal privilege - You need systemd integration via Quadlet - You want to generate Kubernetes YAML from your containers - You prefer no daemon to manage

Stick with Docker when: - Your team already has Docker tooling and infrastructure in place - You rely heavily on Docker Swarm - You use Docker Desktop on macOS/Windows (Docker Desktop has better GUI tooling) - Third-party tooling assumes the Docker socket at /var/run/docker.sock

For most Linux server workloads in 2026, Podman is the more secure default choice. The CLI compatibility means migration is usually just an alias.