Docker Security Best Practices

Why Docker Security Matters

Containers share the host OS kernel. A compromised container can potentially affect the host or other containers. Following security best practices reduces this risk.

1. Run as a Non-Root User

By default, processes inside containers run as root (user ID 0). This is dangerous - if an attacker escapes the container, they have root on the host.

In a Dockerfile

FROM node:20-alpine

WORKDIR /app
COPY . .
RUN npm ci --only=production

# Create a non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# Switch to non-root user
USER appuser

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

At Runtime

# Run as a specific user
docker run -d --user 1000:1000 my-app

# Run as a user by name
docker run -d --user appuser my-app

# Check what user a container is running as
docker exec my-app whoami
docker inspect -f '{{.Config.User}}' my-app

2. Use Read-Only Filesystems

Prevent the container from writing to its filesystem (except specific allowed paths).

# Run with read-only filesystem
docker run -d --read-only my-app

# Allow writes only to specific directories using tmpfs
docker run -d \
  --read-only \
  --tmpfs /tmp \
  --tmpfs /var/run \
  my-app

# Verify
docker inspect -f '{{.HostConfig.ReadonlyRootfs}}' my-app

In Docker Compose:

services:
  app:
    image: my-app
    read_only: true
    tmpfs:
      - /tmp
      - /var/run

3. Drop Unnecessary Linux Capabilities

Linux capabilities grant specific privileges. By default, containers get a set of capabilities. Drop all and only add what you need.

# Drop all capabilities
docker run -d --cap-drop ALL my-app

# Drop all, then add back only what's needed
docker run -d \
  --cap-drop ALL \
  --cap-add NET_BIND_SERVICE \   # allow binding to ports < 1024
  my-app

# Common capabilities to add back
--cap-add NET_BIND_SERVICE    # bind to privileged ports
--cap-add CHOWN               # change file ownership
--cap-add SETUID              # switch user ID
--cap-add SETGID              # switch group ID

In Docker Compose:

services:
  app:
    image: my-app
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE

4. Never Run Privileged Containers

--privileged gives the container almost all Linux capabilities and access to host devices. Avoid it.

# BAD - never do this unless absolutely required
docker run -d --privileged my-app

# GOOD - run without privileges
docker run -d my-app

5. Limit Resources

Prevent a compromised container from starving the host.

docker run -d \
  --memory 512m \
  --cpus 1 \
  --pids-limit 100 \     # limit number of processes
  my-app

6. Use Official and Verified Images

# Prefer official images
docker pull nginx           #  official
docker pull mysql:8.0       #  official

# Avoid unknown community images for production
docker pull random-user/nginx    #  unknown source

# Always specify a version tag - never just "latest" in production
docker pull nginx:1.25      #  specific version
docker pull nginx:latest    #  could change without warning

7. Scan Images for Vulnerabilities

# Scan an image with Docker Scout (built into Docker)
docker scout cves nginx
docker scout cves my-app:1.0

# Quickview
docker scout quickview nginx

# Show only critical and high vulnerabilities
docker scout cves --only-severity critical,high nginx

# Compare two images
docker scout compare my-app:1.0 my-app:2.0

8. Use .dockerignore

Prevent sensitive files from being included in the build context.

# .dockerignore
.env
.env.*
*.key
*.pem
*.p12
secrets/
credentials/
.git/
node_modules/

9. Don't Store Secrets in Images

# BAD - secret is baked into the image and visible in history
ENV API_KEY=abc123secretkey

# BAD - secret in RUN command (visible in image layers)
RUN echo "abc123" > /app/secret.txt

# GOOD - pass secrets at runtime
# docker run -e API_KEY=abc123 my-app

# GOOD - use Docker secrets (Swarm/Compose)

Check image history for exposed secrets:

docker image history --no-trunc my-app

10. Use Docker Secrets (Docker Compose/Swarm)

For sensitive data, use Docker secrets instead of environment variables.

# docker-compose.yml
services:
  app:
    image: my-app
    secrets:
      - db_password
      - api_key
    environment:
      DB_PASSWORD_FILE: /run/secrets/db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt
  api_key:
    file: ./secrets/api_key.txt

Secrets are mounted as files at /run/secrets/<name> inside the container. They are never stored in environment variables or image layers.

11. Limit Container Network Access

# Deny all outgoing network (for batch jobs that don't need internet)
docker run -d --network none my-batch-job

# Use separate networks to isolate services
docker network create frontend-net
docker network create backend-net

# Database only on backend network (not accessible from frontend)
docker run -d --network backend-net mysql:8.0

# Web app on both networks (bridge between them)
docker run -d --network frontend-net --network backend-net my-app

12. Keep Images Up to Date

# Pull latest version of base images regularly
docker pull node:20-alpine
docker pull python:3.11-slim

# Rebuild your images after updating base
docker build --no-cache -t my-app:latest .

13. Use --no-new-privileges

Prevents container processes from gaining new privileges via setuid or setgid.

docker run -d --security-opt no-new-privileges my-app

In Docker Compose:

services:
  app:
    image: my-app
    security_opt:
      - no-new-privileges:true

14. Seccomp Profiles

Seccomp (Secure Computing Mode) filters which system calls a container can make.

# Docker applies a default seccomp profile automatically
# To run without seccomp (not recommended)
docker run -d --security-opt seccomp=unconfined my-app

# Apply a custom seccomp profile
docker run -d --security-opt seccomp=/path/to/profile.json my-app

Security Checklist

PracticeCommand / Method
Non-root userUSER appuser in Dockerfile, --user at runtime
Read-only filesystem--read-only + --tmpfs
Drop capabilities--cap-drop ALL --cap-add NEEDED
No privileged modeNever use --privileged
Resource limits--memory, --cpus, --pids-limit
Official images onlyUse nginx, mysql, etc.
Pin image versionsnginx:1.25, not nginx:latest
Scan for vulnerabilitiesdocker scout cves my-app
No secrets in imagesPass via -e or Docker secrets
No new privileges--security-opt no-new-privileges
Network isolationCustom networks, --network none
Keep images updatedRegular rebuilds with --no-cache

FAQ

Should I memorize every Docker command?+

No. Memorize the core workflow first: build, run, list, inspect, logs, exec, stop, remove, and clean up. Then learn specialized commands when you need them.

Is Docker only for developers?+

No. Docker is useful for system administrators, infrastructure engineers, DevOps engineers, cloud engineers, support engineers, and learners who want repeatable labs.

What should I do after reading this guide?+

Run the examples, write down what each command changes, rebuild the workflow with Docker Compose, and then add one CI/CD step that builds the image automatically.

Need help applying Docker in a real project?

Work directly with Muhammad Irfan Aslam for Docker, Linux, DevOps, CI/CD, cloud deployment, or infrastructure troubleshooting support.

Hire Me for Support