Logo

Podman: rootless containers without daemon

Podman: rootless containers without daemon

Podman is the Docker alternative: same CLI, no root daemon, rootless by default. Pushed by Red Hat, native systemd integration. The future of containers in enterprise environments.

Introduction

Podman ("Pod Manager"):

  • 100% Docker CLI compatible (alias docker=podman)
  • No daemon: each command runs as a user process
  • Rootless by default
  • Native systemd unit generation
  • Supports pods (group of containers sharing resources, like Kubernetes)
  • Standard OCI format

Advantages vs Docker:

  • Security: no root socket exposed
  • No SPOF (no daemon)
  • Native systemd integration

Prerequisites

  • Linux VPS Debian 12 / Ubuntu 24.04 / Rocky 9
  • Root access to install
  • A non-root user for rootless usage

Step 1: Installation

Debian / Ubuntu

sudo apt update
sudo apt install -y podman
podman --version

Rocky / RHEL

sudo dnf install -y podman

Step 2: First container (rootless)

As normal user (not root):

podman run -d -p 8080:80 --name web nginx
podman ps
curl http://localhost:8080

Container runs as your user. No daemon, no root.

Step 3: Docker-compatible commands

podman ps
podman images
podman pull alpine
podman exec -it web bash
podman logs web
podman stop web
podman rm web
podman rmi nginx
podman inspect web

Step 4: docker = podman alias

For transparent transition:

sudo apt install -y podman-docker

/usr/bin/docker is created as symlink to podman.

Step 5: Build an image

Dockerfile (or Containerfile):

FROM alpine:3.18
RUN apk add --no-cache nginx
COPY index.html /var/www/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
podman build -t mysite:1.0 .
podman run -d -p 8080:80 mysite:1.0

Step 6: Generate systemd service

Instead of a daemon to restart containers at boot, Podman generates systemd services directly:

podman create --name myapp -p 8080:80 nginx
podman generate systemd --new --files --name myapp

Generates ~/container-myapp.service. Install:

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

To run service after logout:

loginctl enable-linger $USER

Step 7: Quadlet (modern way)

Podman 4.4+: Quadlet simplifies systemd unit creation. Create ~/.config/containers/systemd/myapp.container:

[Unit]
Description=My Nginx app
After=network-online.target

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

[Service]
Restart=on-failure

[Install]
WantedBy=multi-user.target default.target
systemctl --user daemon-reload
systemctl --user start myapp

Quadlet generates the systemd service on the fly.

Step 8: Pods (container groups)

A Podman pod = multiple containers sharing network + storage:

podman pod create --name webpod -p 8080:80
podman run -d --pod webpod --name nginx nginx
podman run -d --pod webpod --name redis redis

nginx and redis share localhost. nginx reaches redis at localhost:6379.

Step 9: podman-compose

For docker-compose.yml compatibility:

sudo apt install -y podman-compose
podman-compose up -d
podman-compose ps
podman-compose down

Step 10: Security: SELinux / AppArmor

Podman natively integrates with SELinux. If SELinux is enforcing, add :Z to volumes:

podman run -d -v /home/user/data:/data:Z nginx

Step 11: Networking

podman network create mynet
podman run -d --network=mynet --name web nginx
podman run -d --network=mynet --name app myapp:1.0

podman exec app curl http://web

Step 12: Export / Import to Kubernetes

podman generate kube myapp > myapp-k8s.yaml
kubectl apply -f myapp-k8s.yaml

Reverse:

podman play kube myapp-k8s.yaml

Podman launches containers as described in K8s YAML. Useful for local dev without K8s.

Troubleshooting

"newuidmap: write to uid_map failed"

Rootless limits misconfigured:

cat /etc/subuid /etc/subgid

Should have a range:

youruser:100000:65536

Else:

sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 youruser
podman system migrate

Port < 1024 in rootless

Rootless can't bind privileged ports. Solutions:

  1. Use port >= 1024 then reverse-proxy
  2. Allow your user:
sudo setcap 'cap_net_bind_service=+ep' /usr/bin/podman
  1. Enable net.ipv4.ip_unprivileged_port_start=80:
echo "net.ipv4.ip_unprivileged_port_start=80" | sudo tee /etc/sysctl.d/99-podman.conf
sudo sysctl --system

"Error: error preparing container"

Usually SELinux or permissions:

podman info
podman events

Image won't pull

Configure registries in ~/.config/containers/registries.conf:

[[registry]]
location = "docker.io"

Useful commands

podman run -d -p 8080:80 nginx
podman ps -a
podman stop myapp
podman rm myapp
podman exec -it myapp sh

podman pull alpine
podman build -t myapp:1.0 .
podman tag myapp:1.0 registry/myapp:1.0
podman push registry/myapp:1.0
podman images

podman system info
podman system prune -a
podman version

podman pod ps
podman pod create --name webpod -p 8080:80
podman pod start webpod

podman generate systemd --new --files --name myapp
podman generate kube myapp > k8s.yaml
podman play kube k8s.yaml

Conclusion

Podman replaces Docker with advantages:

  • No daemon = no SPOF
  • Rootless = better security
  • Native systemd integration
  • Kubernetes YAML compatible

Going further:

  • Migrate your docker-compose stack with podman-compose or Quadlet
  • Combine with Buildah for daemon-less builds
  • Explore Podman Desktop (cross-platform GUI)

Resources

Join our Discord community server

For any questions, suggestions, or just to chat with the community, join us on Discord!

900+Members