Containers#

Containers are the operator’s portable unit of capability. An application plus its dependencies, packaged once and dropped onto any host that runs an OCI runtime, the same image that ships a defensive sensor ships a recon harness or a parsing pipeline. The Linux kernel provides the primitives (namespaces, cgroups, overlay filesystems); a runtime (containerd, CRI-O, Docker, Podman) drives them; the OCI image format standardises the artifact.

For the operator that means a campaign, an investigation, or a defended estate can stand up identical compute on a laptop, a cloud VM, or a customer’s cluster, then tear it down without artifacts left on the host.

        flowchart LR
  subgraph Build["Build"]
    Dockerfile -->|docker build| Image[OCI Image]
  end
  Image -->|push| Registry[(Container Registry)]
  Registry -->|pull| Node1
  Registry -->|pull| Node2

  subgraph Node1["Host / Node"]
    D1[Docker / containerd] --> R1[runc]
    R1 --> NS1[namespaces<br/>cgroups<br/>overlayfs]
    NS1 --> C1[Container 1]
    NS1 --> C2[Container 2]
  end

  subgraph Node2["Host / Node"]
    D2[Docker / containerd] --> R2[runc]
    R2 --> NS2[namespaces<br/>cgroups<br/>overlayfs]
    NS2 --> C3[Container 3]
  end
    

Runtimes#

The layers of the container runtime stack. Docker provides the user-facing CLI and daemon; containerd is what most Kubernetes nodes run; CRI-O is the lightweight Kubernetes alternative; runc is the low-level OCI runtime everyone calls into. Podman runs daemonless and rootless.

Runtime

Role

Docker

The original; client + daemon + UX.

Podman

Daemonless, rootless, Docker-compatible CLI.

containerd

The runtime under Docker and most Kubernetes installs.

CRI-O

Lightweight runtime for Kubernetes.

runc

Low-level OCI runtime; called by the others.

Images#

An image is a stack of read-only layers plus metadata, addressed by digest. Build them with a Dockerfile (or Containerfile):

# Multi-stage build keeps the runtime image small
FROM golang:1.22 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app ./cmd/app

FROM gcr.io/distroless/static
COPY --from=build /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]
$ docker build -t myorg/app:1.0 .
$ docker push myorg/app:1.0

Registries#

Container registries are the artifact stores for OCI images. Each major cloud has its own; GitHub and Docker Hub serve the public ecosystem; self-hosted Harbor or distribution/registry covers internal use cases. Image addressing combines a host, repository path, tag, and content digest.

Images are stored in registries, addressed by host/repository:tag and a content digest:

Registry

Role

Docker Hub

Public default for the wider ecosystem.

GitHub Container Registry

Public and private images alongside GitHub repos.

Google Artifact Registry

Google Cloud.

Amazon ECR

AWS.

Azure Container Registry

Azure.

Harbor

Self-hosted, CNCF.

distribution/registry

Self-hosted reference implementation.

Running#

The everyday container commands. docker run starts a single container; docker compose up orchestrates many; docker logs and docker exec are the inspection and debugging verbs. Compose’s YAML defines services, networks, and volumes declaratively for development.

$ docker run --rm -it -p 8080:8080 myorg/app:1.0
$ docker compose up -d
$ docker logs -f web
$ docker exec -it web sh

Compose orchestrates multi-container applications declaratively:

services:
  web:
    image: myorg/app:1.0
    ports: ["8080:8080"]
    environment:
      DATABASE_URL: postgres://db:5432/app
    depends_on: [db]
  db:
    image: postgres:16
    volumes: [pgdata:/var/lib/postgresql/data]
volumes:
  pgdata:

Best Practices#

The habits that distinguish a workable Dockerfile from a production-grade image. Digest pinning, minimal bases, multi-stage builds, non-root execution, single-process discipline, layer-aware ordering, and supply-chain hygiene (signing, scanning) all stack into something safe to deploy.

Practice

Shape

Pin base images

By digest, not just tag.

Use minimal bases

distroless, alpine, scratch, to shrink attack surface.

Multi-stage builds

Keep build tools out of the runtime image.

Run as non-root

USER directive plus a non-root UID.

One process per container

Use init systems sparingly.

Cache-friendly layer order

Copy lockfiles first, then RUN install, then app code.

Sign and scan images

cosign, Trivy, Grype.