PODMAN DAEMONLESS ARCHITECTURE Linux Host OS (Kernel: namespaces + cgroups) No Daemon — Direct fork/exec via conmon Container 1 Rootless UID-mapped Container 2 Rootless UID-mapped Pod (shared network namespace) Container 3 App :8080 Container 4 DB :5432 Each container is a child process — no central daemon required

Podman (Pod Manager) is a daemonless, open-source container engine developed by Red Hat that provides a Docker-compatible CLI for building, running, and managing OCI containers and pods. Unlike Docker, Podman does not require a long-running background daemon with root privileges, making it inherently more secure for both development workstations and production servers. If you have been using Docker and are looking for a more secure, systemd-friendly alternative that runs containers as your regular user, Podman is the tool to adopt.

This guide covers everything you need to get started with Podman on Ubuntu: installation, rootless container execution, image management, pod creation, Compose compatibility, systemd integration, networking, and a complete migration path from Docker.

Prerequisites

Before you begin, make sure you have:

  • A system running Ubuntu 22.04 or 24.04 (desktop or server edition)
  • Terminal access with sudo privileges
  • A stable internet connection
  • Basic familiarity with containers and the Linux command line
  • At least 2 GB of free disk space for images

Note: Podman works on any modern Linux distribution, but this guide focuses on Ubuntu. If you are migrating from Docker, no Docker installation is required — Podman replaces it entirely.

What Is Podman?

Podman is a container management tool that implements the same CLI interface as Docker but with a fundamentally different architecture. The name comes from Pod Manager, reflecting its native support for Kubernetes-style pods — groups of containers that share network and IPC namespaces.

Key characteristics of Podman:

  • Daemonless: No background service runs as root. Each podman command launches containers directly as child processes via conmon (the container monitor).
  • Rootless by default: Containers run under your regular user account using Linux user namespaces, with no need for root privileges.
  • OCI-compliant: Podman builds and runs images that conform to the Open Container Initiative (OCI) specification, ensuring full compatibility with Docker images and registries.
  • Pod-native: Podman can group containers into pods, mirroring Kubernetes pod semantics for local development and testing.
  • Systemd integration: Podman generates systemd unit files from running containers, making it straightforward to manage container lifecycle as system services.

Podman vs Docker

Understanding the architectural differences between Podman and Docker helps you make an informed decision and plan your migration.

FeaturePodmanDocker
ArchitectureDaemonless (fork/exec)Client-server (dockerd daemon)
Root requirementRootless by defaultRequires root daemon (rootless mode optional)
Container runtimecrun / runc via conmoncontainerd + runc
CLI compatibilityDrop-in replacement (podman = docker)Native CLI
Pod supportNative (Kubernetes-style)Not available
Compose supportpodman-compose / podman composedocker compose (built-in plugin)
Systemd integrationNative (podman generate systemd)Requires manual unit files
Image formatOCI / DockerOCI / Docker
Build toolBuildah (integrated)BuildKit
Security modelNo root daemon, user namespacesRoot daemon, optional rootless
SocketOptional (podman.socket)Required (docker.socket)

The most significant difference is the daemon. Docker requires the dockerd daemon to run at all times as root, creating a single point of failure and a potential privilege escalation vector. Podman eliminates this entirely — when you run podman run, it directly forks a new process for the container using conmon as the container monitor. If Podman itself crashes, running containers continue operating.

Installing Podman on Ubuntu

Podman is available in the default Ubuntu repositories starting from Ubuntu 22.04. Install it along with its supporting tools:

sudo apt update
sudo apt install -y podman

Verify the installation:

podman --version

Expected output:

podman version 4.9.3

Check the system information to confirm that rootless mode is available:

podman info --format '{{.Host.Security.Rootless}}'

This should return true when run as a non-root user.

For the latest Podman version (if the Ubuntu repository version is too old), you can add the official Kubic repository:

sudo mkdir -p /etc/apt/keyrings
curl -fsSL "https://download.opensuse.org/repositories/devel:kubic:libcontainers:unstable/xUbuntu_$(lsb_release -rs)/Release.key" \
  | gpg --dearmor \
  | sudo tee /etc/apt/keyrings/devel_kubic_libcontainers_unstable.gpg > /dev/null

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/devel_kubic_libcontainers_unstable.gpg] \
  https://download.opensuse.org/repositories/devel:kubic:libcontainers:unstable/xUbuntu_$(lsb_release -rs)/ /" \
  | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list > /dev/null

sudo apt update
sudo apt install -y podman

Configuring Container Registries

By default, Podman requires fully qualified image names (e.g., docker.io/library/nginx). To enable short names like nginx, configure the unqualified search registries:

sudo tee /etc/containers/registries.conf.d/00-unqualified-search.conf <<'EOF'
[registries.search]
registries = ['docker.io', 'quay.io', 'ghcr.io']
EOF

Alternatively, edit /etc/containers/registries.conf and add the search registries under the [registries.search] section.

Running Your First Container

With Podman installed, run a test container as your regular (non-root) user:

podman run --rm docker.io/library/hello-world

You should see the familiar “Hello from Docker!” message, confirming that Podman can pull and run OCI images. Notice that no sudo is required.

Run an interactive container:

podman run -it --rm docker.io/library/ubuntu:24.04 bash

Inside the container, check the user:

whoami
# root (inside the container's user namespace)
id
# uid=0(root) gid=0(root) groups=0(root)

Although you appear as root inside the container, you are actually running as your regular user on the host. This is the power of rootless containers with user namespace mapping.

Exit the container with exit or Ctrl+D.

Rootless Containers

Rootless containers are the defining feature of Podman. Understanding how they work helps you troubleshoot permission issues and design secure container deployments.

How Rootless Containers Work

When you run a container as a non-root user, Podman uses Linux user namespaces to create an isolated environment where:

  1. The container process sees itself as root (UID 0) inside the container.
  2. On the host, the process runs as a subordinate UID mapped from your user’s allowed range.
  3. The mapping is defined in /etc/subuid and /etc/subgid.

Check your user’s subordinate UID/GID mappings:

cat /etc/subuid
# jc:100000:65536

cat /etc/subgid
# jc:100000:65536

This means user jc can map UIDs 100000 through 165535 inside containers. The container’s UID 0 maps to your host user’s UID, and UIDs 1-65535 inside the container map to host UIDs 100000-165535.

Verifying Rootless Operation

Run a container and check the process from the host:

podman run -d --name test-rootless docker.io/library/nginx:alpine

From another terminal on the host:

ps aux | grep nginx
# jc  12345  ... nginx: master process

The nginx process runs as your user (jc), not as root. If the container were compromised, the attacker would only have your user’s privileges — not root access to the host.

Configuring User Namespaces

If /etc/subuid or /etc/subgid do not contain entries for your user, add them:

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

After modifying these files, reset the Podman storage to apply changes:

podman system migrate

Working with Images

Podman uses the same image format as Docker and can pull from any OCI-compliant registry.

Pulling Images

# Pull from Docker Hub
podman pull docker.io/library/nginx:alpine

# Pull from Quay.io
podman pull quay.io/podman/hello

# Pull from GitHub Container Registry
podman pull ghcr.io/actions/actions-runner:latest

Listing Images

podman images

Output:

REPOSITORY                        TAG       IMAGE ID      CREATED       SIZE
docker.io/library/nginx           alpine    a2bd6d6e82f0  2 weeks ago   43.3 MB
quay.io/podman/hello              latest    5dd467fce50b  3 months ago  787 kB

Building Images

Podman uses Containerfile (or Dockerfile — both are supported) and Buildah under the hood:

# Containerfile
FROM docker.io/library/python:3.12-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["python", "app.py"]

Build the image:

podman build -t my-python-app:latest .

Build with a specific file name:

podman build -f Containerfile.prod -t my-python-app:prod .

Pushing Images to a Registry

Tag and push an image to a registry:

podman tag my-python-app:latest quay.io/myuser/my-python-app:latest
podman login quay.io
podman push quay.io/myuser/my-python-app:latest

Removing Images

# Remove a specific image
podman rmi docker.io/library/nginx:alpine

# Remove all unused images
podman image prune -a

Pod Management

Pods are a concept borrowed from Kubernetes. A pod groups one or more containers that share the same network namespace, meaning they can communicate over localhost and share port mappings.

Creating a Pod

Create a pod with port mappings defined at the pod level:

podman pod create --name my-web-pod -p 8080:80 -p 5432:5432

Adding Containers to a Pod

Add an nginx container and a PostgreSQL container to the pod:

podman run -d --pod my-web-pod --name web-server docker.io/library/nginx:alpine

podman run -d --pod my-web-pod --name db-server \
  -e POSTGRES_PASSWORD=secretpass \
  -e POSTGRES_DB=appdb \
  docker.io/library/postgres:16-alpine

Both containers now share the same network namespace. The nginx container can connect to PostgreSQL at localhost:5432, and external clients access nginx via host:8080.

Managing Pods

# List all pods
podman pod ls

# View pod details
podman pod inspect my-web-pod

# Stop all containers in a pod
podman pod stop my-web-pod

# Start a pod
podman pod start my-web-pod

# Remove a pod and its containers
podman pod rm -f my-web-pod

Generating Kubernetes YAML from Pods

One of Podman’s unique features is generating Kubernetes manifests from running pods:

podman generate kube my-web-pod > my-web-pod.yaml

This creates a Kubernetes-compatible YAML file that you can deploy directly to a Kubernetes cluster with kubectl apply -f my-web-pod.yaml. This makes Podman an excellent tool for local development when your production target is Kubernetes.

Podman Compose

Podman supports Docker Compose files for multi-container application stacks. There are two approaches:

Using podman-compose

Install podman-compose (a community Python reimplementation):

sudo apt install -y python3-pip
pip3 install podman-compose

Use it just like docker-compose:

podman-compose up -d
podman-compose ps
podman-compose logs -f
podman-compose down

Using the Built-in podman compose

Podman 4.7+ includes a built-in podman compose command that delegates to an external compose provider (docker-compose or podman-compose):

podman compose up -d
podman compose down

Example docker-compose.yml

A standard docker-compose.yml works without modification:

version: "3.8"
services:
  web:
    image: docker.io/library/nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro
    depends_on:
      - api

  api:
    image: docker.io/library/node:20-alpine
    working_dir: /app
    volumes:
      - ./api:/app
    command: node server.js
    environment:
      - DB_HOST=db
      - DB_PORT=5432

  db:
    image: docker.io/library/postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: secretpass
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Run with Podman Compose:

podman-compose up -d

Generating systemd Units from Containers

Podman integrates with systemd to manage containers as system services. This is especially useful for servers where you need containers to start on boot and restart on failure.

Generate a Unit File from a Running Container

First, start a container:

podman run -d --name my-nginx -p 8080:80 docker.io/library/nginx:alpine

Generate the systemd unit:

podman generate systemd --new --name my-nginx > ~/.config/systemd/user/container-my-nginx.service

The --new flag ensures the unit file creates a fresh container each time (rather than restarting an existing one), which is the recommended practice.

Enable and Start the Service

mkdir -p ~/.config/systemd/user
systemctl --user daemon-reload
systemctl --user enable --now container-my-nginx.service

Enable Lingering for Non-Login Sessions

By default, user systemd services stop when the user logs out. Enable lingering to keep them running:

sudo loginctl enable-linger $USER

Verify the Service

systemctl --user status container-my-nginx.service

Quadlet (Podman 4.4+)

For Podman 4.4 and later, Quadlet provides a simpler declarative format. Create a .container file in ~/.config/containers/systemd/:

# ~/.config/containers/systemd/my-nginx.container
[Container]
Image=docker.io/library/nginx:alpine
PublishPort=8080:80
Volume=./html:/usr/share/nginx/html:ro

[Service]
Restart=always

[Install]
WantedBy=default.target

Reload systemd and start the service:

systemctl --user daemon-reload
systemctl --user start my-nginx.service

Migrating from Docker to Podman

If you are currently using Docker, the migration path to Podman is straightforward.

Install the Docker Compatibility Package

sudo apt install -y podman-docker

This installs a docker command that aliases to podman and creates a /var/run/docker.sock emulation socket. Existing scripts that call docker will use Podman instead.

Migrate Docker Images

Export images from Docker and import them into Podman:

# On the Docker system
docker save my-app:latest -o my-app.tar

# On the Podman system
podman load -i my-app.tar

Convert Docker Compose Files

Most docker-compose.yml files work without modification. The main adjustments to watch for:

  1. Fully qualify image names: Replace nginx:alpine with docker.io/library/nginx:alpine
  2. Network mode: Some Docker-specific network modes may differ
  3. Volume plugins: Docker volume plugins are not supported; use Podman’s native volume drivers

Update CI/CD Pipelines

Replace docker commands with podman in your pipeline scripts, or install podman-docker so the docker alias handles it:

# GitHub Actions example
steps:
  - name: Install Podman
    run: sudo apt install -y podman podman-docker

  - name: Build image
    run: podman build -t my-app:${{ github.sha }} .

  - name: Push image
    run: |
      podman login -u ${{ secrets.REGISTRY_USER }} -p ${{ secrets.REGISTRY_PASS }} ghcr.io
      podman push my-app:${{ github.sha }} ghcr.io/${{ github.repository }}:${{ github.sha }}

Networking

Podman’s networking model differs between rootful and rootless modes.

Rootless Networking

In rootless mode, Podman uses slirp4netns or pasta (Podman 5.0+) to provide network connectivity without root privileges:

  • slirp4netns: Creates a TAP device in a user namespace, providing NAT-based networking. Port forwarding above port 1024 works without root.
  • pasta: A newer, faster alternative that provides improved performance and lower overhead.

Map ports in rootless mode:

podman run -d -p 8080:80 docker.io/library/nginx:alpine

Note: Binding to ports below 1024 (e.g., 80, 443) requires root privileges or adjusting the system’s unprivileged port start with sudo sysctl net.ipv4.ip_unprivileged_port_start=80.

CNI and Netavark

Podman supports two network backends:

  • CNI (Container Network Interface): The legacy backend using CNI plugins.
  • Netavark: The modern, Rust-based network stack (default in Podman 4.0+).

Create a custom network:

podman network create my-network

Run containers on the custom network:

podman run -d --network my-network --name container-a docker.io/library/alpine sleep 3600
podman run -d --network my-network --name container-b docker.io/library/alpine sleep 3600

Containers on the same network can reach each other by name:

podman exec container-a ping -c 3 container-b

Inspecting Networks

# List networks
podman network ls

# Inspect a network
podman network inspect my-network

# Remove a network
podman network rm my-network

Podman Commands Reference

The following table lists the most commonly used Podman commands and their Docker equivalents:

Podman CommandDocker EquivalentDescription
podman rundocker runCreate and start a container
podman psdocker psList running containers
podman ps -adocker ps -aList all containers
podman imagesdocker imagesList local images
podman pulldocker pullPull an image from a registry
podman builddocker buildBuild an image from a Containerfile/Dockerfile
podman pushdocker pushPush an image to a registry
podman execdocker execRun a command in a running container
podman logsdocker logsView container logs
podman stopdocker stopStop a running container
podman rmdocker rmRemove a container
podman rmidocker rmiRemove an image
podman volume createdocker volume createCreate a named volume
podman network createdocker network createCreate a network
podman pod createN/ACreate a pod
podman pod lsN/AList pods
podman generate systemdN/AGenerate systemd unit files
podman generate kubeN/AGenerate Kubernetes YAML
podman system prunedocker system pruneRemove unused data
podman logindocker loginLog in to a container registry
podman inspectdocker inspectView detailed container/image info

Troubleshooting

Error: short-name resolution enforced

Problem: Podman refuses to pull an image using a short name like nginx.

Solution: Use the fully qualified image name or configure search registries:

# Use fully qualified name
podman pull docker.io/library/nginx:alpine

# Or configure search registries
sudo tee /etc/containers/registries.conf.d/00-unqualified-search.conf <<'EOF'
[registries.search]
registries = ['docker.io', 'quay.io']
EOF

Error: cannot find UID/GID mapping

Problem: Rootless containers fail because subordinate UID/GID mappings are not configured.

Solution: Add mappings for your user:

sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER
podman system migrate

Error: permission denied on volume mount

Problem: A rootless container cannot access a bind-mounted directory.

Solution: Add the :Z or :z SELinux label suffix, or ensure the directory is owned by your user:

# Fix ownership
podman unshare chown -R 0:0 /path/to/volume

# Or use Z flag for SELinux
podman run -v /path/to/volume:/data:Z my-image

Container Cannot Bind to Port 80

Problem: Rootless Podman cannot bind to ports below 1024.

Solution: Lower the unprivileged port start value:

sudo sysctl -w net.ipv4.ip_unprivileged_port_start=80
# Make it persistent
echo "net.ipv4.ip_unprivileged_port_start=80" | sudo tee /etc/sysctl.d/99-podman-ports.conf
sudo sysctl --system

Podman Compose Fails with Network Error

Problem: podman-compose errors out when creating networks.

Solution: Ensure the Podman network backend is running and reset if needed:

podman network ls
podman system reset --force
# Recreate your containers after reset

Containers Not Starting After Reboot

Problem: User-level systemd containers do not start after a reboot.

Solution: Enable lingering for your user:

sudo loginctl enable-linger $USER

Slow Image Pulls

Problem: Pulling images is slow in rootless mode.

Solution: Enable fuse-overlayfs for better storage performance:

sudo apt install -y fuse-overlayfs

Verify it is being used:

podman info --format '{{.Store.GraphDriverName}}'
# Should output: overlay

Summary

Podman is a mature, secure, and Docker-compatible container engine that eliminates the need for a privileged daemon process. Its rootless-by-default approach, native pod support, and systemd integration make it an excellent choice for both development and production Linux environments. For anyone running containers on Linux servers, Podman provides a clear security advantage without sacrificing compatibility with the Docker ecosystem.

In this guide you learned how to install Podman on Ubuntu, run rootless containers, manage images and pods, use Podman Compose with existing docker-compose.yml files, generate systemd units for automatic container lifecycle management, configure networking, and migrate from Docker.

For related container topics, see our guides on How to Install Docker on Ubuntu and Docker Compose: A Practical Guide for System Administrators.