Chapter 12: The Ecosystem
Topic 74

Podman and Buildah

ToolingRootless

Podman runs the same OCI images with a Docker-compatible CLI, but with no central daemon: each podman run forks the container as a child of your shell, and it runs rootless by default. That architectural difference removes the root-owned dockerd socket that Chapter 1 and Chapter 10 flagged as a root-equivalent risk.

Where Docker bundles build, run, and image transfer into one binary, Podman splits them across focused tools: Podman runs, Buildah builds, and skopeo moves images between registries and stores. Three tools, the same OCI artifact, no always-on root process owning everything.

Two ways to run the same OCI image
Docker
A central long-lived daemon (dockerd) owns containers, builds, and the socket — root by default, one binary for everything.
Podman
Daemonless fork/exec, each container a child of your shell, rootless by default and CLI-compatible. Buildah builds, skopeo moves images.

Daemonless, Fork/Exec

There is no podman daemon owning your containers. Podman forks and execs each container directly via runc or crun, so a crashed Podman does not take the containers with it, and there is no always-on root socket to compromise. The container is a child of the process that launched it, which is a smaller attack surface and a simpler failure model than a central daemon that everything routes through.

Podman, rootless and daemonless — and the alias that makes scripts work
$ podman run -d -p 8080:8080 \
    --name driftwood-web \
    registry.driftwood.example/driftwood/web:1.4.0
$ podman ps

# most existing Docker commands work unchanged:
$ alias docker=podman
$ docker build -t driftwood/web:1.4.0 .

# run a Linux host's containers as services, surviving logout/reboot:
$ podman generate systemd --new --name driftwood-web > driftwood-web.service

The alias covers the common path, but it is not byte-identical to Docker — Compose, the socket API, and a few flags differ. Treat alias docker=podman as a strong default that you verify, not a guarantee, especially for any script that talks to docker.sock directly.

Rootless by Default

Podman runs as your user using user namespaces — the rootless model Chapter 10 presented as an opt-in is Podman's default. A container breakout therefore lands as an unprivileged user, not as host root, which shrinks the blast radius of an escape to whatever your own account can touch. This is the single biggest reason to reach for Podman on a multi-user or hardened host: there is no root daemon to compromise in the first place.

Pods — A Kubernetes-Shaped Primitive

Podman groups containers into a pod sharing a network namespace — the same concept Kubernetes uses, and the source of the name. podman generate kube emits Kubernetes YAML from running containers, and podman kube play runs a Kubernetes manifest locally. That round-trip makes Podman a natural on-ramp to the sibling Kubernetes course: you model the workload in the same shape locally before a cluster ever enters the picture.

Buildah and skopeo — Unbundled

Buildah builds OCI images, either from a Dockerfile or scriptably step by step without a full Dockerfile at all. skopeo inspects and copies images between registries and stores without a daemon and without running anything — skopeo copy moves driftwood/web from one registry to another, and skopeo inspect reads a manifest without pulling it. Docker folds these jobs into one engine; Podman splits them into tools you can use independently in CI.

systemd and Quadlet — Running Containers as Services

Because there is no daemon to own restarts, Podman integrates with systemd. A Quadlet unit file declares a container as a native systemd service with restart policies and dependencies, so the OS — not a container daemon — handles bringing Driftwood's containers up at boot and back after a crash. A container started loose in your shell does not survive logout; a Quadlet unit survives logout and reboot because systemd owns it.

Docker vs Podman

Docker — a long-lived root daemon (dockerd) owns all containers, builds, and the socket: one binary, the dominant ecosystem, rootful by default with rootless as an opt-in. Choose it for the largest tooling support and the Docker Desktop and Compose workflows most teams already run.

Podman — daemonless and rootless by default, CLI-compatible (alias docker=podman), with builds via Buildah and a native systemd/Quadlet integration. Choose it when a root daemon is unacceptable — multi-user hosts, hardened servers — or you want a Kubernetes-shaped local pod model. Both run identical OCI images.

Common Mistakes
  • Assuming alias docker=podman is 100% transparent — Compose, the docker.sock API, and a few flags differ; podman compose and the Podman socket exist but are not byte-identical, so test any script that talks to the socket.
  • Expecting a Podman container started in your shell to keep running after you log out without arranging supervision — there is no daemon babysitting it; use a Quadlet/systemd unit for anything that must survive logout or reboot.
  • Running Podman rootless and being surprised that binding port 80 fails or that heavy overlay I/O behaves differently — rootless cannot bind low ports without configuration and uses user-namespace mappings that change file-ownership semantics.
  • Treating Buildah and docker build as producing different artifacts — both emit OCI images the other can run; the difference is the build tooling, not the output driftwood/web.
Best Practices
  • Choose Podman when a root-owned daemon is the unacceptable risk — multi-user or hardened hosts — since rootless-by-default shrinks the blast radius of a breakout to an unprivileged user.
  • Run single-host production containers under Quadlet/systemd units rather than leaving them as shell children, so restarts, ordering, and boot-time start are the OS's job.
  • Use skopeo to inspect and copy images between registries in CI without pulling and running them, keeping image movement off any container runtime.
  • Keep building OCI-standard images with Buildah or Docker so the choice of engine never locks in the artifact — driftwood/web runs under either.
Comparable tools Podman · Buildah · skopeo the daemonless suite: run, build, transfer images nerdctl the containerd-based Docker-compatible alternative Docker Engine · BuildKit the bundled-daemon counterparts running the same images

Knowledge Check

What is the architectural difference between Podman and Docker?

  • Podman is daemonless and rootless by default; each container is forked from your shell with no central root daemon
  • Podman uses its own proprietary image and layer format that the Docker engine simply cannot pull, load, or run at all
  • Podman runs a heavier always-on root daemon than dockerd does, listening on its own socket on the host
  • Podman only works as a component running inside an already-provisioned Kubernetes cluster

Why does running rootless by default reduce risk?

  • A container breakout lands as an unprivileged user, not host root, so the blast radius is limited to your account
  • It encrypts the container's root filesystem at rest so even a successful breakout reveals nothing useful to the attacker
  • It makes container breakouts technically impossible at the kernel level under any workload
  • It blocks all outbound and inbound network access from inside the container by default

What do Buildah and skopeo do that Docker folds into one engine?

  • Buildah builds OCI images and skopeo inspects and copies them between registries without a daemon
  • They convert standard images into a Podman-only proprietary on-disk format before storing them
  • They schedule and orchestrate containers across multiple hosts in a cluster, replacing the need for Kubernetes
  • They both run containers as well, just faster and with less memory than Podman itself does

A Podman container started in your shell stops when you log out. What is the right fix for a single-host service?

  • Declare it as a Quadlet/systemd unit so the OS owns restarts, ordering, and boot-time start
  • Install dockerd so its always-on root daemon keeps the container alive after you log out
  • Add a longer restart timeout flag and leave the container running in your login shell session
  • Rebuild the image so the entrypoint relaunches itself in the background after you log out

You got correct