LabHub

Blog

From Docker to Podman — the Complete Guide to Switching to a Daemonless Container Engine

한국어English日本語中文

Introduction — Same Commands, Different Philosophy

Podman's first impression is "a tool where you swap docker for podman." Indeed, alias docker=podman makes most commands just work, and Docker CLI compatibility is an official goal. But beneath that surface the architecture is nearly the opposite — and every difference you meet during migration (config file locations, GPU setup, compose, permission issues) flows from that architectural difference.

This article organizes those differences from the root. Commands here assume Podman 5.x / Docker Engine 27+.

Internals — Daemon vs. fork-exec

Docker is client-server. When you type docker run, the CLI sends a REST request to the dockerd daemon, which delegates to containerd, which delegates to runc. Every container's ultimate parent is the daemon.

Docker:  docker CLI ──REST──▶ dockerd ──▶ containerd ──▶ runc ──▶ container
Podman:  podman CLI ──fork/exec──▶ conmon ──▶ crun(runc) ──▶ container

Podman has no daemon. podman run creates the container directly via fork-exec, like an ordinary process. Each container gets a small monitor process, conmon, managing stdio and exit codes, while actual creation is done by the OCI runtime crun (written in C — lighter and faster than runc). Implications:

Rootless — a Difference of Defaults

Both engines support rootless mode, but the center of gravity differs. Docker is rootful by default with rootless as opt-in; rootless is Podman's default experience (on Fedora/RHEL you use it as an ordinary user right after install).

The mechanism is user namespaces. Root (UID 0) inside the container maps to your own UID outside; other in-container UIDs map to subordinate ranges assigned in /etc/subuid and /etc/subgid. If a container escapes, all it holds is an unprivileged user's rights.

Rootless constraints to know when migrating:

Config Files — What Lives Where

Docker's configuration is daemon-centric (daemon.json); Podman's is library-centric and distributed. System-wide config lives in /etc/containers/, per-user config in ~/.config/containers/ (which takes precedence).

Docker                              Podman
──────────────────────────────      ─────────────────────────────────────────
/etc/docker/daemon.json             /etc/containers/containers.conf   (engine/runtime options)
                                    ~/.config/containers/containers.conf
(registry mirrors also daemon.json) /etc/containers/registries.conf   (registries, mirrors, blocks)
                                    /etc/containers/storage.conf      (storage driver & paths)
                                    /etc/containers/policy.json       (image signature policy)
~/.docker/config.json (auth)        ~/.config/containers/auth.json    (registry auth)

Image/container storage
/var/lib/docker                     rootful:  /var/lib/containers/storage
                                    rootless: ~/.local/share/containers/storage

The two most-touched items during migration: setting unqualified-search registries in registries.conf (Docker resolves nginx to docker.io automatically; Podman by default asks or follows config — add unqualified-search-registries = ["docker.io"] to match Docker), and moving internal mirrors/auth into registries.conf + auth.json.

GPU — CDI Is the Answer

The standard path to NVIDIA GPUs in Podman is CDI (Container Device Interface) — a vendor-neutral spec describing "which device nodes, libraries, and env vars are needed to put this device into a container." It is more transparent and portable than Docker's --gpus runtime-hook approach (Kubernetes' GPU Operator uses CDI under the hood too).

# 1) Install nvidia-container-toolkit, then generate the CDI spec
sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml

# 2) Check the generated device names
nvidia-ctk cdi list
#   nvidia.com/gpu=0, nvidia.com/gpu=all, (with MIG) nvidia.com/gpu=0:0 ...

# 3) Use the GPU in a container
podman run --rm --device nvidia.com/gpu=all \
  nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi

Compose — Can You Keep Your compose.yaml?

Bottom line: mostly yes, and there are two ways.

Option 1 — point the Docker Compose binary at the Podman socket (recommended). Podman can serve a Docker-API-compatible socket. With it enabled, standard docker compose works unchanged with Podman as the backend — no compose.yaml rewrite.

# Enable the user socket (rootless)
systemctl --user enable --now podman.socket

# Point docker compose at the Podman socket
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock

docker compose up -d   # backend is Podman

You're running the official Go implementation of the Compose spec, so compatibility is maximal. The podman compose command itself is a wrapper that calls an external compose provider (docker-compose if installed).

Option 2 — podman-compose. A separate project reimplemented in Python. Its virtue is purity — it invokes podman commands directly with no daemon/socket — but corners of the Compose spec (some network options, profiles, advanced depends_on conditions) can differ subtly from the reference implementation. Fine for simple stacks; for complex compose files, Option 1 is safer.

Compose-related migration notes:

Migration Trap Collection — 30 Minutes If You Know, Half a Day If You Don't

Versions and Communities — Who Builds It, Where It's Discussed

Version sense (as of 2026): Podman is in its 5.x generation — the 4.x → 5.0 transition switched default networking to pasta and matured CDI support. It is deeply integrated into RHEL 9/10, and Fedora has shipped Podman as the default container tool for several releases. Docker Engine is in the 27+ generation and still owns the largest ecosystem and documentation base.

Governance and community:

A practical picking rule: if your team runs RHEL/Fedora, requires rootless security, or prefers systemd-based operations, Podman is the natural fit. If you depend on Docker Desktop tooling or need the ecosystem's documentation mass, Docker remains the safe default. And both stand on the same OCI standard — an image built on either runs on either. Such is the blessing of having standards.

Closing

Moving from Docker to Podman is not a command swap but an operating-model swap — daemon to fork-exec, rootful to rootless, daemon.json to the containers.conf family, --gpus to CDI, compose-daemon dependence to socket compatibility or systemd/Quadlet. Thankfully, the OCI standard carries your artifacts — images and compose.yaml — across nearly untouched, and the traps converge to the few above (SELinux labels, unqualified images, storage paths). To drill container fundamentals hands-on, try this site's container lab and Linux terminal.

References

Comments

No comments yet.

Sign in to leave a comment