Chapter 9 · Part III · Containers, orchestration, and the edge
Containers and Docker
Maya, a new CampusCart developer, receives a bug report on Monday morning. The bookstore application formats a receipt correctly on her macOS laptop but fails in the Linux test environment because a native image library is missing. Registra
Maya, a new CampusCart developer, receives a bug report on Monday morning. The bookstore application formats a receipt correctly on her macOS laptop but fails in the Linux test environment because a native image library is missing. Registration week starts in four days. Maya needs a repeatable way to move the application code, its language runtime, and its operating-system libraries from a developer machine to a cloud machine.
Maya creates a Dockerfile in the CampusCart source repository. A continuous-integration runner reads that file, downloads a named base image, installs locked dependencies, copies the application, and writes a content-addressed image to a registry. A test machine downloads the same image and starts a container. The receipt now has the same native library in both environments, although the macOS laptop and Linux test machine still use different kernels.
The incident gives Maya concrete objects to inspect: a Dockerfile produced by a developer, image layers produced by the builder, a manifest stored by the registry, a Linux process started by the container runtime, and log bytes written by that process. Containers solve a packaging and execution problem. Containers do not create a separate computer, do not preserve data automatically, and do not remove the need to understand networking or operating-system limits.
Prerequisites and earlier lessons
- Chapter 3, Networking for Cloud Applications, supplies Internet Protocol addresses, ports, Domain Name System resolution, and request paths.
- Chapter 4, Virtualization and Cloud Service Models, distinguishes a virtual machine from a physical host.
- The reader should understand a process, a filesystem, an environment variable, and a software dependency.
Learning outcomes
After completing the chapter, the reader can distinguish an image from a container, explain how Linux namespaces and control groups affect a containerized process, predict which bytes survive container replacement, inspect image and runtime evidence, and decide when a container is a useful deployment unit. The reader can also explain why a container is not a security boundary equivalent to a separate physical machine.
Observable evidence
The command docker image inspect campuscart-api:1.0 asks the Docker Engine daemon for image metadata. The daemon returns JavaScript Object Notation (JSON) fields that name the image identifier, architecture, operating system, configuration, and ordered root-filesystem layers. A registry and a local daemon identify image content by cryptographic digest. Matching digests directly establish matching content; a shared tag such as latest does not establish matching content because an operator can move the tag.
The command docker container inspect campuscart-api asks the daemon for a running container description. Fields such as State.Pid, NetworkSettings.Networks, Mounts, and Config.Image report a host process identifier, network attachment, mounted storage, and source image. The Docker daemon produces the description from runtime metadata. The description establishes the daemon's configured view. A separate command such as ps on the Linux host can confirm that the named process exists.
The command docker logs campuscart-api retrieves bytes captured by the configured logging driver from the container's standard output and standard error streams. The application produces those bytes. Docker transports or stores them. A line with Hypertext Transfer Protocol (HTTP) status 500 establishes that the application wrote that line; the line alone does not prove which request failed unless the application included a request identifier and the operator correlates the identifier with other evidence.
Mechanism and terminology in context
An image is an immutable, ordered description of filesystem layers plus execution configuration. A container is a running or stopped instance created from an image. The distinction matters because a registry stores images while a runtime creates containers. Replacing a container can preserve the image and remove the container's writable layer.
On Linux, the runtime asks the kernel to create namespaces for selected views of operating-system objects. A process identifier namespace gives the containerized program a process-number view. A mount namespace gives the program a filesystem-mount view. A network namespace gives the program network interfaces, routes, and ports that differ from the host view. Namespaces change visibility; namespaces do not emulate a new kernel.
Linux control groups, commonly written as cgroups, account for and constrain processor time, memory, and other quantities for groups of processes. A memory limit can cause an out-of-memory termination when the CampusCart process requests more memory than the cgroup permits. A processor quota can cause the kernel scheduler to withhold processor time after the process consumes its allowed share. The application still competes for the physical host's processors and memory buses.
Image layers use a content-addressed, stackable representation. Docker storage drivers present read-only image layers and a thin writable container layer as one filesystem view. A modification to an existing image file can cause a copy-on-write operation: the storage driver copies the file into the writable layer before changing the copy. A volume mounts storage outside the disposable writable layer. Therefore, a PostgreSQL database that writes durable rows belongs on managed database storage or a deliberately managed volume, not only in the container layer.
Container networking commonly attaches a network namespace to a virtual bridge. The host forwards packets between a virtual interface and the outside network, sometimes translating addresses. Publishing host port 8080 to container port 3000 creates a forwarding rule; the application still listens on port 3000 inside its network namespace. A cloud load balancer may send packets to the host or directly to a container-aware target, depending on the platform.
Docker is a product and toolchain that builds, distributes, and runs containers. The Open Container Initiative (OCI) publishes image and runtime specifications. A Kubernetes kubelet calls a runtime service through version 1 of the Container Runtime Interface (CRI); runtime services such as containerd or CRI-O can then use OCI image and runtime conventions. OCI compatibility alone does not implement the CRI. Kubernetes therefore does not require Docker Engine on each node. Product names and interfaces must remain separate from the underlying image and process mechanisms.
Working model: startup transfer time
Suppose the CampusCart image contains five compressed layers. A target node already has four layer digests and lacks a 180-megabyte application layer. The registry-to-node path sustains 60 megabytes per second, and decompression plus filesystem preparation consumes 1.4 seconds. A first approximation is:
startup preparation time = missing compressed bytes / transfer throughput + local preparation time
The predicted preparation time is 180 MB / 60 MB/s + 1.4 s = 4.4 s. The numerator counts only missing compressed bytes because matching layer digests allow reuse. The denominator measures completed registry bytes divided by transfer seconds. The model assumes stable throughput, no registry throttling, enough disk space, and no concurrent pulls. The model omits Domain Name System lookup, Transport Layer Security negotiation, image signature checks, queueing, and application initialization. The omitted lookup, negotiation, verification, queueing, and initialization times matter when the image is small or the registry path is congested.
Layer reuse changes the prediction. A careless Dockerfile that installs dependencies after copying frequently changed source can invalidate a large layer on every build. Moving locked dependency installation before source copy can keep the dependency layer reusable. Layer order affects transfer work, but excessive layering can add metadata and make builds harder to understand.
Empirical test
Build two images from the same CampusCart commit. Image A copies source before installing dependencies. Image B installs dependencies from the lockfile before copying source. Prepare a test node whose cache contains only the recorded base and dependency layer digests. Change one source comment without changing dependencies, rebuild both images, remove the rebuilt image reference without pruning cached content, and pull again. Keep the recorded cache baseline, base image, registry, network path, and node constant.
Record docker history, image digests, bytes transferred, and elapsed pull time for each rebuild. The mechanism predicts that Image B transfers fewer bytes because the dependency layer digest remains present. A result with identical missing bytes would reject the proposed cache benefit for this build, perhaps because the build tool included changing metadata or because both dependency layers were invalidated. Network congestion and registry-side compression are alternative explanations for elapsed-time differences, so transferred bytes provide stronger evidence than time alone.
Worked investigation
Initial question. Why did one of four CampusCart application containers lose uploaded product images after a restart?
Operating conditions. An Amazon Elastic Container Service (Amazon ECS) service runs four tasks from image digest sha256:7a… behind one load balancer. Each task contains one application container that receives product-image uploads at /app/uploads. One container exceeded a 512-megabyte memory limit, and the ECS service replaced its stopped task at 14:07 Coordinated Universal Time. Object storage is configured for catalog thumbnails but not for raw uploads.
Evidence collected. The Amazon ECS control plane produces a service event and stopped-task description that report the terminated task and its replacement. A node diagnostic collector preserved docker inspect output for the stopped container before the cleanup policy removed its metadata; the preserved output lists no volume at /app/uploads. The image manifest contains no uploaded files. Load-balancer access logs show three successful uploads routed to the old container before 14:07. The replacement container returns HTTP status 404 for those paths.
Interpretation. Direct observations establish a replacement and the absence of an external mount. The image manifest establishes the original immutable content, not later writes. The bounded inference is that uploads entered the old container's writable layer and disappeared when that layer was removed. The evidence does not establish whether a backup copied the files elsewhere.
Proposed intervention and prediction. Change the upload path so the application writes each object to versioned object storage, records the returned object key in PostgreSQL, and reports success only after both operations complete or a compensating action is queued. A replacement container should then read the same object by key.
Observed result. A staging run uploads 100 known files, forcibly removes every application container, and retrieves all 100 files through replacement containers. Object-storage audit logs contain 100 successful writes and 100 later reads. Checksums before and after replacement match.
Bounded conclusion and uncertainty. External object storage removed dependence on the container writable layer under the tested replacement conditions. The experiment does not prove cross-region durability, correct authorization, or recovery from a database commit that succeeds after an object write fails. A later transaction-design exercise must test those cases.
Normal case
CampusCart builds one image per reviewed commit, pins deployment manifests to a digest, injects environment-specific configuration at startup, writes request logs to standard output, and stores durable data in PostgreSQL and object storage. Several containers use the same image digest. Each container can be removed without losing application data. Health checks prevent the load balancer from routing requests before the application is ready.
Failure and edge cases
A container can pass a process-alive check while returning incorrect answers because a liveness check establishes only narrow evidence. A read-only root filesystem can break software that assumes /tmp or a home directory is writable. A container built for one processor architecture may not execute on another architecture without a matching image manifest or emulation. A root user inside a container can retain dangerous kernel capabilities. A tag can move between digests. A secret copied during an image build can remain recoverable from a layer even after a later layer deletes the file.
Important qualifications
Containers improve environmental consistency only for content actually captured by the image and declared configuration. The host kernel, processor architecture, runtime, attached storage, and network policy remain external dependencies. Reproducible builds require pinned inputs and a controlled build procedure. Image scanning reports known findings under a scanner's database and configuration; a clean report does not prove absence of vulnerable or malicious code.
Isolation strength depends on kernel configuration, runtime settings, capabilities, seccomp filters, mandatory access controls, and workload behavior. A virtual machine generally provides a separate guest kernel and a different isolation boundary. Neither boundary removes the need for least privilege or patching.
Common errors
“A container is a small virtual machine.” The reasoning fails because the containerized process normally uses the host kernel. An image supplies user-space files, not a guest kernel.
“The image contains the running application data.” The reasoning confuses immutable image layers with a container's writable layer and external mounts. Inspect each mount and write path.
“The same tag means the same release.” A tag is a mutable name. A digest identifies content.
“A low processor limit gives a process a slower processor.” A quota usually changes how much scheduler time the process receives during an interval. The physical processor does not necessarily change clock frequency.
Knowledge check
Question: A container writes 20 megabytes to /var/cache/app, has no volume at that path, and is then removed. A new container starts from the same image digest. Which bytes are guaranteed to appear in the new container?
Answer: Only bytes present in the immutable image layers are guaranteed by the stated conditions. The removed container's 20-megabyte writable-layer change is not part of the image and is not preserved by a volume.
Exercises
Check. Run a disposable container, record its image digest and host process identifier, write a named file into the writable layer, and remove the container. Start a new container from the digest. Submit the commands, relevant inspection fields, and evidence about the file.
Practice. Build the two Dockerfile orders from the empirical test. Submit both Dockerfiles, docker history output, changed-layer digests, bytes transferred, and a short causal explanation. Keep the base image and dependency lockfile constant.
Challenge. Draw CampusCart's browser-to-container packet path for a local Docker bridge and for a cloud load balancer. Name both sides of every address translation and port mapping. Submit the diagrams plus packet or connection evidence from curl, ss, and an appropriate capture tool. Remove credentials and personal data.
Authoritative sources and further reading
Required reading
- Docker overview supplies the official image, container, daemon, registry, namespace, and execution walkthrough.
- Open Container Initiative Image Format Specification defines interoperable image manifests and layers.
Implementation documentation
- Docker storage drivers explains writable layers and copy-on-write behavior; implementation details depend on the selected driver.
- Docker storage distinguishes container layers, volumes, bind mounts, and temporary mounts.
- Kubernetes Container Runtime Interface defines the kubelet-to-runtime interface and current v1 requirement.
Primary standards and kernel reference
- Open Container Initiative Runtime Specification defines the runtime configuration and lifecycle model.
- Linux kernel control group v2 documentation is the primary kernel reference for resource distribution and accounting.
Advanced and version-sensitive material
Runtime socket configuration, image-store implementation, cgroup mode, and available isolation controls vary by operating system, Docker release, Kubernetes release, and runtime service. Record exact versions in exercise evidence.
Transition
A container packages one program and its user-space dependencies. Production applications require repeated placement, health evaluation, network identity, updates, and recovery across many containers and machines. Chapter 10 examines how Kubernetes represents desired workload conditions and continuously reconciles observed cluster conditions toward them.