Comparison

Docker vs Podman

The phrase "drop-in replacement" follows Podman around because its command line matches Docker's almost verb for verb. The host does not match: who runs as root, who restarts containers after a reboot, what listens on the socket, and what a laptop has to run all change. What follows is the list of those operational requirements and, for each one, where the alias stops working.

In short — Alias docker=podman for interactive work and image builds, and stop there: anything that mounts docker.sock, depends on --restart, or uses Swarm needs a real migration. Docker is a long-running daemon (dockerd) that owns every container, its restart policy and the API socket, and needs root unless you set up rootless mode. Podman has no daemon: each command forks the container under a conmon monitor and exits, rootless is the ordinary mode, and systemd owns lifecycle through Quadlet units.
Docker vs Podman: summary by dimension
DimensionDockerPodman
Process modeldockerd daemon plus containerd; the CLI talks to it over a socketNo daemon; podman forks each container with conmon and an OCI runtime (crun or runc), then exits
Latest release (Sep 2026)Docker Engine 29.8.1Podman 6.1.2; Podman Desktop 1.29.3
Root by defaultDaemon requires root unless you opt in to rootless modeSame binary runs rootful or rootless; rootless is the documented default path
Rootless prerequisitesnewuidmap/newgidmap, 65,536 subordinate IDs, dockerd-rootless-setuptool.sh, a user systemd sessionnewuidmap/newgidmap, 65,536 subordinate IDs, pasta (passt) for networking
Restart across rebootsDaemon-owned --restart policies; docs warn not to combine them with a process managersystemd units generated from Quadlet .container files; podman generate systemd is deprecated
API socket/var/run/docker.sock (rootful) or $XDG_RUNTIME_DIR/docker.sock (rootless), always listeningpodman.socket, socket-activated; serves a Docker-compatible API (v1.40 layer) plus the libpod API
Composedocker compose, a Go CLI plugin (Compose v2/v5)podman compose is a wrapper that runs docker-compose or podman-compose against the Podman socket
Pods and Kubernetes YAMLNo pod concept; multi-container apps are Compose projectsNative pods; podman kube play and kube generate for Pod, Deployment, DaemonSet, Job, ConfigMap, Secret, PVC
macOS and WindowsDocker Desktop; free only for personal use, education, non-commercial OSS, and businesses under 250 employees and $10M revenuepodman machine VM (libkrun default on macOS, WSL on Windows); Podman Desktop under Apache-2.0
Rootless limits (documented)No AppArmor, checkpoint, overlay network or SCTP ports; cgroup limits need cgroup v2 + systemdNo checkpoint/restore (CRIU needs root); no resource limits on cgroup v1; images with UIDs beyond the mapped range fail
Published Jun 1, 2025·Updated ·By SecOpsLog

What "drop-in" covers, and where it stops

The claim comes from Podman's own introduction: it "provides a command line interface (CLI) familiar to anyone who has used the Docker Container Engine" and "most users can simply alias Docker to Podman (alias docker=podman) without any problems". Read that as a statement about the command surface. Images are OCI images either way, a Containerfile is a Dockerfile, registries and login flows are the same, and run, build, pull, push, exec, logs and ps behave the way your fingers expect.

Below the CLI the two engines part ways, and that is what a migration has to check. The split below is the honest version of the alias; the rest of this page is the evidence for each line.

alias docker=podman: what carries over and what does not
Changes or breaks
Anything that mounts /var/run/docker.sock (agents, CI runners, monitoring sidecars)
--restart policies as the way containers survive reboots
Docker Swarm, docker service, docker stack
Tooling that reads /var/lib/docker paths or daemon.json
Compose features that need Docker-only daemon behaviour
Docker Desktop settings, extensions and licence assumptions
Carries over
OCI images, tags, digests, registries and credentials
Dockerfile / Containerfile syntax for builds
run, build, pull, push, exec, logs, ps, inspect and their common flags
Volumes and bind mounts as concepts (paths and SELinux labels differ)
compose.yaml files, through an external Compose provider
Kubernetes as the eventual target; Podman adds pods and kube play

Process model and what runs as root

Docker's security page states the model plainly: "running containers (and applications) with Docker implies running the Docker daemon. This daemon requires root privileges unless you opt-in to Rootless mode", and "only trusted users should be allowed to control your Docker daemon" because the daemon will happily bind-mount the host's / into a container. The CLI talks to that daemon over a Unix socket, and access to the socket is access to the daemon. Rootless mode "executes the Docker daemon and containers inside a user namespace", so both run without root, using no setuid binaries "except newuidmap and newgidmap". The hands-on setup of both rootless modes, and what a container escape can still reach, is worked through in the Rootless Docker and Podman lesson.

Podman has no daemon to trust. It "relies on an OCI compliant Container Runtime (runc, crun, runv, etc)" and its containers "can either be run by root or by a non-privileged user". Each podman command sets up the container, hands it to conmon to keep it alive and collect its exit status, and returns. There is no privileged process holding every container, which is the property people are buying, and there is no process to ask for state either, which is the property they sometimes miss: podman ps reads the container store, it does not query a service.

the two engines describing themselves (trimmed from the documented example outputs)
docker info
Client: Docker Engine - Community
Context: rootless
Server:
Security Options:
seccomp
Profile: builtin
rootless
cgroupns
podman info
host:
networkBackend: netavark
ociRuntime:
name: crun
remoteSocket:
path: /run/user/1000/podman/podman.sock
security:
rootless: true
store:
graphDriverName: overlay
graphRoot: /home/alice/.local/share/containers/storage

Those two excerpts already contain most of the migration checklist. Docker tells you which context you are in and lists rootless under Security Options; Podman tells you the runtime, the network backend, where the socket would be if you started one, and that the image store lives under the user's home. If a script assumes /var/lib/docker or /var/run/docker.sock, it will read those lines and fail.

Rootless on real servers: prerequisites and documented limits

The prerequisites are nearly identical, which is unsurprising since both use the same kernel features. Docker requires newuidmap and newgidmap and says /etc/subuid and /etc/subgid "should contain at least 65,536 subordinate UIDs/GIDs for the user"; Podman needs the same files, and its rootless notes state that "a standard rootless configuration only gives containers access to 65536 UIDs and GIDs". Docker's setup is a script, dockerd-rootless-setuptool.sh install, that creates a user-level docker.service and a "rootless" CLI context. Podman needs no install step beyond the packages: run it as the user and it is rootless.

The limits are where the two diverge, and both projects document them. Rootless Docker supports only overlay2 (kernel 5.11 or later), fuse-overlayfs, btrfs and vfs as storage drivers; cgroup resource limits work "only when running with cgroup v2 and systemd"; AppArmor, checkpoint, overlay networks and SCTP ports are "not supported"; privileged ports below 1024 need extra host configuration; and networking runs through a user-mode TCP/IP stack that "is generally slower than the one in kernel mode". Rootless Podman uses native overlayfs on kernel 5.12 or later and falls back to fuse-overlayfs; checkpoint and restore do not work because "CRIU requires root"; resource limits are unavailable on cgroups v1; ports below 1024 need the same net.ipv4.ip_unprivileged_port_start sysctl; and images that need UIDs beyond the 65,536 mapped entries fail to build or run.

Networking is where the defaults differ in kind. "As of Podman 5.0, pasta is the default networking tool" for rootless Podman. Rootless Docker uses slirp4netns when it is installed and gvisor-tap-vsock otherwise (pasta is documented as experimental and VPNKit as legacy), and since Engine 29.5 --net=host is no longer namespaced inside RootlessKit. Either way, rootless container networking is a user-space stack, and throughput-sensitive services should be measured before you commit a fleet to it.

Documented fact versus inference
Every limitation above is quoted or paraphrased from the two projects' own pages. The inference this page adds is that the lists are converging: the hard blockers (checkpoint, cgroup v1 limits, sub-1024 ports, UID range size) are kernel constraints that neither engine can remove, so choosing rootless is a host decision before it is an engine decision.

Boot, restart and the socket: who supervises your containers

Docker supervises. "Docker provides restart policies to control whether your containers start automatically when they exit, or when Docker restarts", and the docs recommend them over process managers with a direct warning: "Don't combine Docker restart policies with host-level process managers, as this creates conflicts." The daemon is the supervisor, which also means that when the daemon stops, so do the containers, unless live-restore is enabled in daemon.json; and live restore "is only supported when installing patch releases", not across major daemon upgrades.

Podman delegates to systemd. Quadlet is "a systemd generator": you drop a .container file into /etc/containers/systemd/ (rootful) or ~/.config/containers/systemd/ (rootless), systemd reads it at boot and on daemon-reload, and generates a normal .service unit you manage with systemctl. The older podman generate systemd is "deprecated" with "no plans to remove the command", receiving urgent fixes only. Restart=always, dependencies, timers and journald logging are systemd's, not Podman's.

one web service: restart policy versus Quadlet unit
# Docker: the daemon owns the restart
docker run -d --name web --restart unless-stopped \
-p 8080:80 -v web-data:/data registry.example.com/shop/web:2.4.1
# Podman: ~/.config/containers/systemd/web.container (rootless) or
# /etc/containers/systemd/web.container (rootful)
[Unit]
Description=shop web
After=network-online.target
[Container]
Image=registry.example.com/shop/web:2.4.1
PublishPort=8080:80
Volume=web-data:/data
AutoUpdate=registry
[Service]
Restart=always
TimeoutStartSec=900
[Install]
WantedBy=default.target
# then: systemctl --user daemon-reload && systemctl --user start web
# Docker-API clients: systemctl --user start podman.socket
# export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock

The socket follows the same split. Docker's socket is always there because the daemon is always there. Podman's API "supports systemd socket activation": podman.socket listens, podman system service starts on the first connection and, "after some time of inactivity, as defined by the --time option, the command terminates"; the default is 5 seconds. The service exposes "a compatibility layer offering support for the Docker v1.40 API, and a Podman-native Libpod layer", so a Docker client or docker-compose can be pointed at it by setting DOCKER_HOST to the Podman socket path (the exact value is in the listing above). Compatibility is with that API version; a tool that needs a newer Engine API feature will fail against the compatibility layer. Our reading of the documented behaviour is that this API gap, not the CLI, is where a "drop-in" migration is most likely to stall.

Compose, pods and the Kubernetes on-ramp

Docker Compose is a first-party CLI plugin: "Compose v2, announced in 2020, is written in Go and is invoked with docker compose", and "Compose v5, released in 2025, is functionally identical to Compose v2" with a Go SDK added. Podman's compose is a shim by design: podman compose "is a thin wrapper around an external compose provider such as docker-compose or podman-compose", and "if installed, docker-compose takes precedence since it is the original implementation of the Compose specification". The wrapper "will emit a warning saying that it executes an external command" unless you silence it. So Compose on Podman is the Docker Compose binary talking to the Podman socket, with the API compatibility caveat from the previous section.

What Podman has instead is Kubernetes shapes. Pods are native, and podman kube play "reads in a structured file of Kubernetes YAML" and recreates it, supporting Pod, Deployment, PersistentVolumeClaim, ConfigMap, Secret, DaemonSet and Job kinds and five volume types. podman kube generate goes the other way, producing YAML from running containers or pods. For a team whose destination is Kubernetes, that is a shorter path than Compose; for a team whose destination is a single host, Compose remains the better-documented tool and Podman runs it at one remove.

Laptops: Desktop licensing and podman machine

Neither engine runs Linux containers natively on macOS or Windows; both need a VM. Docker's VM is Docker Desktop, licensed under the Docker Subscription Service Agreement: free for "small businesses (fewer than 250 employees AND less than $10 million in annual revenue)", personal use, education and non-commercial open source, and requiring a paid subscription for "professional use in larger organizations" and government entities. The docs note that "the licensing and distribution terms for Docker and Moby open-source projects, such as Docker Engine, aren't changing"; the licence is about Desktop, not the engine on your servers.

Podman's VM is podman machine: "Podman on MacOS and Windows requires a virtual machine", managed by podman machine init and start, with libkrun the default provider on macOS and WSL the default on Windows; "all podman machine commands are rootless only". Podman Desktop is a separate graphical application under Apache-2.0. The trade is a licence conversation against a VM you administer yourself, and for a company above the Desktop thresholds that conversation is often the real reason this comparison is being read.

What to run where

Which engine, by situation

SituationRunWhyWhat to plan for
Long-lived services on Linux hosts you administer with systemd, no orchestratorPodman with QuadletUnits, restarts, dependencies and logs are systemd's; no privileged daemon on the hostRewrite --restart into [Service] Restart=; put units under /etc/containers/systemd; AutoUpdate needs fully qualified image names
CI runners, agents or platforms that mount docker.sock and expect the Engine APIDockerThe socket and the current Engine API are the contract those tools were written againstTreat socket access as root; rootless Docker moves the socket to $XDG_RUNTIME_DIR and breaks hard-coded paths
Developer laptops at a company above the Desktop free tierPodman (podman machine or Podman Desktop)No per-seat subscription; same images, same ContainerfileA VM per laptop to keep patched; podman compose runs docker-compose against the socket, so test the compose files you actually use
Multi-tenant build or shared hosts where users must not get root through containersPodman rootlessRootless is the ordinary mode; nothing setuid beyond newuidmap/newgidmap; no daemon to hand out rootProvision subuid/subgid ranges for every user; sub-1024 ports and cgroup v1 limits are off the table
Compose-first applications you deploy as a unit and manage with docker compose commandsDockerFirst-party Compose v5 plugin with the full Compose Specification and the Engine behind itThe daemon runs as root unless you also do the rootless setup, with its storage and networking limits
Workloads headed for Kubernetes, prototyped on a single hostPodmanNative pods; kube play and kube generate move YAML in both directionsOnly a subset of kinds and volume types is supported; treat kube play as a dev loop, not a cluster

Trade-offs, with their prices

A root daemon buys you one supervisor that owns restarts, live-restore, the socket and an API every tool already speaks. The price is a root process whose socket is equivalent to root, so socket access has to be handed out like a privilege and audited like one. That lands hardest on anyone running shared CI and on anyone whose monitoring or agents mount docker.sock.

No daemon buys you the opposite: no privileged long-running process, rootless without a setup script, and no engine process whose restart takes the containers with it. In exchange, supervision moves to systemd, the API is opt-in and socket-activated with a five-second idle timeout by default, and the compatibility layer targets Docker API v1.40. Every docker.sock consumer therefore becomes a migration item, which is a platform team's problem before it is anyone else's.

Rootless, on either engine, means a container escape lands in an unprivileged user namespace. It also means storage driver restrictions, user-mode networking, no checkpoint, no sub-1024 ports without a sysctl, and cgroup limits only on v2 with systemd. The host has to be prepared first (subuid ranges, kernel version, cgroup v2), so whoever owns base images and host builds is deciding this before the container team is.

On Compose, Docker owns the product and Podman runs the same compose files through the same docker-compose binary without the daemon, at the cost of one more layer that emits a warning and depends on API compatibility. Teams whose deployment unit is a compose project are the ones who will feel the difference.

On laptops, Docker Desktop is a supported product with settings, updates and extensions, paid for by subscription above 250 employees or $10M in revenue; podman machine removes the licence conversation and hands you a VM to operate. That trade concerns engineering managers and procurement more than it concerns engineers.

Before you alias docker=podman

Five checks, in the order they usually bite
1. Inventory every mount of /var/run/docker.sock and every DOCKER_HOST; each one needs the Podman socket and an API-version check. 2. List every container started with --restart; each becomes a Quadlet unit. 3. Confirm subuid/subgid ranges, kernel version and cgroup v2 on every host that will run rootless. 4. Run your compose files through podman compose with the provider you will ship and diff the behaviour. 5. Decide who owns the VM on laptops, and whether Desktop licensing is the actual reason you are here.

Where we land: on servers we administer, Podman under systemd, rootless wherever the host can support it, with the socket enabled only on hosts that need the API. Where a tool chain is built around the Docker daemon and its API, Docker, run rootless where its documented limits allow and with socket access treated as root either way. The alias is fine for people; it is the automation that needs the audit.

Found a technical issue on this page? Report it with the tool version you used and the behavior you saw. How resources are maintained.

Go deeper
Hands-on courses for Docker vs Podman