Podman, containerd and the OCI ecosystem
The specs underneath the tooling, where the runtime boundary really sits, and when Podman or nerdctl is the better answer.
Three specifications, many tools
Docker popularised containers but does not define them. Three Open Container Initiative specifications do: the image format, the runtime behaviour, and the registry API. Anything that implements them can build, run and distribute the same artifacts.
| Spec | Defines | Implemented by |
|---|---|---|
| OCI Image | Layers, config, manifest list | Every builder and registry |
| OCI Runtime | How a container process is created | runc, crun, youki |
| OCI Distribution | Pull, push and tag over HTTP | Registries and clients |
| CRI | The Kubernetes runtime interface | containerd, CRI-O |
| Tool | Role |
|---|---|
dockerd | Daemon: builds, manages networks and volumes, talks to containerd |
containerd | Supervises containers; what Kubernetes actually talks to |
runc / crun | Creates the namespaces and execs the process |
| Podman | Daemonless, Docker-compatible CLI with pods and rootless first |
| nerdctl | A Docker-like CLI for containerd namespaces |
| Buildah / Kaniko / BuildKit | Image builders with no privileged daemon |
💡
The layers explain the failure modes: when
docker build hangs, it is usually the builder; when docker run fails to start, containerd or runc; when a pull fails, the distribution spec and the registry. Knowing where the boundary sits halves the diagnosis time.Podman and rootless containers
podman run --rm alpine id # no daemon, no socket
podman build -t app:1.4.2 .
# keep the host UID inside a rootless container
podman run --rm -v "$PWD:/work:Z" --userns keep-id -w /work node:20 npm test
podman pod create --name web -p 8080:80
podman run -d --pod web nginx
podman run -d --pod web --name log-collector alpine sleep 3600
podman pod ps
# run the same declaration with systemd supervision
podman generate systemd --new --name web > ~/.config/systemd/user/web.service
systemctl --user enable --now web.service
# Docker-compatible compose front ends
podman compose up -d # or podman-compose up -d| Aspect | Docker | Podman |
|---|---|---|
| Architecture | Client plus root daemon | One process per container, no daemon |
| Root by default | Yes | No — rootless is the norm |
| SELinux labels on bind mounts | Usually fine | Add :Z or :z |
| Systemd integration | Manual unit authoring | generate systemd or Quadlet |
| Pods | Not a native concept | Native grouping of containers |
| Compose support | Native plugin | podman compose or podman-compose |
| Image format | OCI | OCI — the same images |
- Rootless containers map your UID into a subordinate range, which is why a bind mount can look root-owned inside and yours outside.
--userns keep-idaligns them. :Zrelabels the host path for the container, which fixes access on SELinux systems and is unnecessary elsewhere.- On a machine with systemd, prefer user units over
docker runin a script: they restart, log to the journal and start at boot.
How Kubernetes consumes the same images
# on a node, the runtime is containerd and the CLI you may have is nerdctl
nerdctl --namespace k8s.io ps
nerdctl -n k8s.io images | grep api
crictl ps # the CRI view of the same containers
crictl pull registry.example.com/team/app:1.4.2apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
template:
spec:
containers:
- name: api
image: registry.example.com/team/app@sha256:8f1c9a4e # digest, not latest
imagePullPolicy: IfNotPresent
resources:
limits: { memory: 512Mi, cpu: "1.5" }- The cluster pulls the same OCI image you built and tested; the only difference is who starts it and with which CRI.
- There is no Docker daemon to mount inside a pod — use BuildKit, Buildah or Kaniko to build images in a cluster, never docker-in-docker.
- Pin images by digest in Kubernetes as well, otherwise a moved tag changes the workload at the next rollout.
- Image garbage collection on nodes removes images you stopped using; keep the registry as the source of truth rather than a node's cache.
FAQ
Can I replace Docker with Podman without changing my workflow?
Mostly:
alias docker=podman covers the common commands, and Podman provides a Docker-compatible socket for tools that insist on one. Expect to handle SELinux labels on bind mounts, rootless port binding below 1024, and a different Compose implementation.Why will 'docker' commands not work on my Kubernetes node?
Modern clusters run containerd or CRI-O directly as the CRI runtime, with no dockerd in the path. Use
crictl for the container runtime view or kubectl debug for a shell in the pod's namespaces.Related
Installing Docker and the core CLI workflow BuildKit, cache and multi-platform images
Last refreshed 2026-09-18.