Skip to content

Containers

Core · prereq 01 (DevOps Foundations) · ~1–1.5 days (4 submodules: images/builds, storage, networking, rootless security)

Docker's architecture centers on one thing: a root-owned daemon that owns every container on the host, reachable through a socket that's effectively root access if anything compromises it. Podman removes that single point of privilege — each podman command talks straight to the kernel, and the whole thing can run as your own unprivileged user, no daemon required. That's the daemonless, rootless model this module is built around, and it changes how you build, store, and network containers, not just how you launch them.

TL;DR — in 30 seconds:

  • Podman is daemonless and rootless: no root-owned background process to compromise — podman commands talk straight to the kernel as your own user.
  • The three artifacts to know: image (read-only layers, immutable), container (a running instance plus its own throwaway writable layer), volume (storage that outlives the container).
  • Rootless has real limits worth knowing before you hit them: no binding ports below 1024 without a fix, and no name-based DNS on the default network.

Learning objectives

By the end, the junior can:

  • Explain Podman's daemonless, rootless model and how it differs from Docker.
  • Write a Containerfile (multi-stage, non-root) and build an image with it.
  • Run a container with a named volume and prove data persists across restarts.
  • Reason about rootless networking (why a low port fails, and the fixes).

Do it with Claude

Pair, Do and Prove run in Claude Code, with the onboarding-tutor skill. Click a button to copy the exact prompt, then paste it into Claude Code in this repo.

1. Learn · acquire the concept

Read: the podman course Focus sections: 2 (mental model — daemonless/rootless), 4 (first container), 6 (building with Containerfile), 8 (networking — rootless port mapping and the fixes), 9 (storage — volumes vs bind mounts, the :Z SELinux relabel). Skip for now: section 10 (pods) and 11 (advanced security) — you'll revisit pods in the Kubernetes module. Glossary (for beat 5): the module’s Anki deck Read next: Podman documentation (official docs — daemonless/rootless architecture, Containerfile reference, networking and storage details).

Check yourself — what specifically does 'rootless' remove, and what does it not remove?

It removes the daemon and the requirement to run as host root — podman talks straight to the kernel as your own user. It doesn't remove privilege entirely: a rootless container still runs in its own user namespace with a curated (not full) capability set — layered defenses, not "no restrictions at all."

Check yourself — name the three things you build/run and how they relate: image, container, volume

An image is the read-only, versioned filesystem + config you build with a Containerfile. A container is one running instance of that image, plus its own throwaway writable layer. A volume is storage that lives outside any one container's lifecycle, so data survives when the container is replaced.

2. Pair · passive → active

Drive the onboarding-tutor skill:

  • "Explain daemonless and rootless and why they matter operationally vs Docker."
  • "3-line mental model of an image vs a container vs a volume."
  • "Quiz me on volumes vs bind mounts (and what :Z does) before I build anything."

Done when: you can explain, unprompted, why Podman needs no daemon and what rootless buys you.

3. Do · produce an artifact

Exercise — build and run a small service, rootless, with persistence:

  1. Set up Podman first (course §3): on macOS brew install podman && podman machine init && podman machine start; on RHEL it's already present. Confirm with podman info.
  2. Write a multi-stage Containerfile for any tiny app (a 10-line HTTP server in Go/Python/Node is fine) that runs as a non-root user inside the image.
  3. Build it rootless: podman build -t myapp:0.1 .
  4. Run it with a named volume mounted where the app writes state, publishing a port.
  5. Write something through the app, stop and remove the container, run a fresh one on the same volume, and show the data survived.

  6. On macOS, Podman runs in a small VM (course §3) — that's expected.

  7. The tutor gives hints, not the finished Containerfile.

Common gotchas

  • Building rootful out of habit (sudo podman build). Fix: rootless is the default that matters here — build and run as your own user; reach for sudo only when a task genuinely needs it.
  • Forgetting USER in the Containerfile. Fix: without it the container runs as root by default — set a non-root USER explicitly, even though rootless already narrows what that root can do to the host.
  • Using a bind mount for data you want Podman to manage. Fix: a named volume is simpler and gets the right SELinux label automatically; reach for a bind mount only when a host process needs the same files.
  • Publishing a low port (-p 80:80) and assuming it's broken. Fix: rootless can't bind ports below 1024 by default — pick a higher host port, adjust the sysctl floor, or grant the capability; it isn't a bug in your Containerfile.
  • Skipping :Z/:z on a bind mount and blaming permissions. Fix: an EACCES on a bind mount that looks correctly chmod'd is almost always a missing SELinux relabel, not a Unix-permissions problem.
Check yourself — you bind-mount a host directory and the container gets a permission-denied on a file that looks correctly owned. What's the likely cause?

A missing SELinux relabel, not a Unix permissions bug — the host path doesn't carry the container-accessible SELinux context. Adding :Z (private) or :z (shared) to the -v option fixes it.

Check yourself — why does podman run -p 8080:80 work rootless but -p 80:80 doesn't?

Linux reserves ports below 1024 for root by default, and rootless Podman's port-forwarding helper is still an unprivileged process doing the bind. 8080 is above that floor so it binds fine; 80 needs a fix (higher port, lowered sysctl floor, or the CAP_NET_BIND_SERVICE capability).

Done when: podman run serves the app, and you've demonstrated data persisting across a container replace.

4. Prove · understanding gate

Mandatory. Pass bar in the Socratic gate.

  1. Walkthrough: Explain your Containerfile stage by stage. Why multi-stage? Why non-root?
  2. Alternative: Named volume vs bind mount for this data — which did you choose and why? When is the other correct? What does :Z do and when do you need it?
  3. Failure & debug: You publish :80 rootless and it fails. Why? Give two fixes (course §8) and their trade-offs. How would you diagnose "container exits immediately"?
  4. Provenance: If the tutor/agent suggested any of your Containerfile, which lines — and how did you verify they were right (not just that it built)?

Pass bar: you can defend the image design, the storage choice, and the rootless-port behaviour. "It built and ran" without the why = back to Pair.

5. Retain · fight forgetting

Study the module deck SE Onboarding::03 Containers (Podman) — its theme subdecks (Foundations · Podman the tool · Image management · Container lifecycle · Networking · Storage · Pods · Security · Systemd integration · Kubernetes / Compose · System operations) let you drill one area at a time. It syncs to your Anki automatically on pull (anki-sync).

Done when: you can recall — from the deck — daemonless/rootless, image/container/volume, Containerfile, and the multi-stage build.


Key takeaways

  • Rootless removes the daemon and host-root requirement, not privilege entirely. A rootless container still runs in its own user namespace with a curated capability set — layered defenses, not "no restrictions at all."
  • Three artifacts, three lifecycles. An image is read-only and versioned; a container is one running instance plus its own throwaway writable layer; a volume is storage that outlives any single container.
  • A named volume beats a bind mount for data Podman should manage. It gets the right SELinux label automatically; reach for a bind mount only when a host process needs the same files.
  • A permission-denied on an otherwise-correct bind mount is almost always a missing SELinux relabel. Add :Z (private) or :z (shared) before assuming a Unix-permissions problem.
  • Rootless can't bind ports below 1024 by default. -p 8080:80 works, -p 80:80 doesn't — pick a higher host port, lower the sysctl floor, or grant CAP_NET_BIND_SERVICE; it isn't a bug in the Containerfile.
  • Set USER explicitly in the Containerfile. Without it the container defaults to root, even though rootless already narrows what that root can do to the host.

Go deeper — curated resources

Hand-picked external material to take this topic further — the best books, courses, talks, and writing. Cost is tagged Free, Free online (full text/course free on the web), or Paid.

  • Book · Docker Deep Dive — Nigel Poulton. The clearest containers book; the concepts transfer directly to Podman. Paid.
  • Docs · Podman Documentation — Official. SE's own container engine, straight from the source. Free.
  • Talk · Containers From Scratch — Liz Rice, GOTO 2018. Builds a container live from Linux primitives (namespaces, cgroups). Free.
  • Course · A Docker Tutorial for Beginners — Prakhar Srivastav. Hands-on intro that maps 1:1 to Podman. Free.
  • Book · How Containers Work — Julia Evans. Illustrated zine that builds the mental model fast. Paid.

Gate log (mentor fills on pass)

  • Date passed:
  • Passed by: / checked by:
  • Weak spots noticed (feeds system improvement):