Docker Containers (Dockerfile, Multi-stage Builds, Volumes, Networks & Security)
Deep-dive Docker and OCI containerization mastery covering Linux kernel namespaces and cgroups, OverlayFS layers, Dockerfile instructions (CMD vs ENTRYPOINT, COPY vs ADD, ARG vs ENV), multi-stage builds, storage (Volumes vs Bind Mounts), container networking, and container security hardening.
- Level
- basic
- Languages
- en → en
- Cards
- 32
- Markdown
- Docker Containers (Dockerfile, Multi-stage Builds, Volumes, Networks & Security) (.md)
Flashcards (32)
| # | Front (Term / Question) | Back (Definition / Answer) | Example | Multiple-Choice Options |
|---|---|---|---|---|
| 1 | Why do processes running as `PID 1` inside a container need special signal and zombie-reaping handling (e.g., `docker run --init` or `tini`)? | In Linux, `PID 1` has two unique kernel responsibilities: 1. The kernel ignores default signal handlers (like `SIGTERM`) on `PID 1` unless the process explicitly registers a signal handler in code; 2. If any child process inside the container becomes orphaned, it is re-parented to container `PID 1`—if `PID 1` (e.g., Java, Node, Python, or bash script) does not call `wait()` to reap dead children, zombie processes accumulate. Passing `--init` (or using `tini` as `ENTRYPOINT`) runs a tiny init wrapper as `PID 1` that forwards signals and reaps zombies. | ||
| 2 | What happens when a Docker container exits with code `137` vs. `143` vs. `1` vs. `126`/`127`? | In Linux/POSIX, exit codes `> 128` indicate termination by signal $128 + N$: `137` ($128 + 9 = \text{SIGKILL}$): The container was forcefully killed—most commonly by the Linux Kernel Out-Of-Memory (OOM) Killer (verify via `docker inspect <id> --format '{{.State.OOMKilled}}'`) or because it ignored `SIGTERM` during `docker stop`. `143` ($128 + 15 = \text{SIGTERM}$): Graceful shutdown via `SIGTERM`. `1`: General application error. `126`: Command found but not executable (permission error). `127`: Command/binary not found in `$PATH`. | ||
| 3 | Explain the architecture chain from `docker` CLI -> `dockerd` -> `containerd` -> `containerd-shim` -> `runc`. | 1. `docker` CLI sends REST API requests over `/var/run/docker.sock` to `dockerd`. 2. `dockerd` delegates container lifecycle management via gRPC to `containerd`. 3. For each container, `containerd` spawns a lightweight `containerd-shim` process. 4. `containerd-shim` invokes `runc` (the low-level OCI runtime) to create the Linux namespaces and cgroups and start the container process. 5. Once the container starts, `runc` exits, leaving `containerd-shim` as the container's parent—allowing `dockerd`/`containerd` to be upgraded/restarted without killing running containers ('live restore'). | ||
| 4 | What are Linux Capabilities in Docker, and why is `--cap-drop=ALL` far safer than `--privileged`? | The Linux kernel splits traditional omnipotent `root` privileges into ~40 distinct Capabilities (e.g., `CAP_NET_BIND_SERVICE` to bind ports <1024, `CAP_NET_ADMIN` to modify network interfaces, `CAP_SYS_ADMIN`, `CAP_CHOWN`). Docker grants a reduced default set of ~14 capabilities. Running `--privileged` grants ALL capabilities and exposes all host `/dev` devices (allowing trivial host takeover via mounting the host disk). Best practice: Drop all capabilities with `--cap-drop=ALL` and add back only what is strictly needed (e.g., `--cap-add=NET_BIND_SERVICE`). | ||
| 5 | How do `--memory`, `--memory-swap`, and `--cpus` work in `docker run` using Linux `cgroups`? | `--memory=512m` sets a hard RAM limit in the memory cgroup; if the container exceeds 512 MiB (plus allowed swap), the kernel OOM Killer terminates it (`Exit 137`). Setting `--memory=512m --memory-swap=512m` disables swap completely for the container (since `--memory-swap` represents total RAM + swap). `--cpus=1.5` uses the CFS (Completely Fair Scheduler) quota (`cpu.cfs_quota_us` / `cpu.cfs_period_us`) to throttle the container to at most 1.5 CPU cores' worth of CPU time per 100ms period without killing it. | ||
| 6 | List and explain the seven Linux Kernel Namespaces used by container runtimes (`runc` / `containerd`). | 1. `pid`: Isolates the Process ID number space (the container's entrypoint becomes `PID 1` inside the container); 2. `net`: Isolates network interfaces, loopback, routing tables, and `iptables`/ports; 3. `mnt`: Isolates filesystem mount points (`chroot`/`pivot_root`); 4. `uts`: Isolates hostname and NIS domain name; 5. `ipc`: Isolates System V IPC and POSIX message queues; 6. `user`: Maps root (`UID 0`) inside the container to an unprivileged non-root UID on the host; 7. `cgroup`: Isolates the view of the cgroup hierarchy. | ||
| 7 | Compare Docker Volumes, Bind Mounts, and `tmpfs` mounts for container storage. | 1. Volumes (`docker run -v myvol:/data`): Managed by Docker in `/var/lib/docker/volumes/` on the host; isolated from host path structure, supports volume drivers (NFS/cloud), safe to share across containers, and populates empty volumes with existing container directory contents. 2. Bind Mounts (`-v /host/path:/container/path` or `--mount type=bind`): Mounts an exact file/directory path from the host OS into the container (hides existing container directory contents). 3. `tmpfs` Mounts (`--tmpfs /app/cache`): Stored strictly in host RAM, never written to disk, erased when container stops. | ||
| 8 | Compare the five Docker Networking drivers: `bridge`, `host`, `none`, `overlay`, and `macvlan`. | 1. `bridge` (default): Creates virtual Ethernet pairs (`veth`) connected to a Linux bridge (`docker0` or custom bridge) with NAT (`iptables` MASQUERADE) to the host NIC. 2. `host` (`--network host`): Removes network namespace isolation; container binds directly to the host's IP and ports. 3. `none`: Only a loopback (`lo`) interface, zero external networking. 4. `overlay`: VXLAN encapsulation connecting containers across multiple physical hosts (Swarm/multi-host). 5. `macvlan`: Assigns a unique MAC address and physical LAN IP directly to the container. | ||
| 9 | How does Docker's OverlayFS (`overlay2`) storage driver work with `lowerdir`, `upperdir`, `merged`, and Copy-on-Write (CoW)? | Container images consist of immutable, read-only layers stacked as `lowerdir` (shared across all containers running the same image). When a container starts, Docker adds a thin writable container layer on top called `upperdir`, and presents the unified view at `merged`. Under Copy-on-Write (CoW), if a container modifies an existing file from a read-only image layer, `overlay2` copies that file up into `upperdir` and modifies the copy, leaving the underlying image layer untouched. | ||
| 10 | Compare `COPY` and `ADD` instructions in a `Dockerfile` and state which is preferred. | `COPY` strictly copies local files and directories from the build context into the container image—it is explicit, predictable, and ALWAYS PREFERRED by Docker best practices. `ADD` has two extra magic behaviors: 1. It automatically extracts local compressed tar archives (`.tar.gz`, `.tar.bz2`, `.tar.xz`) into the destination directory; 2. It can fetch remote URLs (though `curl`/`wget` + `rm` in a `RUN` layer is often preferred or `ADD --checksum` in BuildKit). | ||
| 11 | Compare `ARG` and `ENV` instructions in a `Dockerfile`, and explain why `ARG` must NEVER be used for secrets. | `ARG` defines build-time variables (passed via `docker build --build-arg VAR=val`) that are available ONLY during `docker build` and do NOT persist in the running container environment. `ENV` defines environment variables that exist both during build AND persist inside running containers (overridable via `docker run -e`). WARNING: `ARG` values are baked in plain text into the image's layer history (`docker history <image>`), so passing secrets via `ARG` leaks them! Use BuildKit secret mounts (`RUN --mount=type=secret,id=mysecret`) instead. | ||
| 12 | What is the critical difference between Exec Form (`["executable", "param1"]`) and Shell Form (`executable param1`) in `CMD` and `ENTRYPOINT`? | Shell Form (`CMD node server.js`) wraps the command in `/bin/sh -c`, making `/bin/sh` run as `PID 1` inside the container instead of your application! Because `/bin/sh` does not forward POSIX signals like `SIGTERM` (sent by `docker stop` or Kubernetes pod termination) to child processes, your app never receives `SIGTERM`, hangs for the 10s/30s grace period, and gets killed violently with `SIGKILL` (`137`). Exec Form (`CMD ["node", "server.js"]`) runs your app directly as `PID 1` so it receives `SIGTERM` cleanly. | ||
| 13 | Compare the four Docker container restart policies (`no`, `on-failure`, `always`, `unless-stopped`). | `no` (default): Never automatically restart the container. `on-failure[:max-retries]`: Restart ONLY if the container exits with a non-zero exit code (indicating an error). `always`: Always restart the container regardless of exit code—even if you manually ran `docker stop`, it will start back up when the Docker daemon restarts! `unless-stopped`: Always restart the container when it crashes or on daemon reboot, UNLESS the container was explicitly placed into a stopped state by a user before the daemon restarted. | ||
| 14 | What is the Docker Build Context and what role does `.dockerignore` play? | When you run `docker build .`, the Docker CLI archives and sends the directory contents (the 'Build Context') to the Docker daemon/BuildKit builder. Without a `.dockerignore` file, massive folders like `.git/`, `node_modules/`, `venv/`, build artifacts, and sensitive `.env` or `.aws` files are uploaded to the builder and can accidentally be baked into the image via `COPY . .`, causing slow builds, broken layer caches, and credential leaks. | ||
| 15 | Why is running containers as `root` (default) dangerous, and how do you enforce non-root execution in a `Dockerfile`? | By default, `root` (`UID 0`) inside a standard container is the exact same `UID 0` (`root`) on the underlying Linux host kernel! If a container breaks out via a kernel vulnerability or mounts a host directory, the attacker has root privileges on the host. Mitigation: Create a dedicated group and user in the `Dockerfile` (`RUN groupadd -r app && useradd -r -g app -u 10001 app`), set file ownership (`COPY --chown=app:app`), and switch context with `USER 10001:10001` before `ENTRYPOINT`. | ||
| 16 | Why should an Image Digest (`image@sha256:...`) be used instead of a mutable Image Tag (`image:latest` or `image:1.2`) in high-security production deployments? | Docker/OCI Image Tags (even semantic version tags like `v1.2.0`, not just `:latest`) are MUTABLE pointers in a registry—someone can overwrite `v1.2.0` with a different or compromised build tomorrow, resulting in different nodes running different code. An Image Digest (`sha256:4a8b...`) is an immutable cryptographic SHA-256 hash of the image manifest; pinning by digest guarantees bit-for-bit reproducibility across every node. | ||
| 17 | Why should `apt-get update` and `apt-get install` ALWAYS be combined into a single `RUN` instruction alongside cache cleanup? | 1. Cache Busting Bug: If `RUN apt-get update` is on its own line, Docker will cache that layer for weeks; later when you add a package to a separate `RUN apt-get install` line, Docker reuses the stale cached package index and fails with `404 Not Found`. 2. Layer Bloat: Deleting `/var/lib/apt/lists/*` in a LATER `RUN` layer does not reduce image size because the files are already committed to the previous immutable layer! Always combine: `RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*`. | ||
| 18 | What is a Multi-Stage Build in Docker and why is it essential for production images? | A Multi-Stage `Dockerfile` uses multiple `FROM` statements (e.g., `FROM golang:1.22 AS builder` followed by `FROM gcr.io/distroless/static-debian12`). You compile source code, install SDKs/compilers (`gcc`, `maven`, `npm`), and run tests in the heavy builder stage, then use `COPY --from=builder /app/bin /app/bin` to copy ONLY the compiled artifact into a minimal final runtime image—slashing image size by 80–98% and eliminating compilers, shells, and CVEs from production. | ||
| 19 | How do you reclaim disk space on a Docker host and inspect what is consuming storage under `/var/lib/docker`? | Run `docker system df` (or `docker system df -v`) to inspect disk usage broken down by Images, Containers, Local Volumes, and Build Cache. Run `docker system prune` to remove stopped containers, unused networks, dangling (untagged) images, and build cache; add `-a` (`--all`) to remove all unused images not referenced by any container, and `--volumes` to remove unused anonymous/named volumes. | ||
| 20 | How does a User-Defined Bridge network (`docker network create mynet`) differ from the default `bridge` (`docker0`) network? | On the default `docker0` bridge network, containers can only communicate with each other by raw IP address (no automatic DNS resolution by container name). On a User-Defined Bridge network, Docker runs an embedded DNS server at `127.0.0.11`, enabling automatic service discovery where containers resolve each other by container name or `--network-alias`, plus providing better network isolation and dynamic online `docker network connect/disconnect`. | ||
| 21 | What is the difference between `docker compose up`, `docker compose down`, and `docker compose down -v`? | `docker compose up -d` creates the project's custom bridge network, creates named volumes, and starts all services defined in `compose.yaml` in detached mode. `docker compose down` stops and removes the containers and removes the project network, but PRESERVES named volumes so database data is not lost. `docker compose down -v` (`--volumes`) also deletes all named volumes declared in the `volumes:` section, permanently wiping stateful container data. | ||
| 22 | Compare `CMD` and `ENTRYPOINT` in a `Dockerfile`, and explain how they interact when both are specified. | `ENTRYPOINT` configures the container to run as a specific executable binary; `CMD` provides default arguments (or a default command if `ENTRYPOINT` is not set). When ANY trailing arguments are passed to `docker run <image> <args>`, they OVERRIDE `CMD`, whereas `ENTRYPOINT` is NOT overridden unless `--entrypoint` is explicitly passed. Best practice: combine Exec-form `ENTRYPOINT ["nginx"]` with `CMD ["-g", "daemon off;"]` so `CMD` acts as default flags passed into `ENTRYPOINT`. | ||
| 23 | What does the `HEALTHCHECK` instruction in a `Dockerfile` do, and what are the three health states of a container? | `HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 CMD curl -f http://localhost:8080/healthz || exit 1` instructs Docker to periodically run a command inside the container to verify the application is actually serving requests (not just deadlocked while `PID 1` stays alive). The three states are: `starting` (during grace period), `healthy` (command returns `0`), and `unhealthy` (command returns `1` for `retries` consecutive times). | ||
| 24 | How do you debug a running `Distroless` or `scratch` container that has no `/bin/sh` or debugging utilities installed? | Because `docker exec -it <container> sh` fails when there is no shell inside the target container, you can attach a debug container sharing the target container's PID and network namespaces: `docker run -it --rm --pid=container:<target> --net=container:<target> busybox sh` (or use `docker debug` / `kubectl debug -it <pod> --image=busybox --target=<container>` in Kubernetes Ephemeral Containers). From the debug container, the target's filesystem is accessible at `/proc/1/root/`! | ||
| 25 | What is the difference between `EXPOSE 8080` in a `Dockerfile` and `-p 80:8080` (`--publish`) vs. `-P` in `docker run`? | `EXPOSE 8080` in a `Dockerfile` is purely metadata/documentation—it does NOT actually publish or open the port on the host machine! To make the container reachable from outside the host, you must run `docker run -p 80:8080` (which creates an `iptables`/proxy rule mapping host port `80` to container port `8080`, or bind to localhost only via `-p 127.0.0.1:80:8080`). `-P` (`--publish-all`) binds every `EXPOSE`d port to a random ephemeral high port on the host. | ||
| 26 | How does Docker Layer Caching work during `docker build`, and how should you order instructions in a `Dockerfile` to maximize cache hits? | Docker caches each instruction layer sequentially; as soon as ONE layer's cache is invalidated (e.g., a file in `COPY` changed), ALL subsequent layers in the `Dockerfile` must be rebuilt from scratch. Therefore, order instructions from LEAST frequently changing at the top to MOST frequently changing at the bottom: 1. Base OS packages (`RUN apt-get...`); 2. Copy ONLY dependency manifests (`COPY package.json package-lock.json ./`); 3. Install dependencies (`RUN npm ci`); 4. Copy application source code (`COPY . .`) last. | ||
| 27 | How do `--read-only`, `--security-opt=no-new-privileges:true`, and `seccomp` profiles harden a running container? | `--read-only` mounts the container's root filesystem (`upperdir`) as strictly read-only so an attacker exploiting RCE cannot write backdoors or modify binaries (pair with `--tmpfs /tmp` for scratch space). `--security-opt=no-new-privileges:true` prevents container processes from gaining extra privileges via SUID/SGID binaries. Docker's default `seccomp` (Secure Computing Mode) profile blocks ~44 dangerous Linux system calls (out of 300+, such as `kexec_load`, `mount`, `reboot`, `init_module`) at the kernel syscall boundary. | ||
| 28 | What is the Open Container Initiative (OCI) and what are its three specifications (`runtime-spec`, `image-spec`, `distribution-spec`)? | The OCI (under the Linux Foundation) defines vendor-neutral standards for containers: 1. `image-spec`: Defines how an OCI container image manifest, image index (multi-arch), filesystem layer tarballs, and JSON config are structured; 2. `runtime-spec`: Defines how a low-level OCI runtime (like `runc`, `crun`, or `youki`) unpacks a filesystem bundle and launches a container using a `config.json`; 3. `distribution-spec`: Standardizes the HTTP Registry API for pushing and pulling OCI images and artifacts. | ||
| 29 | How do Containers differ architecturally from Virtual Machines (VMs), and what two Linux kernel features make containers possible? | Virtual Machines run a full Guest Operating System and kernel on top of virtualized hardware managed by a Hypervisor (KVM, Hyper-V, ESXi), incurring higher boot time and memory overhead. Containers share the Host OS Linux Kernel directly and isolate processes using two core kernel features: 1. Namespaces (which isolate what a process can SEE), and 2. Control Groups / `cgroups` (which limit how much hardware resources—CPU, RAM, Block I/O, PIDs—a process can USE). | ||
| 30 | Compare Distroless base images, Alpine Linux (`musl` libc), and Scratch images. | `FROM scratch`: Empty 0-byte image containing literally nothing (only works for 100% statically linked binaries like Go/Rust with `CGO_ENABLED=0`; note: lacks CA certificates and `/tmp` unless copied in). `Alpine` (~5 MB): Includes `busybox` shell and `apk` package manager, but uses `musl libc` instead of `glibc`, which can cause subtle DNS resolution or C-extension compatibility/performance issues in Python/Node/JVM. `Distroless` (Google): Contains ONLY the minimal `glibc`/runtime, CA certs, and `/etc/passwd`—no shell (`/bin/sh`) and no package manager. | ||
| 31 | Why is mounting `/var/run/docker.sock` inside a container (e.g., for CI/CD runners or monitoring tools) equivalent to giving that container full `root` access to the host? | `/var/run/docker.sock` is the Unix domain socket that the root-owned `dockerd` daemon listens on without authentication. Any container with write access to `docker.sock` can issue API calls to spawn a new container on the host with `--privileged -v /:/host_root`, `chroot /host_root`, and immediately take full uninhibited `root` control over the underlying host OS. For building images in CI/CD, use rootless BuildKit, Kaniko, or ephemeral VMs instead. | ||
| 32 | How do you build Multi-Architecture (`linux/amd64` and `linux/arm64`) container images using Docker Buildx? | Docker Buildx (powered by Moby BuildKit) supports cross-compilation and QEMU user-mode emulation to build multi-platform OCI Manifest Lists (Image Indexes). Command: `docker buildx create --use && docker buildx build --platform linux/amd64,linux/arm64 -t myorg/app:v1.0 --push .` Using `$TARGETOS` and `$TARGETARCH` automatic build args inside a Go/Rust `Dockerfile` avoids slow QEMU emulation by running native cross-compilers on the host architecture. |