caduh

Docker vs Podman — what’s different and how to migrate

4 min read

As Podman’s daemonless, rootless approach gains traction, here’s a practical comparison with Docker—architecture, security, node networking/volumes, speed, and real migration paths.

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 docker shim 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 for docker compose, and native pods + k8s YAML (podman play kube).
  • Migration: Keep your Dockerfiles and images. Swap dockerpodman, 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: dockerd manages 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 systemd to 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) vs docker 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/:z on bind mounts to relabel and avoid permission denied.
  • Volume plugin ecosystems are broader with Docker, but common cases work on both.

5) Compose & orchestration

  • Docker: docker compose is the standard.
  • Podman options:
    • podman compose up (or podman‑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 generate and run with podman 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)

  1. Install Podman and try your workflows by replacing commands: docker run …podman run …, docker build …podman build ….
  2. Add SELinux flags on bind mounts if needed: -v ./data:/data:Z.
  3. 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

  1. Start Podman’s Docker‑compatible socket (rootless example):
    systemctl --user enable --now podman.socket
    export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock
    
  2. Run docker compose up or 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, or podman 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

  1. Try podman run/podman build on your existing Dockerfiles.
  2. Enable the Docker‑compatible socket and run your current docker compose flows against it.
  3. Fix mounts with SELinux flags, confirm networking, and add healthchecks.
  4. Generate systemd units for always‑on services.
  5. (Optional) Convert long‑lived stacks to pods or Kubernetes YAML for future portability.