📦

Level 16 of 24

Containers

Docker and Podman: images, namespaces, cgroups, and what isolation really means.

Containers are built entirely from Linux primitives you've already learned — this level connects namespaces, cgroups, and processes to what Docker and Podman actually do underneath their friendly commands.

Prerequisites

  • • Level 5: Processes & Services
  • • Level 7: Networking

By the end of this level, you can

  • ✓ Explain what a container actually is in terms of kernel features
  • ✓ Run, port-map, and volume-mount a container
  • ✓ Explain the practical difference between Docker and Podman

What a container actually is

Namespaces and cgroups — the two kernel features containers are built from. · 9 min

A container is not a lightweight virtual machine — there's no separate kernel, no hypervisor layer; a container is a regular Linux process (or group of processes) running on the same kernel as the host, made to look isolated through two specific kernel features. Namespaces isolate what a process can see: a PID namespace makes a containerized process think it's PID 1 in its own private process tree, unaware of other processes on the host; a network namespace gives it its own network interfaces and routing table; a mount namespace gives it its own view of the filesystem. cgroups (control groups) limit and account for what a process can use — CPU, memory, disk I/O — enforced by the kernel regardless of what the process inside believes about its own resources.

This is why containers start in milliseconds and share the host kernel's resources efficiently (no second OS to boot, no hypervisor overhead), but also why container isolation is fundamentally weaker than a real virtual machine's: a kernel vulnerability, or a container run with excessive privileges, can potentially let a process escape its namespace/cgroup confinement and affect the host directly — exactly the reasoning behind avoiding `--privileged` and host-filesystem/Docker-socket mounts for anything running untrusted code, discussed in this course's own lab-architecture design decisions.

An image is a read-only template — a filesystem snapshot plus metadata about what to run — that a container is instantiated from at runtime; multiple containers can run from the same image simultaneously, each getting its own writable layer on top. A registry (Docker Hub being the most common public one) stores and distributes images, pulled down with `docker pull` before a container can be created from them.

  • • Namespaces — isolate what a process can SEE (its own PIDs, network, filesystem view)
  • • cgroups — limit and account for what a process can USE (CPU, memory, I/O)
  • • Image — a read-only template a container is instantiated from
  • • Container — a running instance of an image, with its own writable layer on top

Takeaway: A container is a regular Linux process made to look isolated via namespaces and limited via cgroups — not a separate virtual machine with its own kernel, which is exactly why container isolation, while genuinely useful, is not equivalent to full VM-level isolation.

Running containers with Docker and Podman

Images, port mapping, volumes, and Podman as the rootless alternative. · 10 min

Docker was the tool that popularized this workflow and remains extremely common: `docker run` creates and starts a container from an image, `docker ps` lists running containers, `docker images` lists locally available images, `docker build` creates a new image from a `Dockerfile` (a text file describing the image's contents step by step). Port mapping (`-p 8080:80`) exposes a container's internal port to the host, since a container's network namespace is isolated by default — without an explicit mapping, nothing outside the container can reach a service running inside it. Volume mounting (`-v /host/path:/container/path`) shares a directory between host and container, essential for persisting data beyond a container's own lifecycle, since a container's writable layer is normally destroyed when the container is removed.

Podman is a more recent, largely command-compatible alternative (`podman run`, `podman ps` work almost identically to their Docker equivalents) with one structurally significant difference: Podman has no long-running background daemon running as root — Docker's traditional architecture relies on a root-privileged daemon (`dockerd`) that every `docker` command talks to, which is itself a meaningful attack surface. Podman can run fully rootless, with unprivileged users able to run containers without ever needing root or daemon access, which is a genuine security architecture improvement adopted as the default on RHEL-family systems specifically for this reason.

For either tool, the core safety principle from this course's own security thinking applies directly: never run a container with `--privileged` or mount the Docker/Podman socket into another container unless you specifically understand and accept what that grants — both effectively hand the container the ability to control the host itself, well beyond ordinary container isolation.

CommandPurposeExample
docker run -d -p 8080:80 nginxRun an Nginx container in the background, mapping host port 8080 to container port 80—
docker psList running containers—
docker imagesList locally available images—
docker exec -it container-id bashOpen an interactive shell inside a running container—
docker logs container-idView a container's logs—
podman run -d -p 8080:80 nginxThe Podman equivalent — largely command-compatible with Docker—
🧪

Hands-On Lab

Run, port-map, and volume-mount a container

Objectives

  • ✓ Run a container with a mapped port
  • ✓ Confirm it's reachable from the host
  • ✓ Mount a host directory into the container and confirm data persists

Instructions

  1. Run an Nginx container: `docker run -d -p 8080:80 --name test-nginx nginx` (substitute `podman` if using Podman).
  2. Confirm it's running with `docker ps`, then test it with `curl -I http://localhost:8080`.
  3. Create a local file `echo "hello from host" > index.html` in your current directory.
  4. Stop and remove the test container, then re-run it with a volume mount: `docker run -d -p 8080:80 --name test-nginx -v $(pwd)/index.html:/usr/share/nginx/html/index.html nginx`.
  5. Run `curl http://localhost:8080` again and confirm you now see "hello from host" instead of the default Nginx welcome page.
Hints (1)
  • If port 8080 is already in use from an earlier lab, either stop that service first or map to a different host port.

Takeaway: A container's network and filesystem are isolated by default — nothing is reachable or persisted unless you explicitly map a port or mount a volume, which is a deliberate safety default, not an oversight.

Sponsor / Advertisement