Skip to content
Home
Docker Security: Best Practices for Container Security

Docker Security: Best Practices for Container Security

Docker Docker 8 min read 1616 words Beginner ExcellentWiki Editorial Team

Docker containers share the host kernel, which means a container breakout could compromise the entire system. While containers provide better isolation than running processes directly on the host, they are not as secure as virtual machines. Understanding this threat model is the foundation of Docker security.

This guide covers Docker security best practices — from image selection to runtime security.

The Docker Security Threat Model

┌──────────────────────────────┐
│        Host OS               │
│  ┌────────────────────────┐  │
│  │  Docker Daemon         │  │
│  │  ┌──┐ ┌──┐ ┌──┐       │  │
│  │  │C1│ │C2│ │C3│       │  │
│  │  └──┘ └──┘ └──┘       │  │
│  │  Shared Kernel         │  │
│  └────────────────────────┘  │
└──────────────────────────────┘
  • Shared kernel — containers use the host kernel
  • Root inside container != root on host (if properly configured)
  • Process isolation via namespaces
  • Resource limits via cgroups

Image Security

Choose Minimal Base Images

# ❌ Large image with many attack surfaces
FROM ubuntu:latest

# ✅ Minimal image
FROM alpine:3.19

# ✅ Distroless — no package manager, no shell
FROM gcr.io/distroless/base

# ✅ Scratch — truly empty
FROM scratch

Pin Specific Tags

# ❌ Vulnerable to tag updates
FROM node:latest
FROM python:3

# ✅ Immutable and reproducible
FROM node:20.11.0-alpine@sha256:abc123def456
FROM python:3.12-slim@sha256:789ghi...

Scan Images for Vulnerabilities

# Using Docker Scout (built into Docker Desktop)
docker scout quickview myimage:tag
docker scout cves myimage:tag

# Using Trivy
trivy image myimage:tag

# Using Snyk
snyk container test myimage:tag

# Using Grype
grype myimage:tag

# Scan in CI/CD
trivy image --severity HIGH,CRITICAL --exit-code 1 myimage:tag

Multi-Stage Builds

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

# Production stage — minimal runtime
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "dist/server.js"]

Running Containers Securely

Least Privilege: Non-Root Users

# Dockerfile — create and use a non-root user
FROM node:20-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
CMD ["node", "server.js"]
# Override user at runtime
docker run -u 1000:1000 myimage

Read-Only Filesystem

docker run --read-only --tmpfs /tmp myimage

Makes the container’s filesystem read-only. Use --tmpfs for writable directories the application needs.

Drop Capabilities

# Start with no capabilities, add only what's needed
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myimage

# Alternatively, the secure computing mode
docker run --security-opt seccomp=/path/to/seccomp-profile.json myimage

Resource Limits

# CPU and memory limits — prevent DoS
docker run \
  --memory="512m" \
  --memory-swap="1g" \
  --cpus="0.5" \
  --pids-limit=100 \
  myimage

Secrets Management

Never Hardcode Secrets

# ❌ Bad — secret in image
ENV DB_PASSWORD=supersecret

# ❌ Bad — secret in build args (visible in history)
ARG API_KEY

Use Docker Secrets (Swarm)

# Create a secret
echo "supersecret" | docker secret create db_password -

# Use in a service
docker service create \
  --name app \
  --secret db_password \
  --secret api_key \
  myapp

# Secrets are mounted as files in /run/secrets/

Use BuildKit for Build Secrets

# docker build --secret id=npmrc,src=$HOME/.npmrc .
# syntax=docker/dockerfile:1
FROM node:20-alpine

# Mount secret during build without leaving layers
RUN --mount=type=secret,id=npmrc \
    cp /run/secrets/npmrc .npmrc && \
    npm ci --production

Use Environment with –env-file

# Store in a file with restricted permissions
echo "DB_PASSWORD=supersecret" > .env
chmod 600 .env

# Pass to container
docker run --env-file .env myapp

# Override specific variables
docker run -e DB_PASSWORD=otherpass myapp

Runtime Security

AppArmor and SELinux

# AppArmor
docker run --security-opt apparmor=my-profile myimage

# SELinux
docker run --security-opt label=level:TopSecret myimage

seccomp

# Default seccomp profile blocks ~44 system calls
docker run --security-opt seccomp=default.json myimage

# Custom seccomp profile
docker run --security-opt seccomp=custom-profile.json myimage

Rootless Docker

Run the Docker daemon without root privileges:

# Install rootless Docker
dockerd-rootless-setuptool.sh install

# Run containers without root
docker run -d --name web nginx

Dockerfile Security Checklist

FROM alpine:3.19                      # ✅ Minimal base image
LABEL maintainer="team@example.com"    # ✅ Metadata

RUN addgroup -S app && \
    adduser -S app -G app              # ✅ Non-root user

WORKDIR /app
COPY --chown=app:app . .               # ✅ Proper ownership

RUN apk add --no-cache curl=8.5.0 && \
    npm ci --only=production && \
    npm cache clean --force            # ✅ Pin versions, clean cache

USER app                               # ✅ Drop privileges
EXPOSE 3000                            # ✅ Document ports
HEALTHCHECK --interval=30s CMD curl -f http://localhost:3000/health || exit 1

CMD ["node", "server.js"]

Runtime Security Checklist

  • Containers run as non-root user
  • Read-only root filesystem (--read-only)
  • All capabilities dropped (--cap-drop ALL)
  • Resource limits set (CPU, memory, PIDs)
  • Secrets never in environment variables
  • Images scanned for vulnerabilities
  • No privileged containers
  • No SSH in containers
  • Docker socket not mounted into containers
  • Latest security patches applied

Container Runtime Hardening

Beyond image security, runtime hardening prevents exploits from compromising the host. Use --security-opt=no-new-privileges:true to prevent privilege escalation within the container. Set --ulimit nofile=1024:1024 to limit file descriptors and prevent fork bombs. Configure the container’s /etc/resolv.conf with trusted DNS servers to avoid DNS poisoning. Use --init flag to run an init process (tini or dumb-init) as PID 1, ensuring proper zombie reaping and signal forwarding. For sensitive workloads, enable user namespaces with --userns-remap=default to map container root to a non-privileged host user. Audit container events with docker events --filter 'type=container' --filter 'event=die' and ship logs to a centralized SIEM. Use Falco or Tracee for runtime anomaly detection — these tools monitor syscall patterns and alert on suspicious behavior like reverse shells or unexpected file access.

Compliance and Auditing

For regulated environments, maintain an audit trail of all container activities. Configure Docker daemon with --icc=false to disable inter-container communication on the default bridge. Use docker logs --details to capture container metadata. Export container configuration with docker inspect for compliance reporting. Docker Bench for Security (docker run --net host --pid host --cap-add audit_control -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST -v /var/lib:/var/lib:ro -v /var/run/docker.sock:/var/run/docker.sock docker/docker-bench-security) runs automated CIS benchmark checks against your Docker host and containers. Schedule this tool to run daily in CI or as a cron job.

FAQ

Is Docker secure enough for production?

Yes, when properly configured. Follow the principle of least privilege: run as non-root, drop capabilities, use read-only filesystems, and scan images. For multi-tenant environments, use VMs or Kata Containers for stronger isolation.

What is the most common Docker security mistake?

Running containers as root and not pinning image versions. Root containers give attackers full privileges if they break out. Unpinned versions silently introduce vulnerabilities when upstream tags are updated.

How do I scan images for vulnerabilities?

Use Docker Scout (built-in), Trivy (open source), or Snyk (commercial). Integrate scanning into your CI/CD pipeline with trivy image --severity HIGH,CRITICAL --exit-code 1 myimage to fail builds on critical vulnerabilities.

Should I mount the Docker socket into a container?

Never do this in production. Mounting /var/run/docker.sock gives the container root-level access to the Docker daemon, allowing it to run any container on the host — a complete security bypass.

What is the difference between --cap-drop and --security-opt?

--cap-drop removes Linux capabilities from the container (coarse-grained). --security-opt applies AppArmor, seccomp, or SELinux profiles (fine-grained). Use both for defense in depth.

Related: Check our Docker Networking guide.

Docker Security Fundamentals

Container security operates on multiple layers: kernel, host, container runtime, and application. The Linux kernel’s namespace and cgroup features provide isolation: PID namespaces isolate process visibility, mount namespaces isolate filesystem mounts, and network namespaces provide network stacks. Run containers with least privilege: drop all capabilities with --cap-drop=ALL and add only needed ones like --cap-add=NET_BIND_SERVICE. Use --security-opt=no-new-privileges:true to prevent privilege escalation. Seccomp profiles filter system calls: Docker’s default profile blocks ~44 risky syscalls. AppArmor and SELinux provide mandatory access control. Rootless mode runs the Docker daemon without root privileges. Read-only root filesystem (--read-only) prevents writes to the container filesystem. Enable Content Trust: export DOCKER_CONTENT_TRUST=1 ensures only signed images run.

Vulnerability Management

Image scanning in CI with docker scout identifies CVEs before deployment. Use minimal base images (distroless, Alpine, slim variants) to reduce the attack surface. Regularly update base images with docker pull and rebuild. Secret management with Docker secrets or external vaults avoids hardcoded credentials. Runtime security monitoring with Falco detects anomalous syscall patterns. Integrate scanning into your deployment pipeline with GitHub Actions or GitLab CI — fail the build if any CRITICAL or HIGH severity vulnerability is found. Schedule weekly scans even for stable images, as new CVEs are discovered daily.

For a comprehensive overview, read our article on Docker Beginners Guide.

Related Concepts and Further Reading

Understanding docker security requires familiarity with several interconnected ideas and principles that together form a complete picture. Exploring these related concepts deepens your knowledge and provides context that makes the core material more meaningful and applicable. Each concept builds on the others, creating a web of understanding that supports deeper learning and practical application. Taking time to explore how these elements connect reveals patterns that accelerate comprehension and retention of new information.

The relationship between docker security and adjacent fields is worth particular attention. Many of the most important insights emerge at the boundaries between disciplines, where ideas from different areas combine to create new approaches and solutions that neither field could produce alone. Exploring these connections pays dividends in both breadth and depth of understanding, revealing patterns and principles that might otherwise remain hidden from view. Cross-disciplinary knowledge is increasingly valued as problems become more complex and interconnected.

For those looking to go beyond introductory material, several excellent resources provide deeper treatment of specific aspects of docker security. Academic journals, industry publications, authoritative reference works, and online courses each offer different perspectives and levels of detail. The key is to match your reading to your current learning goals and build knowledge progressively, focusing on quality over quantity in your study materials. A well-chosen resource that matches your current level is worth more than dozens of resources that are too basic or too advanced.

Section: Docker 1616 words 8 min read Beginner 756 articles in section Report inaccuracy Back to top