Skip to content
SRE & DevOpsDeep Dive Published Updated 5 min readViews unavailable

Docker vs. Podman: Rootless Containers and the Daemon-less Architecture

How Podman's daemon-less, fork-exec architecture differs from Docker's client-daemon model, and what that means for rootless containers in production.

Docker popularized containers, but its architecture — a long-running privileged daemon (dockerd) that every docker command talks to over a socket — carries security and operational trade-offs that Podman was built specifically to address. Understanding the actual architectural difference, not just the CLI compatibility, is what determines which one fits a given deployment.

Docker’s architecture: a client talking to a daemon

Every docker command is a thin client sending requests over a Unix socket (/var/run/docker.sock) to dockerd, a long-running background daemon, typically running as root, which itself does the actual work of creating namespaces, cgroups, and managing container lifecycles.

docker run -d --name web nginx
docker ps

That daemon is a single point of failure (if it crashes or is restarted, in older configurations all containers could go down with it) and a significant privilege concentration — anyone who can talk to that socket effectively has root-equivalent access to the host, since the daemon itself runs as root and will do whatever a client asks of it.

Podman’s architecture: no daemon at all

Podman’s podman run directly forks and execs the container process itself — there’s no persistent background daemon brokering the request. Each podman invocation is a self-contained operation, using the same underlying kernel primitives (namespaces, cgroups) but without a privileged intermediary process sitting between the command and the container.

podman run -d --name web nginx
podman ps

This is why Podman containers, once started, are just ordinary child processes of whatever started them (or of systemd, if managed as a service) — there’s no separate daemon process to restart, monitor, or worry about as a shared failure domain across every container on the host.

Rootless containers: the security payoff

Podman was designed from the outset to run rootless — as a completely unprivileged user, using user namespaces to map a container’s internal root (UID 0) to an unprivileged UID on the host, exactly the user-namespace mechanism covered in the Linux namespaces internals.

podman run --rm -it alpine id
# uid=0(root) gid=0(root) — inside the container
# but mapped to an unprivileged host UID outside it

Docker has since added rootless mode too, but it remains a secondary configuration bolted onto an architecture originally built around a root daemon — Podman’s rootless-by-default design reflects that this was the starting assumption, not a later retrofit.

systemd integration: prefer Quadlet for new deployments

Podman integrates with systemd as regular supervised services. Older workflows used podman generate systemd, but current Podman documentation marks that command as deprecated and recommends Quadlet for declarative systemd-managed containers. Existing generated units can still explain an installed system, while new deployments should use supported Quadlet unit files and verify them with the host’s Podman version:

# ~/.config/containers/systemd/web.container
[Container]
Image=docker.io/library/nginx@sha256:<approved-digest>
PublishPort=8080:80

This gives containers the exact same restart policies, dependency ordering, and logging integration (journalctl -u web-container) as any other systemd-managed service — no separate container-specific supervision layer required.

Pods: Podman’s Kubernetes-shaped primitive

Podman natively supports pods — groups of containers sharing a network namespace, directly mirroring Kubernetes’ own pod concept, which makes local development against a pod-shaped mental model more direct than Docker’s flatter container/network model:

podman pod create --name mypod -p 8080:80
podman run -d --pod mypod --name web nginx
podman run -d --pod mypod --name sidecar my-logging-agent

CLI compatibility: podman as a drop-in

Podman’s CLI deliberately mirrors Docker’s closely enough that alias docker=podman works for a large share of everyday usage, and Podman also ships a Docker-API-compatible socket mode for tools that specifically expect to talk to /var/run/docker.sock:

systemctl --user enable --now podman.socket
export DOCKER_HOST=unix:///run/user/$UID/podman/podman.sock

Choosing between them

For a single-host development environment or CI runner where rootless operation and reduced attack surface matter, Podman’s daemon-less design is a direct security win with essentially the same day-to-day CLI experience. Docker’s daemon-based model still has real advantages in mature, already-Docker-centric orchestration tooling, extensive documentation, and Docker Desktop’s polished cross-platform developer experience — the choice increasingly comes down to whether an organization already has deep Docker-specific tooling investment (favoring Docker) or is optimizing specifically for rootless security posture and systemd-native service integration (favoring Podman), rather than either being categorically better across the board.

Rootless is not permissionless

Rootless operation maps container IDs through a user namespace and removes a root-owned daemon from the normal path, but it does not make untrusted images safe. The user can still expose that user’s files, credentials, sockets, and network access through mounts and configuration. Review volume paths, capabilities, devices, seccomp, SELinux or AppArmor labels, and published ports. Never mount a Docker or Podman API socket into an untrusted container merely for convenience.

Rootless networking and low-numbered ports have host-specific behavior. Storage also depends on user namespace mappings and the selected graph driver. Before standardizing, test bind mounts, supplementary groups, NFS or other remote filesystems, cgroup delegation, and service startup after reboot on the actual distribution. Avoid “fixing” permission problems with recursive world-writable modes.

API compatibility has limits

Podman’s Docker-compatible CLI and socket cover many common tools, not every Docker Engine behavior or extension. Compose implementations, build features, networking, secrets, health checks, and Desktop integrations can differ. Run the real build, test, and operational workflow before replacing a daemon endpoint with podman.socket. An alias is a convenience for a human, not a compatibility certification.

Both engines ultimately execute OCI images, but build output can still vary by builder, frontend, cache, and platform. Pin base images, record final digests, and test multi-architecture manifests. Docker and Podman security depend on current engine, runtime, kernel, and helper versions, so keep the complete host stack patched within vendor support.

Lifecycle and evidence

For Docker, record daemon configuration, socket authorization, rootless or rootful mode, storage driver, runtime, logging driver, and restart policy. For Podman, record user or system context, containers/storage configuration, network backend, Quadlet files, and lingering/user-service behavior. Back up durable volumes separately; exporting an image does not preserve mutable application data.

Diagnose from the owning service manager downward. Check systemd unit state, engine events and logs, container exit status, OCI runtime errors, storage capacity, networking, and kernel denials. A daemonless architecture removes one persistent component but does not remove state reconciliation, resource exhaustion, or the need for a clean shutdown contract.

Related:

Sources:

Comments