Lesson 80 of 129
Containers and Docker
A container is an ordinary Linux process (or group of processes) that the kernel runs with a restricted view of the system and a limited share of its resources. A container has no separate kernel. Isolation is provided by kernel features, and the packaged filesystem is provided by an image. This gives microservices a uniform unit of deployment: the same artifact runs on a laptop, in CI, and in production.
The problem containers solve
A service depends on a language runtime, system libraries, and configuration. Installing these directly on shared hosts causes version conflicts between services and differences between environments. A container bundles the service with its user-space dependencies, so the host needs only a kernel and a container runtime.
Isolation: namespaces
A Linux namespace limits what a process can see. Each kind of namespace virtualizes one system resource.
| Namespace | Isolates |
|---|---|
pid |
Process IDs. The first process in the container sees itself as PID 1. |
net |
Network interfaces, IP addresses, routing tables, ports. |
mnt |
Mount points, so the container sees its own root filesystem. |
uts |
Hostname and domain name. |
ipc |
System V IPC and POSIX message queues. |
user |
User and group IDs. Root inside the container can map to an unprivileged user on the host. |
cgroup |
The view of the cgroup hierarchy. |
A container runtime creates a new set of namespaces for each container and starts the process inside them.
Resource limits: cgroups
Control groups (cgroups) limit and account for the resources a set of processes uses.
- CPU: a share weight relative to other groups, or a hard quota per scheduling period. Exceeding a quota causes throttling.
- Memory: a hard limit. If processes in the group exceed it and the kernel cannot reclaim memory, the OOM killer terminates a process in the group.
- I/O and process count: limits on block I/O bandwidth and on the number of processes.
Namespaces control visibility. cgroups control consumption. Neither is a security boundary by itself, so runtimes add further restrictions: dropped Linux capabilities, seccomp syscall filters, and AppArmor or SELinux profiles.
Images and layers
An image is a read-only template containing a filesystem and metadata (default command, environment variables, exposed ports). In the OCI format an image consists of a manifest, a configuration, and an ordered list of layers. Each layer is a set of filesystem changes, stored as a compressed archive and identified by a content hash.
At run time, a union filesystem (commonly OverlayFS) stacks the read-only layers and adds a thin writable layer on top. Writes go to the writable layer using copy-on-write. When the container is removed, that layer is discarded.
graph TB
W["Writable container layer (per container)"]
L3["Layer 3: COPY app binary"]
L2["Layer 2: RUN install dependencies"]
L1["Layer 1: base image (OS user space)"]
W --> L3 --> L2 --> L1Properties of layering:
- Layers are shared. Ten containers from one image use one copy of the read-only layers on disk.
- Layers are cached. Unchanged layers are not rebuilt or re-downloaded.
- Layers are immutable. Deleting a file in a later layer hides it but does not shrink earlier layers.
An image is referenced by a tag (a mutable name such as orders:1.4.2) or by a digest (a content hash such as orders@sha256:...). A digest always refers to the same bytes. A tag can be moved to another image.
Dockerfile
A Dockerfile is a text file of instructions that builds an image. RUN, COPY, and ADD add filesystem layers. Other instructions, such as ENV, USER, EXPOSE, and ENTRYPOINT, only change image metadata.
FROM maven:3-eclipse-temurin-21 AS build
WORKDIR /src
COPY pom.xml ./
RUN mvn -q dependency:go-offline
COPY src ./src
RUN mvn -q package -DskipTests
FROM eclipse-temurin:21-jre
COPY --from=build /src/target/orders.jar /app/orders.jar
USER 10001
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/orders.jar"]| Instruction | Effect |
|---|---|
FROM |
Selects the base image. Starts a build stage. |
WORKDIR |
Sets the working directory. |
COPY |
Copies files from the build context or from another stage. |
RUN |
Executes a command at build time and stores the resulting filesystem changes as a layer. |
ENV |
Sets an environment variable in the image. |
USER |
Sets the user the process runs as. |
EXPOSE |
Documents a listening port. It does not publish it. |
ENTRYPOINT / CMD |
The command run when the container starts. CMD supplies default arguments that can be overridden. |
Building and running the image uses the Docker CLI:
docker build -t registry.example.com/orders:1.4.2 .
docker run --rm -p 8080:8080 --memory 512m --cpus 1 registry.example.com/orders:1.4.2-p 8080:8080 publishes container port 8080 on host port 8080 (this is what EXPOSE does not do). --memory and --cpus set the cgroup limits.
Build practices
- Order instructions from least to most frequently changed. In the example, dependency files are copied before source code, so the dependency layer stays cached while the source changes.
- Multi-stage builds. A build stage holds compilers and build tools. The final stage copies only the artifact, which reduces image size and attack surface.
- Small base images. Fewer packages mean fewer vulnerabilities and faster pulls.
- Run as a non-root user.
- Use a
.dockerignorefile to keep the build context small and exclude secrets and local files. - Do not put secrets in layers. A secret written in any layer stays recoverable from the image even if a later layer deletes it.
- Handle signals. The process started by
ENTRYPOINTis PID 1 in the container and receivesSIGTERMon stop. The kernel does not apply default signal actions to PID 1 in a PID namespace, so a process that installs noSIGTERMhandler ignores the signal and is killed withSIGKILLafter the stop timeout. The process must shut down gracefully onSIGTERM, and it must reap orphaned child processes or a small init process must do so. Use the exec form (ENTRYPOINT ["/bin/app"]), because the shell form runs the command under/bin/sh -c, which may not forward signals.
Registries
A registry stores and serves images. An image name has the form registry-host/repository:tag. Docker Hub, and cloud-provider registries such as Amazon ECR, Google Artifact Registry, and Azure Container Registry, are examples. Registries implement the OCI distribution specification.
Push and pull transfer layers, not whole images. A client skips layers that the registry or the local host already has, identified by hash.
sequenceDiagram
participant CI as CI build
participant R as Registry
participant H as Host runtime
CI->>R: push layers and manifest (orders:1.4.2)
H->>R: pull manifest
R-->>H: manifest with layer digests
H->>R: fetch layers not present locally
H->>H: create namespaces, cgroups, overlay mount, start processDeployment practices: pin production deployments to an immutable tag or a digest, scan images for known vulnerabilities in CI, and sign images so hosts can verify their origin.
Containers versus virtual machines
A virtual machine (VM) runs a full guest operating system, including its own kernel, on virtualized hardware provided by a hypervisor.
| Property | Container | Virtual machine |
|---|---|---|
| Kernel | Shared with the host | Own guest kernel |
| Isolation strength | Process-level, depends on kernel security | Hardware-virtualization boundary |
| Startup time | Typically well under a few seconds | Typically tens of seconds or more |
| Image size | Often tens to hundreds of MB | Often GBs |
| Overhead | Low: no guest OS | Higher: guest OS memory and CPU |
| Guest OS | Must be compatible with the host kernel | Any OS |
| Density per host | High | Lower |
Containers usually run on top of VMs in cloud environments. Where stronger isolation is needed for untrusted code, sandboxed runtimes such as gVisor or lightweight VMs such as Firecracker place an additional boundary around each workload.
Trade-offs and limitations
- A kernel vulnerability can affect every container on the host, because the kernel is shared.
- Container filesystems are ephemeral. Persistent data belongs in volumes or external storage.
- Containers are one process per unit by convention. Multiple cooperating processes are deployed as separate containers grouped by an orchestrator such as Kubernetes.
- Image sprawl and unpatched base images create a maintenance burden. Rebuild images regularly.
Interview questions
- What makes a container different from a process? A container is a process started inside new namespaces, limited by cgroups, and with a root filesystem from an image. The kernel treats it as a normal process.
- Why do Dockerfile instruction order and layers matter? Build caching is per layer. When a layer changes, every later layer is rebuilt. Placing rarely changing steps, such as dependency installation, first keeps rebuilds fast.
- Why is a container less isolated than a VM? Containers share the host kernel, so a kernel bug or misconfiguration can cross the boundary. A VM adds a hypervisor boundary with a separate guest kernel.
- What happens to data written inside a container? It goes to the writable layer and is lost when the container is removed. Durable data must use a mounted volume or an external store.
- Why prefer digests over tags in production? A tag can be reassigned, so the same tag can resolve to different images over time. A digest identifies exact content.
