Skip to content

Docker & Podman

Almost every job posting, tutorial, and Stack Overflow answer says docker, not podman. This course teaches Podman deliberately (daemonless, rootless by default), but you'll meet Docker everywhere else — in interviews, in other teams' repos, in the wider ecosystem. The good news: the two are close to interchangeable at the command line. This page gives you the mapping, so Docker-flavored instructions translate instantly, and the one architectural difference that actually changes behavior.

TL;DR — in 30 seconds:

  • Docker runs a root-owned background daemon (dockerd) that owns every container; the docker CLI talks to it over a socket. Podman has no daemon — each podman command talks straight to the kernel, and it runs rootless by default.
  • Commands are ~1:1: docker runpodman run, docker buildpodman build, docker pspodman ps — most muscle memory transfers directly, including a Dockerfile (Podman calls the same syntax a Containerfile, and reads either name).
  • The daemon is where the difference bites: a compromised Docker daemon socket is effectively root on the host; Podman has no such single point of privilege to compromise.

1. Same commands, different architecture

flowchart LR
    subgraph Docker
        DCLI["docker CLI"] --> DD["dockerd (daemon, root)"]
        DD --> DC1["Container"]
        DD --> DC2["Container"]
    end
    subgraph Podman
        PCLI1["podman CLI"] --> PC1["Container"]
        PCLI2["podman CLI"] --> PC2["Container"]
    end

Docker's docker CLI is a thin client: it sends every command over a socket to dockerd, a long-running root daemon that actually creates and manages containers. Podman's CLI is the whole client — each invocation talks to the kernel directly (namespaces, cgroups) and exits when the command finishes; there's no persistent process holding the keys. This is the daemonless half of Podman's model covered on the main course page, and it's the one structural difference underneath everything else on this page.

2. The command mapping

The CLIs were designed to be compatible — Podman's stated goal is that alias docker=podman works for the overwhelming majority of everyday use:

Task Docker Podman
Run a container docker run -d --name N image podman run -d --name N image
Build an image docker build -t name:tag . podman build -t name:tag .
List running containers docker ps podman ps
View logs docker logs -f N podman logs -f N
Shell inside docker exec -it N /bin/sh podman exec -it N /bin/sh
Stop / remove docker stop N / docker rm N podman stop N / podman rm N
List images docker images podman images
Compose-style multi-container docker compose up podman compose up (or podman play kube)
Build file Dockerfile Containerfile (Podman reads either name)

The flags you already know from the cheat sheet (-p, -v, -e, --rm, --cap-drop, --user) carry over unchanged — they're the same underlying kernel primitives (namespaces, cgroups, bind mounts) exposed through a near-identical CLI surface.

3. Where the difference actually bites

Most days, the daemon is invisible. It shows up in a few concrete spots:

  • The socket is a privilege boundary. Anything with access to /var/run/docker.sock can, in effect, run as root on the host — mounting docker.sock into a container (a common CI/tooling pattern) hands that container root-equivalent control of the whole machine. Podman has no equivalent always-on socket to protect by default.
  • Rootless is opt-in on Docker, default on Podman. Docker has a rootless mode, but it's a separate install path most teams don't use; Podman ships rootless as the default you have to opt out of. If a Docker tutorial says "just run as root," that instinct doesn't carry over — see the rootless & security page for why Podman starts from the safer default instead.
  • A survives-reboot daemon vs. no daemon to manage. Docker containers can be configured to restart when dockerd restarts (e.g. after a host reboot) because the daemon persists and tracks them; Podman containers are supervised per-process instead — see the Quadlet module for how Podman does the equivalent job with systemd, without ever introducing a daemon.

4. When you'll actually see Docker

You don't need to install Docker to use this course — Podman covers the same ground. But you'll run into docker in the wild:

  • Job postings and interviews default to saying "Docker" as the generic word for containers, even at shops that don't run it — the mapping above is what lets you answer fluently either way.
  • Other teams' Dockerfiles and docker-compose.yml files — a Containerfile and a Dockerfile are the same syntax; Podman builds one unmodified.
  • Docker Hub remains the largest public image registry regardless of which CLI pulls from it.

5. How this connects

  • The daemonless/rootless model this page names is the same one the main course page and the rootless & security page cover in depth — this page is the "translate from what you'll see out there" companion to those, not a new architecture.
  • The Containerfile syntax here is identical to the one covered on the images & builds page — nothing about layers, multi-stage builds, or the build cache changes based on which CLI reads the file.
  • The docker.sock privilege-boundary point is the same "don't hand out root" concern that motivates Kubernetes' RBAC and Pod Security Standards (module 04, Kubernetes & Helm) — a daemon socket and a cluster API server are both a single point of privilege worth guarding.

Common gotchas

  • Assuming docker compose and podman compose are identical. Fix: podman compose shells out to an external Compose implementation and mostly works, but Podman's native equivalent for multi-container YAML is podman play kube — check the cheat sheet for both.
  • Mounting docker.sock into a container out of habit. Fix: that pattern hands the container root-equivalent host access; if you're on Podman for its rootless security model, don't reintroduce the same risk through a borrowed Docker pattern.
  • Expecting a dockerd-equivalent process to inspect. Fix: there is no Podman daemon to ps for or restart — if a container needs to persist across reboots, that's a systemd/Quadlet job, not a daemon-restart job.
Check yourself — why can mounting /var/run/docker.sock into a container be dangerous in a way that has no direct Podman equivalent?

Anything with access to that socket can issue commands to the root-owned dockerd daemon, which is effectively root access to the whole host. Podman has no persistent root daemon or socket playing that role, so there's no single mount point that hands out equivalent host-wide privilege.

Check yourself — a coworker pastes a docker run -p 8080:80 -v data:/var/lib/app image command. What do you run instead on Podman?

The identical command with podman substituted for dockerpodman run -p 8080:80 -v data:/var/lib/app image. The flags and syntax are unchanged.

You can defend this when you can translate any everyday Docker command to Podman on sight, explain why the daemon is the one thing that's architecturally different (not just a naming difference), and name a concrete risk (the docker.sock mount) that follows from that difference.