Container Security and Docker Hardening
Containers package an application together with its dependencies into a portable unit, but unlike virtual machines they share the host operating system kernel. That shared kernel makes container security a distinct discipline: a weak image or a misconfigured runtime can let an attacker break out to the host or move laterally across a cluster. Docker hardening focuses on shrinking the attack surface of images and tightly constraining what a running container is allowed to do.
How Container Isolation Actually Works
A container is not a lightweight virtual machine. It is an ordinary host process wrapped in kernel isolation features. Understanding those features clarifies the threat model.
- Namespaces control what a process can see, isolating process IDs, network interfaces, mount points, users, and hostnames so a container perceives its own slice of the system.
- Control groups (cgroups) limit what a process can consume, capping CPU, memory, and process counts to prevent one container from starving others.
- Capabilities break the powers of root into discrete units, so a container can be granted only the privileges it genuinely needs.
- seccomp, AppArmor, and SELinux restrict which system calls and resources a container may access.
The single most important point is that all containers on a host share one kernel. A kernel vulnerability reachable from inside a container can enable a container escape, which is why isolation controls and a patched host matter so much.
Building Minimal, Trustworthy Images
Most container risk enters through the image. A smaller, cleaner image has fewer components that can be vulnerable or abused.
- Start from a minimal base image such as a distroless, Alpine, or scratch image, so the container ships without a shell or package manager an attacker could use.
- Pin versions by digest rather than mutable tags, so builds are reproducible and cannot silently pull altered content.
- Use multi-stage builds to compile in one stage and copy only the finished artifact into a lean runtime stage, leaving build tools behind.
- Never bake secrets into layers. Image layers are additive, so a credential added and later deleted still lives in history.
- Scan images for known vulnerabilities before promoting them.
Running Containers with Least Privilege
Even a clean image must run under tight constraints. A hardened Dockerfile establishes a non-root user and a minimal runtime:
FROM node:20-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=build /app /app
USER 1000 # run as a non-root user, never as root
EXPOSE 3000
ENTRYPOINT ["server.js"]
At runtime, reinforce the image with restrictive flags: drop all Linux capabilities and add back only what is required, mount the root filesystem read-only, set no-new-privileges to block privilege escalation, and cap process counts. Running as a non-root user inside the container is one of the highest-value hardening steps, because it limits what a compromise can reach even if isolation is imperfect.
Threat Model: Container Escape and Lateral Movement
The most serious container risks involve breaking the boundary between container and host.
- Privileged containers run with nearly full host access and effectively erase isolation. Avoid the
--privilegedflag except for narrow, well-understood cases. - Mounting the Docker socket into a container grants control of the daemon, which is equivalent to root on the host.
- Broad host path mounts expose sensitive host files and can enable escape.
- Running as root inside a container combined with a kernel bug is the classic escape recipe.
Because containers are frequently orchestrated, a single compromised container can become a pivot point. Segmenting workloads and controlling network paths, as covered in Kubernetes security, limits how far an intruder can travel.
Supply Chain and Image Provenance
Containers pull in code from registries, base images, and dependencies, so the software supply chain is part of the attack surface.
- Sign images and verify signatures so only trusted builds are deployed.
- Generate a software bill of materials (SBOM) to know exactly what each image contains.
- Pull from trusted, controlled registries rather than arbitrary public sources.
- Rebuild regularly so patched base layers replace vulnerable ones.
Handling the credentials that images and pipelines need belongs in a dedicated system rather than in environment files, as explained in secrets management. All of this fits within the broader division of duties described in the shared responsibility model.
Key Takeaways
- Containers share the host kernel, so isolation depends on namespaces, cgroups, capabilities, and seccomp rather than a hypervisor.
- Build from minimal, digest-pinned base images, use multi-stage builds, and keep secrets out of layers.
- Run as a non-root user with dropped capabilities, a read-only filesystem, and no new privileges.
- Avoid privileged containers and Docker socket mounts, which effectively grant host-level access.
- Secure the supply chain with image signing, scanning, SBOMs, and trusted registries, and rebuild often to absorb patches.