Skip to main content
  1. Posts/
  2. Podman/

Podman: A Modern Alternative to Docker

··2287 words·11 mins· loading · loading · ·
Stilicho2011
Author
Stilicho2011
Writing about homelab, self-hosting, automation and open-source solutions
Table of Contents
Podman - This article is part of a series.
Part : This Article

Introduction
#

If you enjoyed this article, you can support the author by becoming a sponsor on Boosty (link in the contacts section).

When people talk about containers, nine out of ten by default mean Docker. It really has been synonymous with containerization for years: docker run, docker-compose up, and Dockerfile are the minimal set of things almost anyone who has ever deployed a service on something other than bare metal knows. But Docker has an architectural quirk that has, over time, raised questions for part of the community, myself included - a constantly running daemon with root privileges.

Podman is an attempt to solve exactly this problem, not cosmetically but at the architectural level. It’s a daemonless container engine, with a full rootless mode out of the box and native systemd integration. It replaced Docker in my homelab a long time ago, and in this article I want to go through where Podman came from, how it’s built internally, how it fundamentally differs from Docker, and what pods, Quadlet, and Podlet are - terms that usually cause the most confusion.

Where Podman came from
#

Podman’s history isn’t the most obvious, and it’s useful to know it in order to understand why the project is built the way it is.

It all started in 2017 inside the CRI-O project (a container runtime for Kubernetes) with a small utility working under the name kpod - built by Red Hat engineers who needed a way to debug and inspect containers created by CRI-O without having to spin up a full Docker installation. Nobody on the team liked the name kpod, and after a few months of development the tool was split off into a separate project - the libpod library, responsible for managing pods and containers without a daemon. A few months after that, in early 2018, the first public release came out under a new name - Podman, standing for “POD MANager.”

An important nuance: Podman is not a Docker fork. It’s an independent implementation written from scratch, which at the same time uses the same low-level standards and largely the same ecosystem components as Docker:

  • runc or crun - the low-level OCI runtime that directly launches the container;
  • conmon - a lightweight supervisor that watches the container process;
  • containers/storage - a library for storing images and layers;
  • containers/image - handling registries and image formats.

Today Podman is developed under the Red Hat umbrella, is part of the containers project on GitHub, and is the default container engine in RHEL, Fedora, CentOS Stream, and a number of other distributions where Docker hasn’t shipped out of the box for several years now.

What Podman is used for
#

Essentially Podman covers the same tasks as Docker - building images, running containers, working with registries - but with an eye toward different use cases:

  • local development and testing of containerized applications;
  • running containers and pods on servers and in homelabs, especially where you want to avoid a daemon;
  • running containers without root access, which is critical for multi-tenant environments;
  • generating Kubernetes manifests straight from running containers - handy if Kubernetes is just being planned;
  • replacing Docker in CI/CD, where Podman can run inside the CI runner itself without a privileged daemon.

In practice, Podman is most often chosen for home labs and small servers with elevated security requirements, for edge devices with limited resources, and in places where Kubernetes is already in use or clearly on the roadmap - manifest generation significantly simplifies the migration.

Architecture: why there’s no daemon
#

Podman’s key architectural feature is the daemonless model. Docker has dockerd, a constantly running background process with root privileges that accepts commands from the client and manages the lifecycle of all containers. Podman has no such process at all: the podman run command directly, with no intermediaries, calls conmon and runc/crun, which launch the container as a regular child process of the system.

This has several practical consequences:

  • a Podman container shows up in ps as an independent process, not as something hidden inside a daemon - this noticeably simplifies debugging;
  • if something crashes in one container, it doesn’t affect the others and certainly doesn’t require restarting a shared daemon;
  • there’s no single point of failure: if the daemon goes away, all containers don’t go away at once, as sometimes happens with Docker after a botched dockerd update;
  • systemd handles such processes naturally, without workarounds like running docker run inside ExecStart - more on that in the Quadlet section.

Rootless containers and networking
#

Rootless mode in Podman isn’t a hack bolted onto an existing architecture - it’s largely what the project was built for in the first place. A container can be started by a regular, unprivileged user, and inside the container the process still gets its own “root” - it just isn’t the real root of the host system.

Technically this is implemented through:

  • user namespaces - remapping UID/GID inside the container to unprivileged ranges on the host;
  • subuid/subgid - the ID ranges the administrator allocates to a user for this remapping (/etc/subuid, /etc/subgid);
  • a user-space network stack that doesn’t require privileges on the host.

Worth a separate note on networking here, because it’s changed noticeably over the past couple of years. In the past, rootless networking in Podman almost always meant slirp4netns - a working but fairly slow solution. Starting with Podman 5.x, pasta is being promoted as the modern replacement - it’s noticeably faster, has full IPv6 support, and, particularly nicely, “mirrors” the host’s network configuration directly into the container instead of classic NAT. In current Podman versions, pasta is already the default backend for rootless networking, and support for slirp4netns is gradually being phased out in newer releases. If you have an old config with slirp4netns explicitly specified - now’s a good time to check man podman-network for your version and switch to pasta where possible.

The upshot of this approach: even if a container is compromised, the attacker ends up not in the host’s root environment, but in an isolated namespace of a regular user - the potential damage is fundamentally smaller.

How Podman differs from Docker
#

Comparison by key parameters
#

CriterionDockerPodman
ArchitectureClient-daemon (dockerd)Daemonless
Daemon privilegesrootno daemon
RootlessExists, but requires separate setup and has limitationsFull “out of the box” mode
systemd integrationVia workaroundsNative (Quadlet)
Kubernetes manifestsRequires third-party toolsBuilt-in YAML generation
PodsNot a first-class entityPresent, as in Kubernetes
CLIDocker CLICompatible with Docker CLI

Compatibility with Docker
#

Probably the best news for those switching from Docker - there’s almost nothing to relearn. Podman’s CLI is deliberately made as similar as possible:

alias docker=podman

After such an alias, the vast majority of familiar commands just keep working:

podman run -d -p 8080:80 nginx
podman build -t myapp .
podman pull docker.io/library/redis
podman push myapp registry.example.com/myapp

The Dockerfile format is also supported without any changes - Podman can build images from regular Dockerfiles, additionally offering its own more flexible Containerfile format (essentially the same thing, just a different filename).

Pods in Podman
#

What a pod is
#

A pod is a group of one or more containers that:

  • share a single network namespace, i.e. a common IP address and a common port space;
  • can optionally share an IPC namespace;
  • logically represent a single application that’s convenient to start and stop as a unit.

The concept is entirely borrowed from Kubernetes, and that’s not a coincidence but a deliberate architectural decision - Podman was designed from the start so that the local development model would be as close as possible to how an application would later behave in a cluster. A typical pod is, for example, an application container, a reverse-proxy container in front of it, and a sidecar container for logging or metrics, which together form one logical service.

Example: creating a pod by hand
#

podman pod create --name web-pod -p 8080:80
podman run -d --pod web-pod nginx
podman run -d --pod web-pod busybox sleep infinity

All containers inside web-pod get one shared IP address and see each other via localhost, like processes on the same machine - you can freely address each other via 127.0.0.1:<port> without configuring a separate docker network, as you’d have to do in Docker.

Practical upsides of this approach: networking within the pod becomes trivial, the configuration is closer to the Kubernetes model, and the containers are logically grouped, which is convenient both for monitoring and for a later migration.

Generating Kubernetes manifests
#

A separate strength of Podman is the ability to generate a ready-made Kubernetes YAML directly from a running pod:

podman generate kube web-pod > pod.yaml

This lets you assemble and test an application’s architecture locally on a single machine, then carry the configuration over to a cluster without rewriting it from scratch - in this sense Podman works reasonably well as a “local Kubernetes” for development and testing.

Podman and systemd
#

Podman natively integrates with systemd - and this can be done in two ways.

The first, older way - generating a unit file from an already-created container or pod:

podman generate systemd --name nginx --files --new

The command creates a .service file that can be placed among the systemd units and managed with standard systemctl start/stop/enable. This method works, but requires the container to already exist, and is poorly suited for declaratively describing infrastructure “from scratch.”

The second, modern and recommended way - Quadlet.

What Quadlet is
#

Quadlet is a mechanism built into Podman that lets you describe containers, pods, networks, volumes, and images declaratively, using ordinary systemd unit files of a special format. On startup systemd itself translates these files into full .service units and runs them via Podman - nothing needs to be generated manually.

In total Quadlet supports several file types:

ExtensionWhat it describes
.containera single container
.poda pod (group of containers)
.volumea named volume
.networka network
.imagean image that needs to be pulled in advance
.buildbuilding an image from a Containerfile
.kubedeployment from a Kubernetes YAML (podman kube play)
.artifactan OCI artifact

Files are placed in one of the standard paths, depending on whether a system (root) or user (rootless) service is needed:

/etc/containers/systemd/            # system quadlets, root
~/.config/containers/systemd/       # user, rootless

Example .container file
#

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

[Service]
Restart=always

[Install]
WantedBy=multi-user.target

Once the file is created, it’s enough to reload systemd’s configuration and start the service:

systemctl daemon-reload
systemctl start nginx.container

From there it’s a full systemd service: autostart at boot via [Install], a unified journal via journalctl -u nginx.container, dependency management via standard After=/Requires=. For a homelab where there’s no Kubernetes and none is planned, but systemd is there - this is, in my view, the optimal way to keep container infrastructure.

Podlet: a helper for writing Quadlet files
#

Writing .container files by hand isn’t always convenient, especially when you already have a working docker run command or docker-compose.yml that you don’t want to rewrite from scratch. There’s a separate tool for this - Podlet (not to be confused with Quadlet, they’re different things, even though the names look similar).

Podlet is a standalone utility written in Rust, not part of Podman itself, but used closely alongside it. It can generate Quadlet files three ways: from a podman run command (or docker run - the syntax is almost identical), from an existing compose file, and even from an already-running container, pod, network, or volume via podlet generate.

podlet podman run -d -p 8080:80 nginx

The command above will print a ready-made .container file with all the necessary sections to the console - all that’s left is to save it in the right directory. For compose files, Podlet can either split services into separate .container files or gather them into a single .pod together with the accompanying containers - depending on what’s closer to the application’s original architecture.

In practice Podlet is especially useful specifically during the migration stage from Docker Compose: no need to manually work out the syntax of Quadlet sections - just feed it an existing docker-compose.yml, then fine-tune the generated file to suit your needs.

When it’s worth choosing Podman
#

From my own experience, Podman is especially justified if:

  • security and rootless mode by default matter to you, not as an option that needs to be bolted on separately;
  • the server already uses systemd, and you want to manage containers with the same familiar systemctl commands rather than a separate daemon;
  • Kubernetes is already in use or clearly planned - carrying the configuration over via podman generate kube is more convenient than writing manifests from scratch;
  • you need a daemonless architecture with no single point of failure;
  • you’re building a homelab or self-hosted infrastructure and don’t want everything to hinge on a single privileged process.

Conclusion
#

Podman isn’t “Docker, but free,” and it’s not a clone made just to be a clone. It’s a separate, by now fairly mature implementation of a container engine that solves specific architectural problems in Docker: it removes the root-privileged daemon, makes rootless mode complete rather than optional, and integrates natively into systemd via Quadlet.

For homelab and small servers, moving from Docker to Podman personally required almost no compromises for me - the CLI is compatible, Dockerfile works as-is, and in return I got fewer points of failure and much more transparent systemd integration. If you’ve been eyeing Podman for a while but haven’t decided - the best way to find out if it’s for you is to simply try it on one not-too-critical service.


Useful links:

Podman - This article is part of a series.
Part : This Article

Related