cd /blog
DockerContainersDevSecOpsSecurityCloud

#Docker Security: Don't Ship Vulnerabilities in Your Containers

From rootless containers and read-only filesystems to image scanning and Seccomp profiles — a practical checklist for production-grade Docker security.

3 min read 572 words

The Problem With Default Docker

Docker’s defaults prioritise ease of use. Out of the box, containers run as root, images are bloated with unnecessary tools, and the Docker daemon socket is world-accessible. In production, these defaults are serious liabilities.


1. Use a Non-Root User

Running as root inside a container means that a container escape grants root on the host.

FROM python:3.12-slim

# Create a low-privilege user and group
RUN groupadd --gid 1001 appgroup && \
    useradd  --uid 1001 --gid appgroup --no-create-home appuser

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

RUN pip install --no-cache-dir -r requirements.txt

USER appuser
CMD ["python", "main.py"]

2. Use Minimal Base Images

Every package in your image is a potential vulnerability.

# BAD — 1.1 GB image with hundreds of CVEs
FROM ubuntu:22.04

# GOOD — distroless: no shell, no package manager, no CVEs from OS tools
FROM gcr.io/distroless/python3-debian12

# ALSO GOOD — Alpine: tiny, auditable
FROM python:3.12-alpine

Scan images before pushing:

# Trivy — fast, comprehensive scanner
trivy image myapp:latest

# Or with Docker Scout (built into Docker Desktop)
docker scout cves myapp:latest

3. Read-Only Root Filesystem

Prevent attackers from writing tools or modifying configs.

# docker-compose.yml
services:
  app:
    image: myapp:latest
    read_only: true
    tmpfs:
      - /tmp      # Allow writes only to tmpfs
      - /var/run

Or in docker run:

docker run --read-only --tmpfs /tmp myapp:latest

4. Drop Capabilities

Linux capabilities are a fine-grained privilege model. Drop all, add only what you need.

# docker-compose.yml
services:
  app:
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE  # Only if binding to port < 1024

5. Never Mount the Docker Socket

# NEVER DO THIS in production
volumes:
  - /var/run/docker.sock:/var/run/docker.sock

Mounting the Docker socket gives a container full control over the Docker daemon — effectively root on the host. Use a dedicated CI/CD service account and the Docker API via TLS if remote access is required.


6. Seccomp & AppArmor Profiles

Docker ships with a default Seccomp profile. For hardened workloads, use a custom one.

# Apply a custom seccomp profile
docker run --security-opt seccomp=/path/to/profile.json myapp:latest

# Apply AppArmor profile
docker run --security-opt apparmor=docker-default myapp:latest

Generate a minimal profile with oci-seccomp-bpf-hook or syscall2seccomp.


7. Secret Management

Never bake secrets into images. Use Docker secrets or environment injection at runtime.

# BAD — secret baked into image layer
ENV DB_PASSWORD="supersecret123"

# GOOD — Docker secret (Swarm mode)
docker secret create db_password /path/to/secret.txt

# GOOD — Inject at runtime via orchestrator (Kubernetes Secret, AWS SSM, Vault)

Quick Security Checklist

ControlCommand / Config
Non-root userUSER 1001 in Dockerfile
Minimal base imageUse distroless or alpine
Image scanningtrivy image <tag>
Read-only FS--read-only flag
Drop capabilitiescap_drop: [ALL]
No Docker socket mountRemove volume binding
Secrets managementDocker secrets / Vault
Network isolationCustom bridge networks, no --network host

Automated Scanning in CI/CD

# GitHub Actions example
- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: 'myapp:${{ github.sha }}'
    format: 'sarif'
    output: 'trivy-results.sarif'
    severity: 'CRITICAL,HIGH'
    exit-code: '1'   # Fail the build on critical/high CVEs

Security gates in CI ensure you never deploy a vulnerable image.