Container Security & Hardening Practices
Containers share the underlying host operating system kernel. A security breach inside an unhardened container can lead to a full container breakout, compromising the entire host infrastructure. In this chapter, learn how to enforce least privilege, drop dangerous Linux capabilities, lock filesystems into read-only mode, and automate continuous CVE scanning.
1. The Non-Root Imperative: USER Directives
By default, containers execute as UID 0 (root). While namespaces isolate that process from other host users, running as root inside a container grants attackers an immediate foothold if a Linux kernel vulnerability (like Dirty COW or runc exploits) is present.
Always create a dedicated low-privilege system user in your Dockerfile and switch to it:
FROM node:20-alpine
WORKDIR /app
# Create custom group and user with fixed UID/GID
RUN addgroup -S appgroup -g 10001 && \
adduser -S appuser -u 10001 -G appgroup
COPY --chown=appuser:appgroup package*.json ./
RUN npm ci --omit=dev
COPY --chown=appuser:appgroup . .
# Switch execution away from root to UID 10001
USER 10001:10001
EXPOSE 3000
CMD ["node", "server.js"]2. Dropping Linux Capabilities (--cap-drop=ALL)
The Linux kernel partitions traditional root superuser powers into granular units called capabilities. By default, Docker grants containers 14 capabilities (including CHOWN, KILL, SETUID,NET_BIND_SERVICE).
Web applications and microservices rarely require these kernel capabilities. The gold standard for production security is to drop all capabilities, and selectively restore only what is strictly necessary:
# Drop all Linux capabilities and allow binding to low ports (<1024) if needed:
docker run -d \
--name hardened-api \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
-p 80:80 \
my-api:latest3. Read-Only Root Filesystems (--read-only)
Most modern exploits (such as web shell injection or reverse shell malware) rely on writing an executable script or modifying binaries inside /tmp or the app directory. Mounting the container's root filesystem as completely read-only instantly neutralizes these attack vectors:
docker run -d \
--name immutable-app \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=64m \
--tmpfs /app/logs:rw,noexec,nosuid,size=32m \
-p 8080:8080 \
my-app:v1tmpfs mounts with noexec (preventing execution of newly written files).4. Production Hardening Checklist & Flags
| Flag | Security Impact | Threat Mitigated |
|---|---|---|
--security-opt=no-new-privileges:true | Prevents processes inside the container from gaining additional privileges via setuid or setgid binaries. | Privilege escalation exploits. |
--cap-drop=ALL | Strips all 14 default kernel capabilities from the container process. | Kernel manipulation and unauthorized network modifications. |
--read-only | Mounts the rootfs as read-only. Writable disk layer is disabled. | Trojan downloads and rootkit modifications. |
--pids-limit=150 | Limits concurrent active process IDs in the container namespace. | Denial-of-Service (DoS) and fork bombs. |
5. Automated Vulnerability Scanning: Docker Scout & Trivy
Continuously inspect base images and application libraries for Common Vulnerabilities and Exposures (CVEs) prior to deployment:
# 1. Scan image for known CVEs using Docker Scout:
docker scout cves my-app:latest
# 2. View recommended base image upgrades with fewer vulnerabilities:
docker scout recommendations my-app:latest
# 3. Generate a Software Bill of Materials (SBOM) for compliance:
docker buildx build --sbom=true --output type=local,dest=out .Next Up: Environment Variables & Secrets
In Chapter 16, discover how to safely pass secrets, inject .env configurations, and avoid leaking credentials into image layers.
Chapter 16: Env Variables & Secrets →