TL;DR
- Architecture: Docker runs a long‑lived daemon (
dockerd); Podman is daemonless—the CLI spawns containers directly (via libpod). - Rootless by default: Podman emphasizes rootless containers (user namespaces, less blast radius). Docker supports rootless too, but it’s not the classic default.
- Compatibility: Both are OCI compatible (images/registries). Podman’s CLI is largely Docker‑compatible; you can use a Docker‑compatible socket or a
dockershim for many workflows. - Disk & speed: Builds and pulls are comparable. Podman builds via Buildah under
podman build; Docker uses BuildKit. Differences tend to be workflow‑dependent, not dramatic. - Ecosystem: Docker has first‑class Compose; Podman offers
podman compose/podman‑compose, a Docker‑compatible socket fordocker compose, and native pods + k8s YAML (podman play kube). - Migration: Keep your Dockerfiles and images. Swap
docker→podman, enable the Docker socket for existing tools, and replace Compose or point Compose at Podman’s socket.
What actually differs (day‑to‑day)
1) Architecture & security model
- Docker:
dockerdmanages containers; CLI talks to the daemon (root by default). - Podman: no central daemon; each command interacts directly. Strong rootless story (user namespaces, seccomp, SELinux/AppArmor).
- Systemd‑friendly: Podman can generate units with
podman generate systemdto supervise containers without a daemon.
2) Images, registries, and builds
- OCI all the way: Docker images work on Podman and vice‑versa. Use
podman login,podman pull/push,podman tag. - Builds:
podman build(Buildah) vsdocker build(BuildKit). Multi‑stage Dockerfiles work on both. Minor differences can appear with build secrets and experimental features—test your Dockerfile.
3) Networking
- Docker: own networking stack (bridge networks,
docker network). - Podman: modern releases use netavark + aardvark‑dns (rootful) and slirp4netns for rootless. Port <1024 needs root privileges.
- Pods: Podman can group containers into pods (shared network namespace) similar to Kubernetes Pods.
4) Volumes & mounts
- Bind mounts and named volumes exist in both. With SELinux, Podman often needs
:Z/:zon bind mounts to relabel and avoidpermission denied. - Volume plugin ecosystems are broader with Docker, but common cases work on both.
5) Compose & orchestration
- Docker:
docker composeis the standard. - Podman options:
podman compose up(orpodman‑compose) to run docker‑compose files.- Run Docker Compose unchanged by exposing a Docker‑compatible socket from Podman and pointing Compose at it.
- Skip Compose entirely: convert to Kubernetes YAML with
podman kube generateand run withpodman play kube.
6) Desktop / non‑Linux hosts
- On Linux, Podman can be truly daemonless/rootless.
- On macOS/Windows, both Docker Desktop and Podman use a VM under the hood (
podman machine). The UX differs, but core concepts remain.
Migration paths (kept short & practical)
Path A — Drop‑in CLI replace (many cases)
- Install Podman and try your workflows by replacing commands:
docker run …→podman run …,docker build …→podman build …. - Add SELinux flags on bind mounts if needed:
-v ./data:/data:Z. - Generate systemd units if you previously relied on the Docker daemon for keep‑alive:
podman generate systemd --new --name my-api > ~/.config/systemd/user/my-api.service systemctl --user enable --now my-api
Path B — Keep Docker‑native tools, point them at Podman
- Start Podman’s Docker‑compatible socket (rootless example):
systemctl --user enable --now podman.socket export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock - Run
docker compose upor any tool that talks to the Docker socket—no code changes.
Path C — Move from Compose to Pods/Kubernetes
- Convert:
podman kube generate <container-or-pod>→ edit YAML →podman play kube file.yaml. - Great stepping stone if you plan to move to Kubernetes later.
Command equivalents (cheat sheet)
# Images
docker pull nginx ↔ podman pull nginx
docker images ↔ podman images
docker rmi <id> ↔ podman rmi <id>
# Containers
docker run -d -p 8080:80 nginx ↔ podman run -d -p 8080:80 nginx
docker ps ↔ podman ps
docker logs -f <ctr> ↔ podman logs -f <ctr>
docker exec -it <ctr> sh ↔ podman exec -it <ctr> sh
docker stop/rm <ctr> ↔ podman stop/rm <ctr>
# Build
docker build -t app . ↔ podman build -t app .
# Compose
docker compose up -d ↔ podman compose up -d
# or point docker compose at Podman’s socket (Path B)
Pitfalls & fixes
| Problem | Why it happens | Fix |
|---|---|---|
| permission denied on bind mounts (SELinux) | Labels don’t allow container access | Add :Z or :z to the mount, or adjust SELinux policy |
| Rootless can’t bind low ports | Ports <1024 need elevated caps | Use -p 8080:80, or run rootful/systemd socket activation |
| Compose feature mismatch | Some Compose extensions differ | Use the Docker‑compatible socket or adjust to podman compose features |
| Daemon‑assumption in scripts | Scripts expect long‑lived daemon | Generate systemd units; use podman auto-update as needed |
| Network differences | Different backends & defaults | Recreate networks; test service discovery and DNS first |
Quick checklist
- [ ] Confirm OCI images build/run under Podman (
podman run,podman build). - [ ] Decide on Compose strategy:
podman compose, Docker Compose via Podman socket, orpodman play kube. - [ ] If using bind mounts on SELinux hosts, add
:Z/:z. - [ ] For services, generate systemd units; no daemon needed.
- [ ] On macOS/Windows, set up
podman machine(VM) and test port forwarding. - [ ] Document rootless vs rootful expectations (ports, cgroups).
One‑minute adoption plan
- Try
podman run/podman buildon your existing Dockerfiles. - Enable the Docker‑compatible socket and run your current
docker composeflows against it. - Fix mounts with SELinux flags, confirm networking, and add healthchecks.
- Generate systemd units for always‑on services.
- (Optional) Convert long‑lived stacks to pods or Kubernetes YAML for future portability.