Docker · devops

Docker Certified Associate (DCA)

Complete DCA exam prep: containers vs VMs, Dockerfile + multi-stage builds, layer caching, image registries and Content Trust, named volumes / bind mounts / tmpfs, bridge / overlay / macvlan networking, Docker Swarm with Raft consensus, services and rolling updates, secrets and configs, capabilities, seccomp, AppArmor, image signing and CVE scanning. Six modules, eighteen lessons, exam-aligned.

6Modules
25 hoursDuration
intermediateLevel

Jump straight into practice questions

Scenario-based DCA questions covering every exam domain — free, no signup required.

DCA Exam Snapshot

FormatMultiple choice + DOMC
Duration90 minutes
Questions~55
Passing score~65–70%
PrerequisiteNone (6+ months Docker)
Validity2 years

Exam Domain Weights

Orchestration (Swarm)25%
Image Creation, Management & Registry20%
Installation & Configuration15%
Networking15%
Security15%
Storage & Volumes10%

Key Concept: Containers ≠ Lightweight VMs

The single biggest mental-model fix the DCA exam pushes is that containers are processes, not tiny machines. There is no guest kernel, no hypervisor, no boot sequence — a container is one or more host processes wrapped in Linux namespaces (isolation) and cgroups (resource limits), reading from a layered read-only filesystem (OverlayFS). Every DCA security, networking, and storage decision flows from this. If you ever catch yourself thinking "the container's OS" — stop. The host kernel is the container's kernel.

🎧

Learn Docker on the go

Tune in to multi-stage build walkthroughs, Swarm vs Kubernetes comparisons, DCA exam strategy, and container security deep-dives. New episodes every week — perfect for commutes and gym sessions.

Listen on Spotify

6 modules · ~25 hours

Each module maps to one DCA exam domain. Work through them in order — Module 1 (Architecture) and Module 2 (Images) build the foundation every later module assumes. Module 5 (Swarm, 25%) is the heaviest single domain on the exam and the most common place candidates lose points on the clock.

01

Container Fundamentals & Docker Architecture3 lessons

Before any CLI flag or Dockerfile instruction, you need the mental model: what a container actually is, who runs it, and which daemon talks to which runtime. Roughly 15% of DCA questions live here under "Installation & Configuration", and the wrong mental model causes mistakes in every later domain.

namespaces cgroups overlayfs dockerd containerd runc daemon-json logging-drivers
~3h
📖 Read in-depth chapter
Lesson 1.1 Containers vs VMs — namespaces, cgroups, OverlayFS

A VM virtualises hardware: the hypervisor lies to a guest OS about CPUs, NICs, and disks, and that guest boots a full kernel. A container virtualises only what userland sees of the host — a process and its children get their own filesystem, network interface, PID list, hostname, and user IDs while still running on the host's single kernel. Faster startup, smaller footprint, but a thinner blast wall: a container escape is a host escape.

Key concepts
  • Linux namespaces — kernel feature that isolates a process's view of the system. Docker uses six: pid (process IDs), net (network interfaces, routes, iptables), mnt (mount points), uts (hostname, domain), ipc (System V IPC, POSIX message queues), user (UID/GID remap).
  • Control groups (cgroups v1/v2) — limit and account CPU, memory, block I/O, network bandwidth, PIDs. docker run --memory 512m --cpus 1.5 writes to /sys/fs/cgroup/... on the host.
  • Union / layered filesystem — every image layer is a read-only directory; the container's writable layer is overlaid on top. Default driver on Linux is overlay2; layers are content-addressed by SHA256 digest.
  • Capabilities — Linux splits root power into ~40 capabilities (CAP_NET_ADMIN, CAP_SYS_ADMIN, …); Docker drops most by default and runs containers with a minimal set. This is why a container "root" cannot reboot the host.
  • What containers do NOT virtualise — the kernel, kernel modules, the time/date clock, sysctls (mostly), or hardware. A bad host kernel update can break every container at once.
Concrete example

Run docker run --rm -it alpine sh, then inside the shell: ps -ef shows your shell as PID 1 (PID namespace), ip addr shows only the container's eth0 (network namespace), hostname shows the container ID (UTS namespace), and cat /etc/os-release reports Alpine even though your host is Ubuntu (mnt namespace + image rootfs). Run uname -r in both the container and the host — same kernel version. That single command sums up the container model.

Key takeaway: a container is one (or more) host processes wrapped in namespaces and cgroups, reading from a layered read-only filesystem. There is no guest kernel and no boot sequence. Every later DCA topic — security, networking, storage — is just a way of configuring those four things (namespaces, cgroups, capabilities, filesystem layers).
⚡ Mini-quiz
Drill containers-vs-VMs and isolation primitives → study mode (10 questions).
Lesson 1.2 Docker architecture — daemon, containerd, runc, registry

"Docker" is not one binary — it is a small stack of cooperating processes. The exam asks who talks to whom, who actually creates a container, and what survives a daemon restart. Get this graph right and half the troubleshooting questions answer themselves.

Key concepts
  • Docker CLI (docker) — thin client that speaks the Docker REST API to the daemon over the Unix socket /var/run/docker.sock (or TCP when configured). Stateless.
  • Docker daemon (dockerd) — long-running server. Exposes the REST API, manages images / networks / volumes / Swarm state, and delegates container lifecycle to containerd.
  • containerd — the container runtime that pulls images, unpacks them, and manages running containers. Survives dockerd restarts (your containers keep running while you upgrade the daemon).
  • runc — the OCI low-level runtime that actually invokes the kernel syscalls to create the namespaces / cgroups bundle. containerd calls runc per container; runc exits once the container's PID 1 has been started.
  • Registry — a separate service (Docker Hub, GHCR, Harbor, ECR, a self-hosted Registry image…) that stores image manifests and layer blobs. Always TLS-fronted in production.
  • Object model — Image (read-only template, SHA256 digest), Container (running or stopped instance of an image with its writable layer), Volume (managed persistent data), Network (virtual L2/L3 fabric between containers).
Concrete example

When you run docker run -d nginx: the CLI POSTs to /containers/create on the daemon socket; dockerd checks for the nginx:latest image locally, asks containerd to pull missing layers from the registry; containerd writes the layers to /var/lib/docker/overlay2/; dockerd POSTs /containers/<id>/start; containerd creates an OCI bundle and shells out to runc; runc creates the namespaces + cgroups + writable layer mount, execs nginx as PID 1, then exits. The container keeps running attached to containerd-shim — which is exactly why systemctl restart docker can be safe with live-restore enabled.

Key takeaway: Docker is a stack of cooperating processes — CLI → dockerd → containerd → runc — plus a registry. Knowing who owns container lifecycle (containerd) explains live-restore, why docker ps hangs but containers stay up during daemon issues, and why a runc CVE is a critical patch.
⚡ Mini-quiz
Drill daemon / containerd / runc separation → quick quiz (5 questions).
Lesson 1.3 Daemon configuration — daemon.json, logging drivers, live-restore

The DCA exam is loud about daemon configuration: where the file lives, which flags belong there, and how each one changes behaviour. Anything you would otherwise pass to dockerd on the command line goes in /etc/docker/daemon.json and survives upgrades.

Key concepts
  • Canonical path/etc/docker/daemon.json on Linux, %programdata%\docker\config\daemon.json on Windows. After editing: systemctl reload docker for most keys; restart for storage driver or live-restore changes.
  • Logging driversjson-file (default, with max-size + max-file rotation), journald, syslog, fluentd, gelf, awslogs, splunk. Set globally in daemon.json or per-container with --log-driver.
  • Live-restore"live-restore": true keeps containers running across dockerd restarts. Required for zero-downtime daemon upgrades but incompatible with Swarm mode.
  • Storage driver"storage-driver": "overlay2" on modern Linux. Changing it is destructive; configure on a fresh node only.
  • Insecure registries"insecure-registries": ["registry.lan:5000"] tells the daemon to skip TLS for a non-HTTPS registry; never use in production.
  • Default resource limitsdefault-ulimits, default-shm-size, plus userns-remap and seccomp profile paths all live in this file.
Concrete example

Production-grade /etc/docker/daemon.json for a non-Swarm host: { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" }, "storage-driver": "overlay2", "live-restore": true, "default-ulimits": { "nofile": { "Name": "nofile", "Hard": 65536, "Soft": 65536 } }, "userns-remap": "default" }. After systemctl reload docker, every new container writes capped, rotated JSON logs, can survive daemon restarts, has a sane file-descriptor limit, and runs with its in-container root mapped to an unprivileged host UID — three common DCA exam topics covered by one config block.

Key takeaway: /etc/docker/daemon.json is the canonical place for production daemon config — logging rotation, live-restore, storage driver, default ulimits, userns-remap. Memorise the JSON keys (they are the exact CLI flag names without the leading --) and the reload-vs-restart rules. The exam tests both.
⚡ Mini-quiz
Drill daemon.json keys and reload behaviour → study mode (10 questions).
02

Images & Dockerfile Mastery3 lessons

Image Creation, Management & Registry is exam domain 2 (20%) — the second heaviest after Swarm. Every Dockerfile instruction has a layer-cache implication, every push has a registry-auth implication, and the exam tests both at the same time. Three lessons cover instruction semantics, multi-stage builds and layer caching, and registry management end-to-end.

dockerfile cmd-vs-entrypoint multi-stage layer-cache dockerignore buildkit registry manifest
~5h
📖 Read in-depth chapter
Lesson 2.1 Dockerfile instruction reference — CMD vs ENTRYPOINT, COPY vs ADD, ARG vs ENV

The DCA exam loves to weaponise pairs of instructions that look identical but behave differently. CMD vs ENTRYPOINT, COPY vs ADD, ARG vs ENV — the question is rarely "what does X do" and almost always "given this Dockerfile, what does docker run produce?". Learn each pair with its exec form and you will read every question correctly.

Key concepts
  • FROM — required first instruction (or after ARG for multi-platform). Pin to a digest in production: FROM nginx@sha256:abc….
  • RUN — executes during build, creates a new layer per instruction. Chain with && + \ to keep the number of layers low.
  • COPY vs ADD — both copy files into the image. ADD additionally auto-extracts local tar archives and supports URLs. Default to COPY; use ADD only when you need extraction or URL fetch.
  • ARG vs ENVARG is build-time only (passed with --build-arg, gone at runtime); ENV persists into the image metadata and is visible to docker run. Both can be referenced in later RUN steps.
  • CMD — default arguments for the container. Overridden by any positional args after docker run image. Exec form: CMD ["nginx", "-g", "daemon off;"].
  • ENTRYPOINT — the executable. Combined with CMD, ENTRYPOINT receives CMD as its arguments. docker run --entrypoint overrides it; CMD args are appended to ENTRYPOINT.
  • EXPOSE — pure documentation. Does not publish the port; you still need -p on docker run or a Swarm --publish.
  • WORKDIR / USER / HEALTHCHECK / VOLUME — set working directory, runtime UID, container-level healthcheck command, declared mount points (auto-creates anonymous volumes if not mounted).
  • Shell vs exec form — shell form (CMD echo hi) wraps the command in /bin/sh -c; exec form (CMD ["echo", "hi"]) execs directly. Exec form is the safe default — it forwards signals (so docker stop reaches your process) and avoids surprises with quoting.
Concrete example

Given FROM alpine\nENTRYPOINT ["echo"]\nCMD ["hello"]: docker run img prints hello (ENTRYPOINT + default CMD), docker run img world prints world (CMD is replaced, ENTRYPOINT stays), docker run --entrypoint ls img -la / runs ls -la / (ENTRYPOINT replaced, CMD discarded). This is the exam's favourite four-line trap — read every Dockerfile question first as "what is ENTRYPOINT", second as "what is CMD", then compose the answer.

Key takeaway: learn each Dockerfile instruction as a pair: CMD vs ENTRYPOINT, COPY vs ADD, ARG vs ENV, shell vs exec form. Always pick the safer / more explicit member of the pair (ENTRYPOINT + exec form, COPY, ENV for runtime, ARG for build secrets). The exam rewards explicit, the codebase rewards explicit.
⚡ Mini-quiz
Drill CMD/ENTRYPOINT and COPY/ADD scenarios → study mode (10 questions).
Lesson 2.2 Multi-stage builds & layer caching — the small-image discipline

Multi-stage builds and .dockerignore are the two cheapest wins in image quality. Multi-stage lets you compile in a fat image and ship from a thin one; cache ordering lets a one-line code change rebuild in seconds instead of minutes. The DCA exam phrases both as "which Dockerfile is correctly optimised?".

Key concepts
  • Multi-stage syntaxFROM golang:1.22 AS builder opens a build stage; FROM alpine starts the final stage; COPY --from=builder /app/bin . moves only the compiled artefact across. Only the last stage is the published image (unless --target is used).
  • Layer cache invariant — a layer is reused only if (a) all prior layers are identical and (b) the instruction's bytes are identical. The first changed instruction invalidates every layer below it.
  • Order rule — put rarely-changing instructions first (base image, apt-get install, npm config), frequently-changing instructions last (copy source code, build). For Node: COPY package*.json .RUN npm ciCOPY . .RUN npm run build.
  • .dockerignore — controls what enters the build context (sent to the daemon). Always exclude .git/, node_modules/, *.log, secrets — keeps builds fast and avoids leaking files into images.
  • BuildKit — modern builder (enabled by DOCKER_BUILDKIT=1 or by default on newer Docker Desktop). Parallel stages, secret mounts (--mount=type=secret,id=mykey), cache mounts (--mount=type=cache,target=/root/.npm), and reproducible builds.
  • Distroless / scratch — final stages can be FROM gcr.io/distroless/static or FROM scratch for the smallest, most-secure runtime images. Statically compiled Go binaries fit in FROM scratch.
Concrete example

Production multi-stage Dockerfile for a Go service: FROM golang:1.22 AS build\nWORKDIR /src\nCOPY go.mod go.sum ./\nRUN go mod download\nCOPY . .\nRUN CGO_ENABLED=0 go build -o /app -ldflags '-s -w' ./cmd/svc\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=build /app /app\nUSER nonroot:nonroot\nENTRYPOINT ["/app"]. Three wins at once: final image < 20 MB, cache-friendly (only go.mod + go.sum trigger a dependency re-fetch), and runs as a non-root user with no shell to drop into after an exploit. This is exactly what DCA "best practice" questions reward.

Key takeaway: multi-stage + layer-cache ordering + .dockerignore is the small-image trio. Compile in a builder stage, copy only artefacts into a minimal runtime base, put COPY-source-code after dependency installation, and exclude .git / node_modules / secrets from the build context. Every DCA "which Dockerfile is correct" question is this rule in disguise.
⚡ Mini-quiz
Drill multi-stage + layer-cache scenarios → study mode (10 questions).
Lesson 2.3 Image management & registries — push, pull, tag, digest

Once images exist they need to move — into a registry, across environments, across teams. The DCA exam tests the difference between tags (mutable) and digests (immutable), the right way to authenticate against a private registry, and the difference between save / load and export / import.

Key concepts
  • Tag vs digest — a tag (myapp:1.0) is a human label that can be moved; a digest (myapp@sha256:abc…) is the content hash of the manifest and can never refer to different bytes. Pin production deployments by digest.
  • Push flowdocker tag local registry.com/team/app:1.0 + docker login registry.com + docker push registry.com/team/app:1.0. Credentials are stored under ~/.docker/config.json (use a credentials helper in production).
  • Manifests & multi-arch — modern images are described by an OCI manifest. Multi-arch images ship a "manifest list" pointing to per-arch manifests (amd64, arm64, arm/v7); docker buildx build --platform=linux/amd64,linux/arm64 --push creates both at once.
  • Image cleanupdocker image prune (dangling untagged), docker image prune -a (all unused), docker system prune (containers + images + networks + build cache). Add --filter "until=24h" to scope.
  • save / load vs export / importdocker save myimg | gzip > img.tar.gz preserves all layers + history (use to move images offline); docker export <container> flattens a container's filesystem to a tar (no layers, no history). Match the right pair: saveload, exportimport.
  • Run your own registry — the open-source registry:2 image runs a v2 OCI-compatible registry in seconds. Production setups put it behind TLS, add auth (htpasswd or token service) and persistent storage.
Concrete example

CI pipeline that builds and publishes a multi-arch image: docker buildx create --use --name multiarch\ndocker buildx build --platform linux/amd64,linux/arm64 \ \n -t ghcr.io/acme/svc:1.4.2 \ \n -t ghcr.io/acme/svc@sha256:<digest> \ \n --push .. Production deploy pulls by digest (immutable rollbacks), staging pulls by tag (latest 1.x). When the CFO asks "are we running the audited build?", the answer is kubectl describe pod — the digest in the spec is the proof.

Key takeaway: tags are for humans, digests are for production. Pair saveload and exportimport. Always TLS your registry, always prune build cache (docker builder prune + docker image prune -a --filter "until=168h" is a sane weekly cron) and always pin by digest in deployment manifests.
⚡ Mini-quiz
Drill registry + tag/digest + save/load scenarios → study mode (10 questions).
03

Storage — Volumes, Bind Mounts & tmpfs3 lessons

Storage & Volumes is exam domain 6 (10%) — the smallest weight, but every wrong choice corrupts data. The mount type, the storage driver, and the choice between volume plugins decide whether your database survives a container restart, a node reboot, or a Swarm rescheduling.

named-volume bind-mount tmpfs overlay2 btrfs zfs volume-plugins backup-restore
~3h
📖 Read in-depth chapter
Lesson 3.1 Three mount types — named volumes, bind mounts, tmpfs

Containers are ephemeral; anything you write to the writable layer dies with docker rm. Docker exposes three mount types to escape this — each with a different durability, portability, and security profile. The exam reliably asks "which mount type for which use case?".

Key concepts
  • Named volumes — Docker-managed, stored in /var/lib/docker/volumes/<name>/_data. Survive docker rm, portable across hosts via docker volume create, shareable between containers. The default choice for database data, app state, anything you need to keep.
  • Bind mounts — map an arbitrary host path into the container. Bypass Docker's volume management; tightly coupled to the host filesystem layout. Right for dev (live code reload), config files (/etc/nginx/conf.d), and host log forwarding; wrong for portable persistent data.
  • tmpfs mounts — Linux-only; backed by host RAM, never written to disk. Right for secrets you don't want to land on disk (session tokens, decrypted keys) and ephemeral caches you want gone on container stop.
  • --mount vs -v--mount is verbose but explicit (key=value, supports all options); -v is the legacy shorthand. Behavioural difference worth remembering: -v /host:/ct silently creates /host if missing; --mount type=bind,src=/host,dst=/ct errors out — which is usually what you want.
  • Read-only mounts — append :ro or readonly. Combine with --read-only root filesystem for hardened containers that can only write to declared volumes.
  • Anonymous volumes — created automatically by VOLUME in a Dockerfile or docker run -v /data. They live forever unless pruned. Always prefer named volumes for anything you want to find later.
Concrete example

Production Postgres with three mount types at once: docker run -d --name pg \ \n --mount type=volume,src=pg-data,dst=/var/lib/postgresql/data \ \n --mount type=bind,src=/etc/pg/postgresql.conf,dst=/etc/postgresql/postgresql.conf,readonly \ \n --mount type=tmpfs,dst=/tmp \ \n postgres:16. Named volume for durable data (survives rm/rebuild), read-only bind mount for the config file (host-managed, version-controlled, container can't modify it), tmpfs for /tmp so query temp files never hit disk. Three mounts, three responsibilities — that's the exam-correct Postgres.

Key takeaway: named volume for data you keep, read-only bind mount for config you control from the host, tmpfs for ephemeral or sensitive in-memory data. Prefer --mount over -v for production — explicit, self-documenting, and surfaces typos as errors instead of creating unexpected paths.
⚡ Mini-quiz
Drill mount-type-per-use-case scenarios → study mode (10 questions).
Lesson 3.2 Storage drivers — overlay2, btrfs, zfs, devicemapper

A storage driver is how Docker stacks image layers and the container's writable layer onto a single filesystem view. The choice usually defaults to overlay2 and you should leave it there — but the DCA exam still tests why, and what each alternative trades off.

Key concepts
  • overlay2 — the modern default on all major distros. Uses the kernel OverlayFS feature; very low overhead; supports page cache sharing between containers using the same image (RAM saving at scale).
  • btrfs / zfs — block-level Copy-on-Write filesystems. Snapshots and quotas come "for free" from the filesystem itself. Right when you already run the host on btrfs/zfs; not worth migrating to.
  • devicemapper — historical default on RHEL/CentOS without OverlayFS. Two modes: loop-lvm (terrible — never in production) and direct-lvm (acceptable). Being phased out; modern RHEL ships overlay2.
  • fuse-overlayfs — userspace OverlayFS, used by rootless Docker. Lower performance than kernel overlay2 but enables non-root install.
  • Migration cost — changing storage drivers wipes all local images, containers, and writable layers. Always configure on a fresh host; never on a populated one without taking docker save archives first.
  • Inspect what you havedocker info | grep -i "storage driver". The daemon refuses to start if the configured driver does not match the underlying filesystem.
Concrete example

Migrating a populated host from devicemapper loop-lvm to overlay2: (1) docker save every image you need to keep; (2) systemctl stop docker; (3) back up /var/lib/docker; (4) edit /etc/docker/daemon.json to set "storage-driver": "overlay2"; (5) rm -rf /var/lib/docker/* (the docs are not joking); (6) systemctl start docker + docker load your archives. This is destructive on purpose — Docker has no driver-to-driver layer translator, and the exam likes asking which step is "always" required (steps 5 + load).

Key takeaway: use overlay2; everything else exists for legacy or filesystem-specific reasons. Storage-driver changes wipe local state — back up images with docker save first. docker info is the single command that tells you which driver is active.
⚡ Mini-quiz
Drill storage-driver trade-offs → quick quiz (5 questions).
Lesson 3.3 Volume plugins, backups & production patterns

Local named volumes are perfect on a single host. The moment a container reschedules to another Swarm node, it loses its /var/lib/docker/volumes/.... Volume plugins solve this by backing volumes with networked storage (EBS, Ceph, NFS, GlusterFS, Portworx). The exam wants you to know the pattern and the right backup workflow.

Key concepts
  • Local driver — the default; volumes live on the local host's filesystem. Cheap and fast, but not multi-host.
  • Volume plugins — external drivers registered with Docker (docker plugin install vendor/plugin). Common: Portworx (block storage with snapshots), NetApp Trident, REX-Ray, NFS via local-persist, Ceph RBD via rexray-ceph.
  • NFS the easy waydocker volume create --driver local \ \n --opt type=nfs \ \n --opt o=addr=10.0.0.5,rw \ \n --opt device=:/exports/data \ \n shared-data. No plugin needed, kernel NFS client does the work.
  • Backup recipe — run a helper container that mounts both the volume and a host path, then tar: docker run --rm \ \n -v mydata:/from -v /backup:/to \ \n alpine tar czf /to/mydata-$(date +%F).tgz -C /from .. Idempotent, works for any volume, and is the canonical "how do I back up a volume?" exam answer.
  • Restore recipe — same pattern in reverse: mount the empty volume, mount the backup, tar xzf into it.
  • Volume labelsdocker volume create --label env=prod --label backup=daily mydata; docker volume ls --filter label=env=prod. Use labels so your cron + observability tooling can find volumes without hard-coded lists.
Concrete example

Production pattern for a Swarm-deployed Postgres replica: declare volumes: { pg-data: { driver: rexray/ebs, driver_opts: { size: 50, encrypted: true } } } in the compose / stack file. When Swarm reschedules the Postgres task to another node, the EBS volume is detached from the old node and attached to the new one — same bytes, new host. A nightly cron runs the tar czf backup recipe above against the (already-mounted) volume name and ships the archive to S3. The DCA exam phrases this as "how do you make persistent data follow a service across Swarm nodes?" — answer: a volume plugin that supports cross-node attachment.

Key takeaway: local volumes for single-host, volume plugins (EBS, Portworx, Ceph, NFS) for multi-host Swarm. Backups are a helper container that bind-mounts the volume and tars it — the same recipe for any volume, any driver. Always label volumes so automation can find them by purpose.
⚡ Mini-quiz
Drill volume-plugin + backup-recipe scenarios → study mode (10 questions).
04

Docker Networking — Bridge, Overlay, Host, Macvlan3 lessons

Networking is exam domain 4 (15%). The DCA exam tests which driver to pick for which problem, why containers on the default bridge cannot resolve each other by name, and how the Swarm overlay+ingress combo actually load-balances traffic across nodes. Three lessons cover drivers, service discovery, and multi-host overlays.

bridge host overlay macvlan embedded-dns port-publishing ingress vxlan
~4h
📖 Read in-depth chapter
Lesson 4.1 Network drivers — bridge, host, none, macvlan

Every container connects through a driver. The driver decides whether it gets its own IP, shares the host's network stack, is allowed to talk to anything, or appears on the LAN as a first-class device. Pick the wrong one and you either over-isolate ("why can't my app reach the DB?") or under-isolate ("why is my container's port open to the world?").

Key concepts
  • bridge (default for standalone containers) — a virtual L2 switch (docker0 or a custom bridge). Containers get a private IP, traffic to the host is NAT'ed. Default docker0 bridge has no embedded DNS; custom bridge networks do — that single difference is on every DCA exam.
  • host — Linux-only; the container shares the host's network namespace. No isolation, no port mapping needed (the container's port IS the host's port), highest performance. Use sparingly; a misconfigured nginx now binds 0.0.0.0:80 on the host.
  • none — only a loopback interface; the container is fully disconnected. Useful for batch jobs that should never see the network.
  • macvlan — assigns each container its own MAC address; the container appears as a physical device on the parent LAN. Needs promiscuous mode on the NIC and good parent-interface config. Right for legacy apps that expect direct LAN connectivity (broadcast, DHCP, …); wrong almost everywhere else.
  • overlay — multi-host VXLAN network used by Docker Swarm. Covered in Lesson 4.3.
  • Inspect everythingdocker network ls lists drivers; docker network inspect <net> shows subnet, gateway, IPAM, attached containers. Bookmark this command; it answers most "why can't X reach Y?" questions.
Concrete example

Side-by-side: docker network create app-net + docker run -d --name db --network app-net postgres:16 + docker run -it --network app-net alpine ping db — the ping works, because app-net is a custom bridge with embedded DNS. Drop the --network app-net arguments (so both containers run on docker0) and the same ping db fails with "bad address" — the default bridge has no DNS. This four-command demo is the answer to "why use a custom bridge?" and lives in roughly half of all DCA networking questions.

Key takeaway: always create a custom bridge network for multi-container apps — embedded DNS turns "what IP did the DB get today" into "ping db". Use host networking only when you accept zero isolation, macvlan only when an app needs LAN-level presence, none for fully offline jobs.
⚡ Mini-quiz
Drill driver-per-use-case scenarios → study mode (10 questions).
Lesson 4.2 Service discovery, embedded DNS & port publishing

Once a container has an IP, two questions follow: how does other containers find it (service discovery) and how does the outside world reach it (port publishing). Docker answers both — embedded DNS for inside, -p for outside — and the exam mixes the two on purpose.

Key concepts
  • Embedded DNS server127.0.0.11 inside every container on a custom bridge or overlay network. Resolves container names, service names (Swarm), and network aliases. Forwards external lookups to the host's resolvers.
  • Container aliasesdocker run --network app-net --network-alias api myapi makes the container reachable as both myapi and api. In compose: networks: app-net: aliases: [api].
  • Port publishing forms-p 8080:80 (host 8080 → ct 80, TCP, all interfaces), -p 127.0.0.1:8080:80 (loopback only, the secure default), -p 53:53/udp (explicit UDP), -P (publish every EXPOSEd port on random host ports).
  • EXPOSE ≠ publishEXPOSE in a Dockerfile is metadata; it does not open the port to the host. Only -p / -P / Swarm --publish creates the port mapping.
  • NAT path — bridge port publishing inserts iptables NAT rules. iptables -t nat -L DOCKER -n on the host shows them; that's where firewalling lives.
  • userland-proxy — fallback proxy used for some loopback scenarios. Disabled in daemon.json with "userland-proxy": false for performance; rarely needed to know unless an exam question asks why iptables NAT is missing.
Concrete example

Two-tier app on one host: docker network create webnet + docker run -d --name db --network webnet postgres:16 + docker run -d --name api --network webnet --network-alias backend -e DB_HOST=db myapi + docker run -d --name web --network webnet -p 127.0.0.1:8080:80 --link api:backend nginx-proxy. The frontend reaches backend by DNS alias, the API reaches db by name, the host exposes only 127.0.0.1:8080 — a reverse proxy or systemd-managed TLS proxy then publishes 443 to the world. The DCA exam will mix this up by hiding the missing -p or the missing --network — read the question slowly.

Key takeaway: EXPOSE documents, -p publishes. Embedded DNS is on every custom bridge / overlay network and resolves container names + aliases. Bind publishes to 127.0.0.1 by default and let a dedicated proxy handle public ports — that single discipline closes a class of accidental-public-exposure bugs the exam loves.
⚡ Mini-quiz
Drill DNS + port-publishing scenarios → study mode (10 questions).
Lesson 4.3 Multi-host overlays, VXLAN & the Swarm routing mesh

Single-host bridges stop at the host's NIC. The moment two Swarm nodes need a service to talk across them, Docker uses an overlay network: a VXLAN-encapsulated virtual L2 fabric that spans every participating node. The routing mesh and ingress overlay are how Swarm load-balances published ports across the cluster.

Key concepts
  • VXLAN encapsulation — overlay traffic is wrapped in a UDP/4789 packet between nodes. Each Swarm overlay network has its own VXLAN ID; the data plane runs entirely inside the kernel.
  • Required ports — TCP 2377 (Swarm management, Raft), TCP+UDP 7946 (node discovery / gossip), UDP 4789 (VXLAN data plane). Open these on every node firewall.
  • Encryption — overlay networks support --opt encrypted for IPsec on the VXLAN tunnels. The ingress overlay network is encrypted by default.
  • ingress network — the special overlay network Swarm creates on init. Every published service port goes through it; every node accepts the published port even if no replica runs there (routing mesh).
  • Routing mesh — when a request hits any node on a published port, the kernel IPVS rules load-balance it across all healthy task replicas, anywhere in the cluster. The hop is transparent to the client.
  • Host mode publishing--publish mode=host,target=80,published=80 bypasses the routing mesh; the port is bound only on the node running the task. Useful for performance-sensitive workloads (no extra hop, no NAT) and for protocols that don't survive load balancing.
  • Service mesh DNS — inside an overlay, the embedded DNS resolves tasks.<svc> to the individual task IPs and <svc> to the service VIP. Picking the right one matters for stateful protocols.
Concrete example

A 3-node Swarm cluster running a frontend service replicated 6× and a single Postgres task. docker network create --driver overlay --opt encrypted app-overlay; docker service create --name web --replicas 6 --network app-overlay --publish 80:8080 web:1. Any client hitting node1:80 is load-balanced by IPVS to one of the six task replicas — possibly on node3. The Postgres task reaches the web service via the overlay's web VIP; if it needs sticky-per-replica connections it queries tasks.web instead, picks one IP, and bypasses the VIP. The exam phrases this as "which DNS name returns multiple A records?".

Key takeaway: overlay networks are VXLAN over UDP/4789, gossip on 7946, control plane on TCP/2377 — open all three between Swarm nodes. The ingress network + routing mesh is Swarm's built-in L4 load balancer; mode=host publishing opts out. Inside the overlay, <svc> resolves to a VIP, tasks.<svc> to each task IP.
⚡ Mini-quiz
Drill overlay + routing-mesh scenarios → study mode (10 questions).
05

Docker Swarm — Orchestration at Scale3 lessons

Orchestration is exam domain 1 (25%) — the single heaviest domain on the DCA. Three lessons cover the Raft-based control plane (init/join, quorum, manager promotion), the service / replica / rolling-update lifecycle, and the Swarm secrets + configs + ingress patterns. If you only have time for one module, double-down here.

swarm-init raft manager-quorum services replicas rolling-update secrets configs
~5h
📖 Read in-depth chapter
Lesson 5.1 Swarm architecture — managers, workers, Raft quorum

Swarm is Docker's built-in orchestrator. A Swarm cluster has two role types — manager and worker — and the manager group runs Raft consensus to keep cluster state consistent. The DCA exam loves the quorum math (how many manager failures can you tolerate?) and the join-token model.

Key concepts
  • Managers — accept CLI commands, schedule tasks, run the Raft log. Always an odd count for clean quorum: 1 (lab), 3 (most prod), 5 (HA), 7 (rare). Adding more does not increase performance; it just adds Raft write latency.
  • Workers — execute tasks; report back to the leader manager. No state, can be scaled freely.
  • Quorum formula — a Swarm with N managers tolerates ⌊(N-1)/2⌋ failures. 3 managers → tolerate 1. 5 → tolerate 2. 7 → tolerate 3. Lose quorum and the cluster is read-only (running tasks keep running; you cannot schedule new ones).
  • Two join tokensdocker swarm join-token worker + docker swarm join-token manager. Each is a long string starting with SWMTKN-1-…. Rotate with --rotate if a token leaks.
  • Auto-lockdocker swarm init --autolock encrypts the Raft logs on disk; managers need an unlock key to restart. Right for high-security environments.
  • Node managementdocker node ls / inspect / promote / demote / update --availability {active|pause|drain}. Drain a node before maintenance; tasks reschedule elsewhere.
  • Leave / remove — workers can docker swarm leave at will; managers must demote themselves or be force-removed. Always remove the node from manager state with docker node rm after it leaves.
Concrete example

Bootstrap a 3-manager / 4-worker Swarm: on the first manager, docker swarm init --advertise-addr 10.0.0.10 (prints two join commands and a worker token); docker swarm join-token manager on the same host to get the manager token; on managers 2 + 3, run the printed manager join command; on the four workers, run the worker join command. Verify with docker node ls — you should see three nodes with MANAGER STATUS = Reachable/Leader and four with empty manager status. This cluster tolerates one manager failure (formula: ⌊(3-1)/2⌋ = 1) — the canonical DCA quorum-math question.

Key takeaway: odd number of managers, quorum tolerance is ⌊(N-1)/2⌋, two join tokens (worker + manager). Drain nodes for maintenance; remove them with docker node rm after they leave. Auto-lock encrypts the Raft store on disk for high-security clusters.
⚡ Mini-quiz
Drill Swarm-architecture + quorum-math scenarios → study mode (10 questions).
Lesson 5.2 Services, replicas, scaling & rolling updates

Once the cluster is up, you stop thinking in containers and start thinking in services: a declarative spec (image + replicas + ports + constraints) that Swarm enforces. The DCA exam tests the rolling-update flags hardest — parallelism, delay, failure-action, monitor — because that is where bad deploys cause real outages.

Key concepts
  • Service — desired state. docker service create --name web --replicas 5 --publish 80:8080 nginx:1.27. Swarm schedules five tasks, restarts any that die, reschedules any whose node leaves.
  • Task — one container running on one node. The atomic unit of scheduling. docker service ps web lists every task with current state, desired state, and node.
  • Replicated vs Global--mode replicated (default; N tasks total) vs --mode global (exactly one task per eligible node, perfect for monitoring agents or log collectors).
  • Scalingdocker service scale web=10 or docker service update --replicas 10 web. Either form schedules five new tasks immediately.
  • Rolling update flags--update-parallelism N (tasks updated simultaneously), --update-delay 10s (wait between batches), --update-failure-action {pause|continue|rollback}, --update-monitor 30s (post-update grace period). Default failure-action is pause — silent stops in production unless you set rollback.
  • Health-driven updates — combine with a Dockerfile HEALTHCHECK or a service-level --health-cmd. Swarm only marks a new task "running" after it passes a health probe; otherwise rollback / pause kicks in.
  • Placement constraints--constraint node.role==worker, --constraint node.labels.zone==eu-west-1. Label managers + workers with docker node update --label-add.
  • Rollbackdocker service rollback web returns to the previous service spec. Swarm keeps the previous spec, not history — only one rollback step is available.
Concrete example

Production-safe rolling update of an HTTP API from v1.4 to v1.5: docker service update \ \n --image registry/api:1.5 \ \n --update-parallelism 2 \ \n --update-delay 20s \ \n --update-failure-action rollback \ \n --update-monitor 30s \ \n --update-order start-first \ \n api. Swarm updates two tasks at a time, waits 20s between batches; if a new task fails its healthcheck within 30s of starting, the entire service rolls back to v1.4 automatically. start-first starts the new task before stopping the old one — zero capacity dip during deploys. Memorise these five flags; they show up in basically every DCA Swarm question.

Key takeaway: services declare desired state, tasks are the unit of scheduling. Rolling updates have five flags — parallelism, delay, failure-action, monitor, order — and the safe defaults are 2 / 20s / rollback / 30s / start-first. docker service rollback reverts one step; pair it with healthchecks for automatic safety.
⚡ Mini-quiz
Drill service + rolling-update scenarios → study mode (10 questions).
Lesson 5.3 Secrets, configs, stacks & the ingress mesh

Production Swarm deploys live in compose files (docker stack deploy) and lean on first-class Secrets + Configs to keep credentials out of images. The DCA exam tests three things tightly: where secrets are mounted, how configs differ from secrets, and how routing-mesh vs host-mode publishing route traffic.

Key concepts
  • Secrets — encrypted in the Raft log at rest, decrypted only on the nodes that run tasks needing them, mounted read-only into the container at /run/secrets/<name>. Never appear in docker inspect output. Maximum size: 500 KB.
  • Configs — same delivery model as secrets but for non-sensitive content (nginx.conf, prometheus.yml). Stored unencrypted in Raft; mounted at any path you choose with --config target=….
  • Create from filedocker secret create db_pw ./db_pw.txt or pipe stdin printf %s "$PW" | docker secret create db_pw -. Same syntax for configs.
  • Immutability — secrets and configs are immutable. To change one: create a new _v2 secret, run docker service update --secret-rm db_pw --secret-add source=db_pw_v2,target=db_pw, delete the old. The exam loves asking about this rotation pattern.
  • Stack files — compose v3+ YAML with deploy:, secrets:, configs: top-level keys. docker stack deploy -c app.yml mystack creates one stack with services prefixed mystack_<svc>.
  • Publish modes--publish mode=ingress,target=80,published=8080 (default; routing-mesh load balancing) vs --publish mode=host,target=80,published=8080 (bind directly on each task's node, bypass mesh). Host mode is required when the upstream LB does the load balancing or when the protocol can't traverse IPVS NAT.
  • Inspecting trafficdocker service inspect --pretty <svc> shows endpoint mode + published ports; iptables -t mangle -L on a node shows the IPVS magic that routes incoming connections.
Concrete example

A production stack: secrets: \n pg_pw: { external: true } \nservices: \n api: \n image: registry/api:1.5 \n secrets: [pg_pw] \n deploy: { replicas: 4, update_config: { parallelism: 2, failure_action: rollback }, restart_policy: { condition: on-failure } } \n ports: [ "target: 80, published: 80, mode: ingress" ]. The API reads /run/secrets/pg_pw at startup, ports 80 on every node route to the four replicas via the ingress mesh, rolling updates batch two at a time with auto-rollback. Rotating the password is a three-line change — create pg_pw_v2, service-update --secret-rm pg_pw --secret-add source=pg_pw_v2,target=pg_pw, prune the old. No image rebuild, no env-file leak, no downtime.

Key takeaway: secrets are encrypted at rest + mounted at /run/secrets/<name>; configs are the unencrypted twin for non-sensitive files. Both are immutable — rotate by creating _v2 and swapping with --secret-rm + --secret-add. mode=ingress uses the routing mesh, mode=host bypasses it.
⚡ Mini-quiz
Drill secrets + stack-file + publish-mode scenarios → study mode (10 questions).
06

Container Security & Production Hardening3 lessons

Security is exam domain 5 (15%) and the question pool that punishes shortcuts. Three lessons cover the kernel security primitives (capabilities, seccomp, AppArmor, userns-remap), image security (Content Trust, signing, CVE scanning, distroless), and runtime hardening (non-root user, read-only root, no-new-privileges, resource limits).

capabilities seccomp apparmor userns-remap content-trust cosign trivy no-new-privileges
~5h
📖 Read in-depth chapter
Lesson 6.1 Linux kernel primitives — capabilities, seccomp, AppArmor, userns-remap

Docker security is not a Docker feature — it is the kernel's. Capabilities slice up root power, seccomp filters syscalls, AppArmor/SELinux enforce mandatory access control, userns-remap maps in-container root to an unprivileged host UID. Knowing which layer addresses which threat is exactly what the DCA exam tests.

Key concepts
  • Capabilities — Linux divides root power into ~40 capabilities. Docker drops most by default and keeps a minimal set (NET_BIND_SERVICE, CHOWN, DAC_OVERRIDE, …). Modify with --cap-drop ALL --cap-add NET_BIND_SERVICE; never use --privileged in production (it grants everything).
  • seccomp — kernel-level syscall filter. Docker ships a default profile that blocks ~44 dangerous syscalls (keyctl, ptrace from another PID namespace, …). Apply a custom profile with --security-opt seccomp=/path/profile.json; unconfined disables filtering — never do this.
  • AppArmor / SELinux — mandatory access control. Docker applies the docker-default AppArmor profile on Debian/Ubuntu; SELinux is enabled per-container on RHEL with --security-opt label=....
  • User namespace remapping"userns-remap": "default" in daemon.json maps in-container UID 0 (root) to an unprivileged host UID (e.g. 100000). A container breakout lands on an unprivileged shell, not on host root.
  • no-new-privileges--security-opt no-new-privileges:true sets the kernel no_new_privs bit, blocking setuid/setgid escalation inside the container. Cheap and almost always safe.
  • Read-only root + tmpfs--read-only + --mount type=tmpfs,dst=/tmp makes the rootfs immutable; combine with capability drops for a defence-in-depth runtime.
Concrete example

Hardened production run of an HTTP service: docker run -d \ \n --read-only \ \n --tmpfs /tmp:rw,noexec,nosuid \ \n --cap-drop ALL --cap-add NET_BIND_SERVICE \ \n --security-opt no-new-privileges:true \ \n --security-opt seccomp=default.json \ \n --user 10001:10001 \ \n --memory 512m --cpus 1 \ \n -p 127.0.0.1:8080:80 \ \n myapp:1.0. Drop all capabilities, allow only the one needed to bind port 80, read-only root with tmpfs scratch, syscall filtering, no privilege escalation, non-root UID, resource limits, loopback-only publishing. This eight-flag command is the answer to half of the DCA's "harden this container" questions.

Key takeaway: security layers compose — namespaces (isolation) → cgroups (resource limits) → capabilities (privilege slicing) → seccomp (syscall filter) → AppArmor/SELinux (MAC) → userns-remap (host UID mapping). Default-drop, allow-list, never --privileged.
⚡ Mini-quiz
Drill capabilities + seccomp + userns scenarios → study mode (10 questions).
Lesson 6.2 Image security — Content Trust, signing & CVE scanning

Half of every container security incident traces back to the image: a vulnerable base layer, an unsigned third-party build, or a leaked secret in a layer no one inspected. Three controls — Docker Content Trust, image signing, and CVE scanning — defend the supply chain end-to-end, and the DCA exam tests each.

Key concepts
  • Docker Content Trust (DCT)export DOCKER_CONTENT_TRUST=1 tells the CLI to refuse to push or pull unsigned images. Backed by Notary v1 / TUF (The Update Framework). Per-publisher keys live under ~/.docker/trust/.
  • Cosign / Sigstore — modern signing layered on top of OCI registries. Sign with cosign sign $IMG, verify with cosign verify $IMG --certificate-identity-regexp …. Keyless flow uses OIDC + Sigstore's transparency log (Rekor) — no long-lived signing keys.
  • CVE scanningdocker scout cves myapp:1.0, trivy image myapp:1.0, grype myapp:1.0 all walk the image's package metadata and match against vulnerability databases. Run in CI on every push; fail the build on high / critical findings.
  • Minimal base imagesdistroless, alpine, scratch. Fewer packages = fewer CVEs = faster scans. Distroless ships no shell, which alone defeats a large class of post-exploitation toolkits.
  • SBOM — Software Bill of Materials in SPDX or CycloneDX. syft myapp:1.0 -o spdx-json > sbom.json + cosign attest --predicate sbom.json --type spdxjson myapp:1.0 attaches the SBOM as a signed attestation.
  • Layer hygiene — never ADD secrets in any layer; even if the next layer deletes them, the previous layer remains in the image. Use BuildKit secret mounts (--mount=type=secret,id=npm) so secrets never touch a persisted layer.
Concrete example

Supply-chain-secure CI: docker buildx build --push -t ghcr.io/team/api:1.5 . + cosign sign --yes ghcr.io/team/api:1.5 + trivy image --severity HIGH,CRITICAL --exit-code 1 ghcr.io/team/api:1.5 + syft ghcr.io/team/api:1.5 -o spdx-json > sbom.json && cosign attest --predicate sbom.json --type spdxjson ghcr.io/team/api:1.5. On deploy, an admission webhook on the cluster runs cosign verify and refuses any image without a valid signature and a clean trivy attestation. This four-command pipeline is the answer to "how do you stop unsigned or vulnerable images from reaching production?" — the exam's favourite supply-chain question.

Key takeaway: sign every image (DCT or Cosign), scan every image (Trivy / Scout / Grype) on every push, ship an SBOM as a signed attestation, and verify both at deploy time. Use minimal base images so the attack surface and CVE list stay small.
⚡ Mini-quiz
Drill signing + scanning + SBOM scenarios → study mode (10 questions).
Lesson 6.3 Runtime hardening & production checklist

The last layer is operational: how you run a container, what limits you set, how you watch it. The DCA exam frames this as "given this docker run command, what is missing for production?" — the answer is almost always a subset of the eight flags below plus a logging / monitoring story.

Key concepts
  • Non-root userUSER 10001:10001 in the Dockerfile or --user 10001 at run time. Most app frameworks do not need root at all.
  • Read-only rootfs + tmpfs--read-only + --mount type=tmpfs,dst=/tmp for scratch space. Immutable rootfs kills a class of malware persistence.
  • Resource limits--memory 512m --memory-swap 512m --cpus 1.5 --pids-limit 200. Without these, a single buggy container can OOM-kill the host.
  • Restart policies--restart on-failure:5 (retry up to 5 times) or --restart unless-stopped (always restart unless explicitly stopped). Pair with a healthcheck.
  • Logging discipline — set --log-driver json-file --log-opt max-size=10m --log-opt max-file=3 per container (or globally in daemon.json) so logs don't fill the disk.
  • Drop the socket — never mount /var/run/docker.sock into a container unless you have decided that container is allowed to be root on the host (which is what socket access means). Use a sidecar with the API exposed over TCP+TLS+auth if you really need it.
  • Observabilitydocker stats, cAdvisor, Prometheus node-exporter + cadvisor scrapes, Falco for runtime anomaly detection. Every prod cluster needs at least metrics + alerting.
  • Patch cadence — pin base images by digest, run a weekly job that bumps the digest if a newer one exists, runs the CI scan, and opens a PR. Treat container patching like OS patching.
Concrete example

Final hardened service definition that puts every Module 6 lesson together: services: \n api: \n image: ghcr.io/team/api@sha256:<digest> \n user: "10001:10001" \n read_only: true \n tmpfs: [ /tmp ] \n cap_drop: [ ALL ] \n cap_add: [ NET_BIND_SERVICE ] \n security_opt: [ "no-new-privileges:true", "seccomp=default.json" ] \n deploy: \n replicas: 4 \n resources: { limits: { cpus: '1.0', memory: 512M } } \n update_config: { parallelism: 2, delay: 20s, failure_action: rollback } \n healthcheck: \n test: [ "CMD", "curl", "-f", "http://localhost:80/healthz" ] \n interval: 10s \n retries: 3 \n logging: { driver: json-file, options: { max-size: "10m", max-file: "3" } } \n secrets: [ db_pw ]. Digest pin, non-root user, immutable rootfs, capability drop, no privilege escalation, seccomp, replicas + rolling updates with auto-rollback, healthcheck-driven readiness, log rotation, secrets out of env. A DCA "production-ready stack" answer in 15 lines.

Key takeaway: production hardening is a checklist — digest pin, non-root user, read-only root + tmpfs, drop capabilities, no-new-privileges, seccomp, resource limits, healthcheck, log rotation, secrets via Swarm Secrets (not env). Never mount the Docker socket. Patch images on a cadence — pinned digests + scheduled re-scan.
⚡ Mini-quiz
Drill runtime-hardening + checklist scenarios → study mode (10 questions).
Start practicing → 🗺️ Open Cert Quest path