DevSecOps

Container Hardening Guide 2025: From Base Image to Production

A practical guide to hardening container images and deployments. Covers base image selection, build-time security, runtime protections, and Kubernetes-specific controls.

Shadab Khan
Security Engineer
6 min read

Containers are the dominant deployment model for modern applications, but the defaults are not secure. A freshly built Docker image running on a default Kubernetes cluster has a significant attack surface: unnecessary packages, root users, writable filesystems, overly permissive network access, and more.

This guide covers practical container hardening steps, from selecting a base image to configuring production runtime protections. These are not theoretical recommendations -- they are the controls that prevent real breaches.

Base Image Selection

The base image is the foundation of your container's security posture. Every package in the base image is a component in your supply chain and a potential vulnerability.

Minimize the base

The single most impactful hardening step is reducing the base image size. Fewer packages means fewer vulnerabilities, smaller attack surface, and less for an attacker to work with post-exploitation.

Distroless images (Google's distroless project) contain only the application runtime and its dependencies. No shell, no package manager, no utilities. A compromised distroless container gives an attacker very little to work with.

Alpine-based images provide a minimal Linux environment (~5MB) with a package manager for cases where you need some OS utilities. Alpine uses musl libc instead of glibc, which means smaller images but occasional compatibility issues.

Scratch images are empty -- they contain literally nothing. Statically compiled Go binaries and similar self-contained applications can run on scratch images. This is the ultimate in minimization.

What to avoid: General-purpose base images like ubuntu:latest or debian:latest include hundreds of packages your application does not need. Every unnecessary package is a liability.

Pin your base images

Never use latest tags for base images. A FROM node:latest instruction means your build will use whatever version is current at build time, which changes without notice.

Pin to specific digests: FROM node:20-alpine@sha256:abc123.... This ensures build reproducibility and prevents supply chain attacks through base image tampering.

Build-Time Hardening

Multi-stage builds

Multi-stage builds separate the build environment from the runtime environment. Build tools, compilers, and development dependencies stay in the build stage and never appear in the final image.

# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
RUN npm run build

# Runtime stage
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "dist/index.js"]

Non-root users

Running containers as root is the default, and it is a security failure. If an attacker achieves code execution inside a root container, they have root privileges on the container's filesystem and potentially on the host (depending on other controls).

Always include a USER instruction in your Dockerfile to run as a non-root user. Most base images include a non-root user (e.g., node in Node.js images, nobody in many others).

Read-only filesystem

Configure containers with a read-only root filesystem. This prevents attackers from modifying application binaries, installing tools, or writing persistence mechanisms.

Mount specific writable volumes for directories that need write access (temp files, logs, caches), and keep everything else read-only.

No secrets in images

Never embed credentials, API keys, or certificates in container images. Use runtime secret injection through Kubernetes Secrets, HashiCorp Vault, AWS Secrets Manager, or similar mechanisms.

Scan images for embedded secrets as part of your CI pipeline. Tools like GitLeaks and TruffleHog can scan container image layers for accidentally included credentials.

Image Scanning and SBOMs

Scan at build time

Integrate container image scanning into your CI/CD pipeline. Every image build should be scanned for:

  • Known vulnerabilities in OS packages and application dependencies.
  • Misconfigurations (root user, exposed ports, etc.).
  • Embedded secrets.
  • License compliance issues.

Generate SBOMs for images

Generate an SBOM for every container image as a build artifact. This SBOM should include both OS-level packages and application dependencies. Store SBOMs alongside images in your registry for later analysis.

Continuous monitoring

Build-time scanning is necessary but not sufficient. New CVEs are published daily, and an image that was clean at build time may have known vulnerabilities a week later. Implement continuous monitoring of deployed images against updated vulnerability databases.

Kubernetes-Specific Controls

Pod Security Standards

Kubernetes Pod Security Standards (PSS) define three security levels: Privileged (unrestricted), Baseline (minimal restrictions), and Restricted (tightly locked down).

At minimum, enforce the Baseline level across all namespaces. For workloads that do not need special privileges, enforce the Restricted level. This prevents:

  • Privileged containers
  • Host namespace sharing
  • Privilege escalation
  • Running as root

Network Policies

By default, Kubernetes allows all pod-to-pod communication. This means a compromised container can communicate with any other container in the cluster.

Implement Network Policies to restrict traffic to only what is needed. Start with a default deny-all policy and add explicit allow rules for legitimate communication paths.

Resource Limits

Always set CPU and memory limits on containers. Without limits, a compromised container can consume all available resources, impacting other workloads (denial of service) or mining cryptocurrency.

Admission Controllers

Use admission controllers to enforce security policies at deployment time:

  • Reject images from untrusted registries.
  • Require image signatures (using Sigstore/cosign).
  • Enforce SBOM presence as a deployment requirement.
  • Block images with critical unpatched vulnerabilities.

Runtime Protections

Seccomp profiles

Seccomp (Secure Computing Mode) restricts which system calls a container can make. The default Docker seccomp profile blocks approximately 44 of 300+ syscalls, but custom profiles can be much more restrictive.

For applications with well-understood system call patterns, create custom seccomp profiles that allow only the syscalls your application actually needs.

AppArmor/SELinux

Mandatory access control systems provide additional runtime restrictions. AppArmor profiles or SELinux policies can limit file access, network operations, and capability usage beyond what standard Linux permissions allow.

Runtime monitoring

Deploy runtime security monitoring that can detect anomalous behavior inside containers: unexpected process execution, unusual network connections, filesystem modifications in read-only areas, and privilege escalation attempts.

How Safeguard.sh Helps

Safeguard.sh integrates container security into your SBOM-driven vulnerability management program. By generating and analyzing SBOMs for your container images, Safeguard provides continuous visibility into the vulnerability status of your deployed containers.

Safeguard's policy gates can enforce container hardening requirements in your CI/CD pipeline: blocking images that run as root, requiring SBOMs for all deployed images, and preventing deployment of images with critical unpatched vulnerabilities. This shifts container security left, catching issues during build rather than discovering them in production.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.