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
podmancommand launches containers directly as child processes viaconmon(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.
| Feature | Podman | Docker |
|---|---|---|
| Architecture | Daemonless (fork/exec) | Client-server (dockerd daemon) |
| Root requirement | Rootless by default | Requires root daemon (rootless mode optional) |
| Container runtime | crun / runc via conmon | containerd + runc |
| CLI compatibility | Drop-in replacement (podman = docker) | Native CLI |
| Pod support | Native (Kubernetes-style) | Not available |
| Compose support | podman-compose / podman compose | docker compose (built-in plugin) |
| Systemd integration | Native (podman generate systemd) | Requires manual unit files |
| Image format | OCI / Docker | OCI / Docker |
| Build tool | Buildah (integrated) | BuildKit |
| Security model | No root daemon, user namespaces | Root daemon, optional rootless |
| Socket | Optional (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:
- The container process sees itself as
root(UID 0) inside the container. - On the host, the process runs as a subordinate UID mapped from your user’s allowed range.
- The mapping is defined in
/etc/subuidand/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:
- Fully qualify image names: Replace
nginx:alpinewithdocker.io/library/nginx:alpine - Network mode: Some Docker-specific network modes may differ
- 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 Command | Docker Equivalent | Description |
|---|---|---|
podman run | docker run | Create and start a container |
podman ps | docker ps | List running containers |
podman ps -a | docker ps -a | List all containers |
podman images | docker images | List local images |
podman pull | docker pull | Pull an image from a registry |
podman build | docker build | Build an image from a Containerfile/Dockerfile |
podman push | docker push | Push an image to a registry |
podman exec | docker exec | Run a command in a running container |
podman logs | docker logs | View container logs |
podman stop | docker stop | Stop a running container |
podman rm | docker rm | Remove a container |
podman rmi | docker rmi | Remove an image |
podman volume create | docker volume create | Create a named volume |
podman network create | docker network create | Create a network |
podman pod create | N/A | Create a pod |
podman pod ls | N/A | List pods |
podman generate systemd | N/A | Generate systemd unit files |
podman generate kube | N/A | Generate Kubernetes YAML |
podman system prune | docker system prune | Remove unused data |
podman login | docker login | Log in to a container registry |
podman inspect | docker inspect | View 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.