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:
- Fixing ‘No Space Left on Device’ from Docker Image and Container Buildup
- Solomon Hykes Demos Docker Publicly for the First Time
Sources: