100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
YAML

Docker Security Basics

Defensive practices for hardening container images and runtime, including non-root users, minimal base images, vulnerability scanning, and dropped capabilities.

Docker Best PracticesIntermediate10 min readJul 8, 2026
Analogies

The Container Security Mindset

Containers share the host kernel, so a compromised container can be a stepping stone to the host or other workloads if not properly hardened. Docker security basics focus on defense in depth: reducing what an attacker can do even if application code is compromised, by limiting privileges, minimizing what's present in the image, and catching known vulnerabilities before deployment.

🏏

Cricket analogy: A shared kernel is like a shared team dressing room at Eden Gardens: if one careless player leaves the door unlocked, a stranger can wander from the away team's bench straight into the home team's equipment room, so every player must lock their own kit.

Running as a Non-Root User

By default, processes inside a container run as root unless told otherwise, which is dangerous because a container escape or misconfiguration could grant root-level access on the host. Always create and switch to a dedicated non-root user in your Dockerfile using the USER instruction.

🏏

Cricket analogy: Letting every net bowler use the main pitch groundskeeper's master key by default is dangerous; instead each bowler should be issued a limited-access pass, just as a Dockerfile's USER instruction switches a container from root to a restricted account.

dockerfile
FROM python:3.12-slim

RUN groupadd -r appgroup && useradd -r -g appgroup appuser

WORKDIR /app
COPY --chown=appuser:appgroup . .

USER appuser

CMD ["python", "app.py"]

Minimizing Attack Surface with Slim and Distroless Images

Every package, shell, and utility in an image is a potential vector for exploitation if an attacker gains code execution. Distroless and slim base images remove shells, package managers, and unnecessary binaries, meaning even if an attacker achieves remote code execution, they have far fewer tools available to escalate or pivot.

🏏

Cricket analogy: A stripped-down practice net with no extra equipment lying around gives a trespasser nothing to misuse, unlike a fully stocked pavilion; distroless images similarly remove shells and tools so an attacker who breaks in has nothing to pivot with.

Distroless images have no shell (no /bin/sh), which also means 'docker exec -it <container> sh' will not work — this is intentional hardening, not a bug.

Scanning Images for Known Vulnerabilities

Base images and installed packages regularly accumulate publicly disclosed CVEs. Image scanning tools compare installed package versions against vulnerability databases and should be run in CI before an image is pushed to a registry or deployed.

🏏

Cricket analogy: Before the season starts, teams run every player through fitness and injury screening to catch known issues; image scanning tools similarly check installed packages against known CVE databases before an image ships to production.

bash
# Scan a local image with Trivy before pushing
trivy image myapp:1.4.0

# Fail the CI build on HIGH or CRITICAL findings
trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:1.4.0

Never Bake Secrets into Images

Secrets such as API keys, database passwords, or private certificates must never be added via COPY, ARG, or ENV in a Dockerfile, because they become permanently embedded in image layers and are recoverable even if later layers 'delete' them. Instead, inject secrets at runtime via orchestrator secrets, environment variables passed at container start, or Docker BuildKit's secret mount.

🏏

Cricket analogy: Writing a player's confidential medical report directly onto the permanently archived team scorebook means it stays visible in the historical record forever, even if crossed out later; secrets baked into image layers are similarly unerasable.

dockerfile
# WRONG: secret is permanently baked into a layer
ENV DB_PASSWORD=hunter2

# BETTER: use BuildKit secret mount, never persisted in a layer
# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=db_password \
    DB_PASSWORD=$(cat /run/secrets/db_password) ./configure.sh

ARG values are visible in 'docker history' and image metadata by default. Never pass secrets as build ARGs — use BuildKit secret mounts or runtime injection instead.

Read-Only Filesystems and Dropped Capabilities

Running a container with a read-only root filesystem prevents an attacker or misbehaving process from writing malicious files or tampering with the application at runtime. Similarly, Linux capabilities granted to containers by default (like CAP_NET_RAW) are often unnecessary; dropping all capabilities and adding back only what's required follows the principle of least privilege.

🏏

Cricket analogy: Sealing the pitch under groundskeeper supervision so no one can dig it up mid-match, and only granting a bowler the exact run-up length needed rather than the whole ground, mirrors a read-only filesystem plus dropped Linux capabilities.

bash
docker run --read-only \
  --tmpfs /tmp \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  --security-opt=no-new-privileges \
  myapp:1.4.0
  • Always set a non-root USER in the Dockerfile instead of relying on root
  • Use minimal or distroless base images to shrink the attack surface
  • Scan images for CVEs in CI and fail builds on high/critical findings
  • Never bake secrets into image layers; use BuildKit secret mounts or runtime injection
  • Run containers with --read-only and --cap-drop=ALL, adding back only needed capabilities
  • Use --security-opt=no-new-privileges to block privilege escalation

Practice what you learned

Was this page helpful?

Topics covered

#YAML#DockerKubernetesStudyNotes#DevOps#DockerSecurityBasics#Docker#Security#Container#Mindset#StudyNotes#SkillVeris