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.

SpecDefinesImplemented by
OCI ImageLayers, config, manifest listEvery builder and registry
OCI RuntimeHow a container process is createdrunc, crun, youki
OCI DistributionPull, push and tag over HTTPRegistries and clients
CRIThe Kubernetes runtime interfacecontainerd, CRI-O
ToolRole
dockerdDaemon: builds, manages networks and volumes, talks to containerd
containerdSupervises containers; what Kubernetes actually talks to
runc / crunCreates the namespaces and execs the process
PodmanDaemonless, Docker-compatible CLI with pods and rootless first
nerdctlA Docker-like CLI for containerd namespaces
Buildah / Kaniko / BuildKitImage 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
AspectDockerPodman
ArchitectureClient plus root daemonOne process per container, no daemon
Root by defaultYesNo — rootless is the norm
SELinux labels on bind mountsUsually fineAdd :Z or :z
Systemd integrationManual unit authoringgenerate systemd or Quadlet
PodsNot a native conceptNative grouping of containers
Compose supportNative pluginpodman compose or podman-compose
Image formatOCIOCI — 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-id aligns them.
  • :Z relabels 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 run in 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.2
apiVersion: 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.

Installing Docker and the core CLI workflow BuildKit, cache and multi-platform images

Last refreshed 2026-09-18.