Skip to content

Containers 101

A container is an isolated process that shares the host kernel. Unlike a VM, there is no guest OS — the container sees its own filesystem, network stack, and PID namespace but runs directly on the host. This makes containers lighter and faster to start than VMs.

On Kestrel, every workload runs as a container inside a Kubernetes Pod. You never SSH into a node; you interact with containers through kubectl.

A container image is a read-only filesystem snapshot packaged as a stack of layers. Each layer corresponds to a step in the build process. Layers are cached and shared between images, so two images that start from the same base only store the diff.

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "main.py"]

Key points:

  • Order matters. Put layers that change least (base image, dependency install) first. Layers that change often (your source code) go last.
  • Each instruction creates a layer. Combine related RUN commands with && to reduce layer count.
  • Don’t store secrets in layers. Anything written during build is baked into the image permanently.

A registry is a server that stores and distributes images. When you docker pull nginx:1.27, the client fetches layers from a registry (Docker Hub by default).

Common registries:

RegistryUse case
Docker HubPublic images, upstream base images
GitHub Container Registry (ghcr.io)Project images tied to GitHub repos
Harbor / GitLab RegistrySelf-hosted, often used by institutional clusters

A Dockerfile is a text recipe that builds an image. The most common instructions:

InstructionPurpose
FROMBase image to start from
WORKDIRSet the working directory inside the image
COPY / ADDCopy files from the build context into the image
RUNExecute a command during build (install packages, compile code)
ENVSet environment variables
EXPOSEDocument which port the app listens on (does not publish the port)
CMD / ENTRYPOINTDefault command when the container starts

Use multi-stage builds to keep final images small. Build in one stage, copy only the artifact into a minimal runtime stage:

FROM golang:1.22 AS build
WORKDIR /src
COPY . .
RUN go build -o /app .
FROM gcr.io/distroless/static
COPY --from=build /app /app
CMD ["/app"]

The final image contains only the compiled binary — no compiler, no source code.

When Kubernetes starts a container:

  1. The kubelet pulls the image (if not cached on the node).
  2. A new container process starts with the image’s filesystem as its root.
  3. The process runs until it exits, is killed, or the Pod is deleted.
  4. Logs from stdout/stderr are captured and accessible via kubectl logs.

Containers are ephemeral. Any data written inside the container is lost when it stops unless you mount a persistent volume.