Guide contents

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 --> L1

Properties 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.

DOCKERFILE
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:

Code
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 .dockerignore file 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 ENTRYPOINT is PID 1 in the container and receives SIGTERM on stop. The kernel does not apply default signal actions to PID 1 in a PID namespace, so a process that installs no SIGTERM handler ignores the signal and is killed with SIGKILL after the stop timeout. The process must shut down gracefully on SIGTERM, 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 process

Deployment 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.