Container Security Basics: Images, Privileges, and Resource Limits

Trusted images, least privilege, and resource caps — a handful of defaults that meaningfully shrink your container attack surface.

Containers make deployment easy, but the defaults tend to favor "it runs" over "it runs safely." Whether you host containers on a single VPS or across a cluster, it's worth walking through the layers of hardening below before you ship.

Start With a Trusted Image

The image is the foundation of a container. A poisoned base image undermines every control layered on top of it.

  • Prefer official or otherwise trusted images, and lean toward slim variants (-slim, distroless, alpine). Smaller images ship fewer components to attack.
  • Pin an explicit version tag — never rely on latest. latest drifts over time, so a build you reproduced yesterday can quietly change today. In production, go a step further and pin by digest, which is immutable:
# Pin by digest so the content can't change underneath you
docker pull nginx@sha256:<digest>
  • Scan for vulnerabilities in your pipeline to block known high-severity CVEs before they reach production:
trivy image --severity HIGH,CRITICAL your-registry/app:1.4.2

Run With Least Privilege Inside the Container

Even with a clean image, assume the process inside could be compromised — and limit the damage if it is.

  • Don't run as root. Switch to an unprivileged user in your Dockerfile with USER:
RUN adduser --system --uid 10001 app
USER 10001
  • Make the root filesystem read-only so an attacker can't drop or tamper with files; mount a separate writable volume only where the app genuinely needs to write.
  • Drop Linux capabilities you don't need, keep only what's essential, and forbid privilege escalation.

In a cluster, capture all of this centrally in the Pod's securityContext:

securityContext:
  runAsNonRoot: true
  runAsUser: 10001
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  capabilities:
    drop: ["ALL"]

Set Resource Requests and Limits

A runaway container — a memory leak or a tight loop — can starve the host and take neighboring services down with it. Setting bounds on every container contains the blast radius:

resources:
  requests:
    cpu: "100m"
    memory: "128Mi"
  limits:
    cpu: "500m"
    memory: "256Mi"

requests drive scheduling and reserve capacity; limits are hard ceilings — a container that exceeds its memory limit is killed rather than allowed to drag the whole host down.

Don't Hand the Container the Host's Keys

Many serious escape incidents trace back to a careless mount.

  • Never mount the Docker socket (/var/run/docker.sock) into an ordinary container — access to it is effectively root on the host.
  • Don't mount sensitive host paths such as /, /etc, /root, or /var/run. Mount only the minimal paths the container truly needs, and mount them read-only where you can.
  • Avoid --privileged and the host network/PID namespaces unless you fully understand the cost.

Keep Base Images Updated

Security is an ongoing state, not a one-time action. Libraries in a base image accumulate new CVEs over time, so rebuild and redeploy on a regular cadence: pull the patched base image, run the scan, and roll out the update. Wiring "scan plus rebuild" into a scheduled job or CI lets you patch hours after a vulnerability goes public — not months later.

Summary

Most of container security comes down to a few defaults: use trusted, version-pinned, scanned images; run with least privilege as non-root with a read-only root and dropped capabilities; isolate resources with requests and limits; never mount the Docker socket or sensitive host paths; and keep rebuilding to pick up base-image updates. Bake these into your Dockerfile and deployment-manifest templates so new services are secure by default instead of patched after the fact.