Podman is a daemonless, rootless container engine for running OCI-compliant containers on Linux. Unlike Docker, Podman does not require a background daemon process – containers run as direct child processes of the Podman command, making it more secure and suitable for systemd integration. This guide covers installing and using Podman on Ubuntu 24.04 and Rocky Linux 10, including container management, pods, rootless containers, networking, volumes, Podman Compose, Buildah image builds, Quadlet systemd integration, and generating systemd service units.

Prerequisites

  • A server or workstation running Ubuntu 24.04 LTS or Rocky Linux 10
  • Root or sudo access
  • At least 2 GB RAM and 10 GB free disk space
  • Internet connectivity to pull container images

What Makes Podman Different from Docker

Docker uses a client-server architecture where every container is a child process of the Docker daemon. If the daemon crashes, all running containers go down with it. Podman takes a different approach – each container is a direct child process of the Podman command. There is no central daemon. This architecture has several practical benefits:

  • No daemon – Podman launches containers directly using the OCI runtime (typically crun or runc). No background service to manage or monitor.
  • Rootless by default – Regular users can run containers without root privileges. User namespaces map container UIDs to unprivileged host UIDs.
  • Systemd integration – Because containers are regular processes, systemd can manage them directly. Podman can generate systemd unit files and supports Quadlet for declarative container management.
  • Pod support – Podman natively supports pods (groups of containers sharing namespaces), matching the Kubernetes pod concept.
  • Docker CLI compatible – Most Docker commands work with Podman by replacing docker with podman.

Podman vs Docker Comparison

FeaturePodmanDocker
ArchitectureDaemonless, fork-exec modelClient-server with dockerd daemon
Rootless containersBuilt-in, first-class supportAvailable but requires extra setup
Pod supportNative (like Kubernetes pods)Not supported
Systemd integrationNative with Quadlet and generate systemdRequires manual unit files
Image formatOCI and Docker formatsDocker format (OCI compatible)
Compose supportpodman-compose or docker-compose with socketdocker compose (built-in plugin)
Build toolBuildah (integrated) or podman buildBuildKit
Container runtimecrun (default) or runcrunc (default) or crun
Registry supportMultiple registries in registries.confDocker Hub as default
GUI toolPodman DesktopDocker Desktop

Step 1: Install Podman on Ubuntu 24.04

Podman is available in the default Ubuntu 24.04 repositories. Install it with apt.

$ sudo apt update
$ sudo apt install -y podman

Verify the installation.

$ podman --version
podman version 4.9.3

Check the system configuration for rootless support.

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

Step 2: Install Podman on Rocky Linux 10

On Rocky Linux 10, Podman ships as part of the container-tools module in the default repositories.

$ sudo dnf install -y podman

Verify the installed version.

$ podman --version
podman version 5.x.x

On Rocky Linux with SELinux enabled, container volumes and custom storage paths require correct SELinux labels. If you use a custom graphroot directory, apply the proper context as described in our guide on setting SELinux context labels for Podman storage.

Step 3: Configure Container Registries

Podman supports pulling images from multiple container registries. The configuration file is /etc/containers/registries.conf. By default, Podman searches Docker Hub, Quay.io, and other registries.

View the current unqualified search registries.

$ podman info --format '{{.Registries.Search}}'
[docker.io quay.io]

To add a private registry, edit the registries configuration file.

$ sudo vim /etc/containers/registries.conf

Add your registry under the unqualified-search-registries list.

unqualified-search-registries = ["docker.io", "quay.io", "registry.example.com"]

For setting up your own container registry, see our guide on installing a secure container registry with Podman.

Step 4: Pull and Manage Container Images

Pull images from a registry using podman pull.

$ podman pull docker.io/library/nginx:latest
$ podman pull docker.io/library/alpine:latest
$ podman pull docker.io/library/ubuntu:24.04

List all downloaded images.

$ podman images
REPOSITORY                    TAG       IMAGE ID       CREATED        SIZE
docker.io/library/nginx       latest    a8758716bb6a   2 weeks ago    192 MB
docker.io/library/alpine      latest    b7b28af77ffe   3 weeks ago    7.8 MB
docker.io/library/ubuntu      24.04     3db8720ecbf5   4 weeks ago    78.1 MB

Tag an image with a custom name.

$ podman tag docker.io/library/nginx:latest mywebserver:v1

Remove an image by name or ID.

$ podman rmi mywebserver:v1

Search for images in configured registries.

$ podman search nginx --limit 5
INDEX       NAME                                DESCRIPTION                 STARS   OFFICIAL
docker.io   docker.io/library/nginx             Official build of Nginx.    19000   [OK]
docker.io   docker.io/nginxinc/nginx-unprivileged Unprivileged Nginx         150
docker.io   docker.io/bitnami/nginx             Bitnami container for Nginx 180

Step 5: Run Containers with Podman

Run a basic container that prints a message and exits.

$ podman run --rm docker.io/library/alpine echo "Hello from Podman"
Hello from Podman

Run an Nginx container in detached mode with port mapping.

$ podman run -d --name webserver -p 8080:80 docker.io/library/nginx:latest
a3b7f2c9d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1

Verify the container is running.

$ podman ps
CONTAINER ID  IMAGE                           COMMAND               CREATED        STATUS            PORTS                   NAMES
a3b7f2c9d4e5  docker.io/library/nginx:latest  nginx -g daemon o...  5 seconds ago  Up 5 seconds ago  0.0.0.0:8080->80/tcp    webserver

Test the web server responds.

$ curl -s http://localhost:8080 | head -5
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>

Interactive Shell Access

Start a container with an interactive shell using the -it flags.

$ podman run -it --rm docker.io/library/ubuntu:24.04 bash
root@c5d8e2f1a4b7:/# cat /etc/os-release | head -3
PRETTY_NAME="Ubuntu 24.04 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
root@c5d8e2f1a4b7:/# exit

Execute Commands in a Running Container

Use podman exec to run commands inside a running container.

$ podman exec webserver nginx -v
nginx version: nginx/1.27.x

Open an interactive shell in the running container.

$ podman exec -it webserver bash

View Container Logs

Check container logs with podman logs.

$ podman logs webserver

Follow logs in real time.

$ podman logs -f webserver

Show only the last 20 lines.

$ podman logs --tail 20 webserver

Stop and Remove Containers

Stop a running container.

$ podman stop webserver

List all containers including stopped ones.

$ podman ps -a
CONTAINER ID  IMAGE                           COMMAND               CREATED         STATUS                     PORTS                   NAMES
a3b7f2c9d4e5  docker.io/library/nginx:latest  nginx -g daemon o...  10 minutes ago  Exited (0) 5 seconds ago   0.0.0.0:8080->80/tcp    webserver

Remove a stopped container.

$ podman rm webserver

Remove all stopped containers at once.

$ podman container prune

Step 6: Rootless Containers

One of Podman’s strongest features is rootless container support. Any regular user can run containers without sudo or root access. This works through user namespaces – the container sees UID 0 (root) inside, but on the host it maps to your unprivileged user.

Verify your user has subordinate UID/GID ranges configured.

$ grep $USER /etc/subuid
josphat:100000:65536

$ grep $USER /etc/subgid
josphat:100000:65536

If these entries are missing, add them as root.

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

Run a container as a regular user without sudo.

$ podman run --rm docker.io/library/alpine id
uid=0(root) gid=0(root) groups=0(root)

Inside the container, the process runs as root. On the host, check the actual UID.

$ podman run -d --name rootless-test docker.io/library/alpine sleep 3600
$ podman top rootless-test huser
HUSER
1000

The container process runs as your regular user (UID 1000) on the host. No root privileges are required or granted.

Rootless containers have one limitation: binding to ports below 1024 requires either root or adjusting the unprivileged port start value.

$ sudo sysctl net.ipv4.ip_unprivileged_port_start=80

To make this permanent, add the setting to sysctl configuration.

$ echo "net.ipv4.ip_unprivileged_port_start=80" | sudo tee /etc/sysctl.d/podman-ports.conf
$ sudo sysctl --system

Step 7: Working with Podman Pods

Pods group multiple containers that share the same network namespace, similar to Kubernetes pods. Containers in a pod communicate over localhost without port mapping between them.

Create a pod with a published port.

$ podman pod create --name webapp -p 8080:80
e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5

List pods to confirm creation.

$ podman pod list
POD ID         NAME    STATUS    CREATED          # OF CONTAINERS   INFRA ID
e4f5a6b7c8d9   webapp  Created   10 seconds ago   1                 f1a2b3c4d5e6

Every pod starts with an infra container that holds the network namespace. Add an Nginx container to the pod.

$ podman run -d --pod webapp --name web-frontend docker.io/library/nginx:latest

Add a second container to the same pod. Since both containers share the network namespace, the app container can reach Nginx on localhost:80.

$ podman run -d --pod webapp --name web-app docker.io/library/alpine sleep 3600

Verify the pod now has three containers (infra + two application containers).

$ podman ps -a --pod --filter pod=webapp
CONTAINER ID  IMAGE                           COMMAND               STATUS            NAMES           POD ID         PODNAME
f1a2b3c4d5e6  registry.k8s.io/pause:3.9                             Up 2 minutes ago  e4f5a6b7-infra  e4f5a6b7c8d9   webapp
b2c3d4e5f6a7  docker.io/library/nginx:latest  nginx -g daemon o...  Up 1 minute ago   web-frontend    e4f5a6b7c8d9   webapp
c3d4e5f6a7b8  docker.io/library/alpine:latest  sleep 3600           Up 30 seconds ago web-app         e4f5a6b7c8d9   webapp

Test that the app container can reach Nginx over localhost.

$ podman exec web-app wget -qO- http://localhost:80 | head -3
<!DOCTYPE html>
<html>
<head>

Stop and remove the entire pod with all its containers.

$ podman pod stop webapp
$ podman pod rm webapp

Port mappings must be defined at pod creation time. You cannot add new port mappings after the pod is created.

Step 8: Networking in Podman

Podman supports multiple networking backends depending on whether containers run as root or rootless.

Rootless Networking with slirp4netns and pasta

Rootless containers use pasta (on newer Podman versions) or slirp4netns for network connectivity. These tools create a user-mode network stack without requiring root. Pasta is the default on Podman 5.x and offers better performance than slirp4netns.

Check which network mode is active.

$ podman info --format '{{.Host.NetworkBackendInfo.Backend}}'
netavark

Root Networking with Netavark and CNI

Rootful containers use Netavark (the default on modern Podman) or CNI plugins for networking. Netavark provides bridge networking, DNS resolution between containers, and firewall rule management.

Create a custom network.

$ podman network create mynet
mynet

List available networks.

$ podman network ls
NETWORK ID    NAME        DRIVER
2f259bab93aa  podman      bridge
a1b2c3d4e5f6  mynet       bridge

Run containers on the custom network. Containers on the same network can resolve each other by name.

$ podman run -d --name db --network mynet docker.io/library/postgres:16 -e POSTGRES_PASSWORD=secret
$ podman run -d --name app --network mynet docker.io/library/alpine sleep 3600

Test DNS resolution between containers.

$ podman exec app ping -c 2 db
PING db (10.89.0.2): 56 data bytes
64 bytes from 10.89.0.2: seq=0 ttl=64 time=0.152 ms

Remove the custom network after stopping containers that use it.

$ podman stop db app
$ podman rm db app
$ podman network rm mynet

Step 9: Volumes and Bind Mounts

Podman supports named volumes and bind mounts for persistent data storage. Named volumes are managed by Podman, while bind mounts map a host directory directly into the container.

Named Volumes

Create and use a named volume.

$ podman volume create appdata
$ podman run -d --name db -v appdata:/var/lib/postgresql/data docker.io/library/postgres:16 -e POSTGRES_PASSWORD=secret

List volumes and inspect the storage location.

$ podman volume ls
DRIVER    VOLUME NAME
local     appdata

$ podman volume inspect appdata --format '{{.Mountpoint}}'
/home/josphat/.local/share/containers/storage/volumes/appdata/_data

Bind Mounts

Map a host directory into a container using -v with an absolute path.

$ mkdir -p /home/josphat/web-content
$ echo "Hello from Podman" > /home/josphat/web-content/index.html
$ podman run -d --name web -p 8080:80 -v /home/josphat/web-content:/usr/share/nginx/html:Z docker.io/library/nginx:latest

The :Z suffix tells Podman to relabel the volume for SELinux. On Rocky Linux 10 with SELinux enforcing, this is required for bind mounts to work. On Ubuntu, the :Z flag is harmless and can be included for portability.

Verify the bind mount serves the custom content.

$ curl http://localhost:8080
Hello from Podman

Remove unused volumes with the prune command.

$ podman volume prune

Step 10: Build Container Images with Buildah

Buildah is the image-building tool that Podman uses under the hood. It can build images from Dockerfiles (Containerfiles) and also supports scripted builds without a Dockerfile. For a detailed walkthrough, see our guide on building OCI container images with Buildah.

Install Buildah if it is not already present.

# Ubuntu 24.04
$ sudo apt install -y buildah

# Rocky Linux 10
$ sudo dnf install -y buildah

Build from a Containerfile

Create a project directory and a Containerfile.

$ mkdir -p ~/myapp && cd ~/myapp

Create the Containerfile with the following content.

FROM docker.io/library/python:3.12-slim
WORKDIR /app
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]

Create a simple Python application file.

$ cat > ~/myapp/app.py <<'PYEOF'
from http.server import HTTPServer, SimpleHTTPRequestHandler
class Handler(SimpleHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"Hello from Podman container")
HTTPServer(("0.0.0.0", 5000), Handler).serve_forever()
PYEOF

Build the image with podman build (which uses Buildah internally).

$ podman build -t myapp:latest ~/myapp/
STEP 1/5: FROM docker.io/library/python:3.12-slim
STEP 2/5: WORKDIR /app
STEP 3/5: COPY app.py .
STEP 4/5: EXPOSE 5000
STEP 5/5: CMD ["python", "app.py"]
COMMIT myapp:latest
Successfully tagged localhost/myapp:latest

Run the custom image.

$ podman run -d --name myapp -p 5000:5000 myapp:latest
$ curl http://localhost:5000
Hello from Podman container

Scripted Buildah Build

Buildah can build images without a Containerfile using command-line instructions.

$ container=$(buildah from docker.io/library/alpine:latest)
$ buildah run $container apk add --no-cache curl
$ buildah config --cmd "/bin/sh" $container
$ buildah commit $container alpine-curl:latest
$ buildah rm $container

The resulting image is available to both Podman and Buildah.

$ podman images | grep alpine-curl
localhost/alpine-curl   latest   d4e5f6a7b8c9   10 seconds ago   12.3 MB

Step 11: Podman Compose

Podman Compose is a drop-in replacement for Docker Compose that works with Podman. It reads standard docker-compose.yml files and creates Podman containers and pods.

Install podman-compose using pip.

# Ubuntu 24.04
$ sudo apt install -y podman-compose

# Rocky Linux 10
$ sudo dnf install -y podman-compose

Create a docker-compose.yml file for a WordPress setup with MariaDB.

version: "3.9"
services:
  db:
    image: docker.io/library/mariadb:11
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wpuser
      MYSQL_PASSWORD: wppass
    volumes:
      - db_data:/var/lib/mysql

  wordpress:
    image: docker.io/library/wordpress:latest
    depends_on:
      - db
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: wpuser
      WORDPRESS_DB_PASSWORD: wppass
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wp_data:/var/www/html

volumes:
  db_data:
  wp_data:

Start the application stack.

$ podman-compose up -d
Creating pod ...
Creating container db ...
Creating container wordpress ...

Check the running containers.

$ podman-compose ps

Stop and remove the stack.

$ podman-compose down

Step 12: Generate Systemd Units with Podman

Podman can generate systemd unit files from running containers. This allows systemd to manage container lifecycle – start on boot, restart on failure, and proper shutdown ordering. For more details on running containers as services, see our guide on running Podman containers as systemd services.

First, create and start a container.

$ podman run -d --name nginx-service -p 8080:80 docker.io/library/nginx:latest

Generate a systemd unit file from the running container.

$ podman generate systemd --name nginx-service --new --files
/home/josphat/container-nginx-service.service

The --new flag creates a unit that recreates the container on each start, which is the recommended approach. Move the generated file to the systemd user directory.

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

Verify the service is active.

$ systemctl --user status container-nginx-service.service
  Active: active (running)

To allow user services to run after logout, enable lingering for your user.

$ loginctl enable-linger $USER

Step 13: Quadlet for Systemd Integration

Quadlet is the modern replacement for podman generate systemd. Instead of generating unit files from running containers, you write declarative .container files and systemd generates the units automatically. Quadlet is included in Podman 4.4 and later.

Create a Quadlet file for a rootless Nginx container.

$ mkdir -p ~/.config/containers/systemd/

Create the container definition file.

$ vim ~/.config/containers/systemd/nginx.container

Add the following content.

[Unit]
Description=Nginx web server container
After=local-fs.target

[Container]
Image=docker.io/library/nginx:latest
PublishPort=8080:80
Volume=web-content.volume:/usr/share/nginx/html:Z

[Service]
Restart=always
TimeoutStartSec=120

[Install]
WantedBy=default.target

Create a matching volume definition.

$ vim ~/.config/containers/systemd/web-content.volume

Add the volume configuration.

[Volume]
Label=app=nginx

Reload systemd to pick up the new Quadlet files and start the service.

$ systemctl --user daemon-reload
$ systemctl --user start nginx.service
$ systemctl --user status nginx.service
  Active: active (running)

Quadlet also supports .pod, .network, .kube, and .image file types for managing pods, networks, Kubernetes YAML, and image pulls through systemd.

Step 14: Inspect and Monitor Containers

View detailed information about a container with podman inspect.

$ podman inspect webserver --format '{{.NetworkSettings.IPAddress}}'
10.88.0.5

View running processes inside a container.

$ podman top webserver
USER    PID   PPID   %CPU    ELAPSED          TTY   TIME    COMMAND
root    1     0      0.000   12m30.123456s    ?     0s      nginx: master process nginx -g daemon off;
nginx   29    1      0.000   12m29.654321s    ?     0s      nginx: worker process

Monitor resource usage across all running containers.

$ podman stats --no-stream
ID            NAME        CPU %   MEM USAGE / LIMIT     MEM %   NET I/O         BLOCK I/O    PIDS
a3b7f2c9d4e5  webserver   0.05%   5.234MiB / 3.842GiB   0.13%   1.23kB / 456B   0B / 0B      3

To scan container images for known security vulnerabilities before deploying them, consider using Trivy for container image scanning.

Podman Desktop

Podman Desktop is an open-source graphical application for managing containers, images, pods, and Kubernetes environments from a desktop interface. It supports Podman, Docker, and Kubernetes, and runs on Linux, macOS, and Windows.

Key features include container and pod management, image building, Kubernetes integration, extension support, and migration from Docker Desktop. Download it from the official site at podman-desktop.io. For command-line workflows covered in this guide, the Podman CLI is all you need – Podman Desktop is optional for users who prefer a GUI.

Useful Podman Commands Reference

CommandDescription
podman pull image:tagDownload a container image from a registry
podman run -d --name n imageRun a container in detached mode
podman ps -aList all containers (running and stopped)
podman exec -it name cmdExecute a command in a running container
podman logs -f nameFollow container log output
podman stop nameStop a running container
podman rm nameRemove a stopped container
podman rmi imageRemove a container image
podman pod create --name pCreate a new pod
podman pod listList all pods
podman network create netCreate a custom network
podman volume create volCreate a named volume
podman system prune -aRemove all unused data (containers, images, volumes)
podman generate systemd --newGenerate systemd unit from a container

Conclusion

Podman provides a secure, daemonless container runtime that works as a direct replacement for Docker on Ubuntu 24.04 and Rocky Linux 10. With rootless containers, native pod support, Quadlet systemd integration, and Buildah for image builds, it covers the full container workflow from development to production. For production deployments, enable automatic updates with podman auto-update, configure resource limits on containers, use named volumes for persistent data, and set up monitoring for container health checks.

Related Guides

LEAVE A REPLY

Please enter your comment!
Please enter your name here