How to Install Podman on Ubuntu: Rootless Container Alternative to Docker

By 

Published on

7 min read

Installing Podman on Ubuntu for rootless containers

Podman is a daemonless container engine that runs OCI containers without a long-lived root process. Each container is started as a regular user-owned process, which removes the privileged daemon that Docker relies on and makes containers safer to run on shared servers. The command-line surface mirrors Docker almost exactly, so most Dockerfiles, image references, and docker run patterns work with little to no change.

This guide explains how to install Podman on Ubuntu, configure rootless mode, run your first container, and use Podman as a drop-in replacement for the Docker CLI.

Quick Reference

For a printable quick reference, see the Podman cheatsheet .

TaskCommand
Install Podmansudo apt install podman
Run a containerpodman run -d --name web -p 8080:80 nginx
List containerspodman ps -a
Build an imagepodman build -t myimage .
List imagespodman images
Pull an imagepodman pull alpine:latest
Stop and removepodman rm -f web
Inspect rootless setuppodman info

Install Podman

Podman has been part of the Ubuntu universe repository since 20.10, so on every supported release you can install it through apt without adding a third-party PPA.

Update the package index, then install:

Terminal
sudo apt update
sudo apt install podman

Confirm the version:

Terminal
podman --version
output
podman version 5.7.0

Ubuntu 26.04 ships Podman 5.7 from the universe repository. Ubuntu 24.04 ships the 4.9 series, while Ubuntu 22.04 ships the 3.4 series, so the exact version depends on the Ubuntu release you are using.

Verify the Rootless Setup

Podman supports both root and rootless modes, and the rootless mode is the one most users want. Run podman info as your regular user and look for rootless: true:

Terminal
podman info | head -20
output
host:
  arch: amd64
  cgroupManager: systemd
  ...
  security:
    rootless: true

Two pieces have to be in place for rootless containers to work: subordinate UID/GID mappings, and a user namespace. Ubuntu sets both up automatically on first run, but you can confirm with:

Terminal
cat /etc/subuid /etc/subgid
output
sara:100000:65536
sara:100000:65536

Each user gets a range of 65,536 subordinate UIDs and GIDs that Podman uses to map container processes to non-overlapping host IDs. If the file is missing your user, add an entry with sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 sara and run podman system migrate to apply the change.

Run Your First Container

Pull and run an Nginx container in detached mode, exposing port 8080 on the host:

Terminal
podman run -d --name web -p 8080:80 nginx
output
Resolved "nginx" as an alias (/etc/containers/registries.conf.d/shortnames.conf)
Trying to pull docker.io/library/nginx:latest...
Getting image source signatures
Copying blob sha256:...
Writing manifest to image destination
Storing signatures
2b9bd4e1f3...

Check that the container is running:

Terminal
podman ps
output
CONTAINER ID  IMAGE                          COMMAND               CREATED         STATUS         PORTS                 NAMES
2b9bd4e1f3a8  docker.io/library/nginx:latest  nginx -g daemon o...  10 seconds ago  Up 10 seconds  0.0.0.0:8080->80/tcp  web

Hit the published port from the host:

Terminal
curl -I http://localhost:8080
output
HTTP/1.1 200 OK
Server: nginx/1.27.4

The response confirms that the user-mode network stack forwarded the request into the container correctly. Stop and remove the container with:

Terminal
podman rm -f web

Configure Registries

By default Podman searches a configured list of registries when you run podman pull nginx (without a registry prefix). The list lives in /etc/containers/registries.conf. To prefer Docker Hub, edit the file:

Terminal
sudo nano /etc/containers/registries.conf

Set the unqualified-search registries:

/etc/containers/registries.conftoml
unqualified-search-registries = ["docker.io", "quay.io"]

Save and exit. From now on podman pull alpine resolves to docker.io/library/alpine:latest, which matches Docker’s default behavior. Save yourself confusion by always pulling with the full reference (podman pull docker.io/library/alpine) in scripts and CI pipelines.

Use Podman as a Docker Replacement

If you already have shell history, Makefiles, or scripts that call docker, install the alias package and keep them working:

Terminal
sudo apt install podman-docker

The package adds a /usr/bin/docker symlink that points to podman, plus a stub that suppresses the daemon-not-running warning. Test it:

Terminal
docker run --rm hello-world
output
Hello from Docker!

Behind the scenes, docker run invoked podman run. Most everyday commands (build, pull, push, images, ps, logs, exec) work identically. The differences show up around Docker Compose and the daemon socket; Podman 4 ships its own socket that Docker Compose can target by setting DOCKER_HOST.

Build an Image with a Dockerfile

Podman builds OCI images from a standard Dockerfile, no changes required. Create a small Flask app to demonstrate:

Terminal
mkdir hello-podman && cd hello-podman
nano app.py
app.pypy
from flask import Flask

app = Flask(__name__)


@app.route("/")
def index():
    return "Hello from Podman!"


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

Write the Dockerfile:

Dockerfiledockerfile
FROM python:3.12-slim
WORKDIR /app
RUN pip install --no-cache-dir flask
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]

Build the image:

Terminal
podman build -t hello-podman .
output
STEP 1/6: FROM python:3.12-slim
STEP 2/6: WORKDIR /app
...
Successfully tagged localhost/hello-podman:latest

Run a container from the new image:

Terminal
podman run -d --name hello -p 5000:5000 hello-podman
curl http://localhost:5000
output
Hello from Podman!

The image and container both live in the user’s storage (~/.local/share/containers), so no sudo is involved at any step.

Run Pods of Containers

The name “Podman” comes from its support for Kubernetes-style pods: groups of containers that share a network namespace. Create an empty pod, then add containers to it:

Terminal
podman pod create --name webapp -p 8080:80
podman run -d --pod webapp --name redis redis:7
podman run -d --pod webapp --name nginx nginx

Inside the pod, nginx can reach Redis at localhost:6379 because they share the network namespace. List pods with:

Terminal
podman pod ps
output
POD ID        NAME    STATUS   CREATED         INFRA ID      # OF CONTAINERS
8f4d7b8c0e1f  webapp  Running  20 seconds ago  c2a8f9b1d3e4  3

The count of three includes an infra container that holds the shared namespaces.

Run Podman as a systemd Service

To start a container at boot in rootless mode, use a Quadlet container unit. Quadlet is Podman’s current systemd integration method and keeps the container definition in a small, readable file.

Terminal
mkdir -p ~/.config/containers/systemd
nano ~/.config/containers/systemd/web.container

Add the container definition:

~/.config/containers/systemd/web.containerini
[Unit]
Description=Rootless Nginx container

[Container]
Image=docker.io/library/nginx:latest
ContainerName=web
PublishPort=8080:80

[Service]
Restart=always

[Install]
WantedBy=default.target

Reload the user systemd manager, then start the generated service:

Terminal
systemctl --user daemon-reload
systemctl --user start web.service

Enable lingering so the user services keep running after logout:

Terminal
sudo loginctl enable-linger sara

The [Install] section tells the Podman systemd generator to attach the service to the user’s default target. The container now starts without root or a system-wide daemon.

Troubleshooting

Error: short-name "nginx" did not resolve to an alias
The unqualified-search-registries list is empty. Edit /etc/containers/registries.conf and add at least docker.io, or pull with the full reference (podman pull docker.io/library/nginx).

ERRO[0000] cannot find UID/GID for user
The current user has no entry in /etc/subuid or /etc/subgid. Run sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER, then podman system migrate.

Ports under 1024 refuse to bind in rootless mode
The kernel restricts low ports for non-root users. Either publish to a high port (-p 8080:80), or allow your user to bind low ports with sudo sysctl net.ipv4.ip_unprivileged_port_start=80.

FAQ

Is Podman a drop-in Docker replacement?
For everyday run, build, pull, push, and exec workflows, yes. Compose and the long-lived socket need extra setup. Most CI jobs only need the CLI and switch over without changes.

Does Podman work with Docker Compose?
Yes. Start the Podman socket with systemctl --user enable --now podman.socket, export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock, and run docker compose up.

Are Podman images compatible with Docker?
Yes. Both produce OCI-compliant images, so an image built with podman build runs unchanged under Docker, and vice versa.

Conclusion

Podman gives you the Docker workflow without the daemon and without root, which is often what you want on a multi-user server or a developer workstation. Install it from apt, alias docker if you have existing scripts, and the rest of the CLI feels familiar.

For follow-up reading, see our guides on installing Docker on Ubuntu 26.04 and building Docker images with a Dockerfile .

Linuxize Weekly Newsletter

A quick weekly roundup of new tutorials, news, and tips.

About the authors

Dejan Panovski

Dejan Panovski

Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 800+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.

View author page