0% read
Skip to main content
Docker Container Security - Production Hardening Guide

Docker Container Security - Production Hardening Guide

Complete guide to securing Docker containers in production. Learn image scanning, runtime security, network isolation, secrets management, and compliance best practices with real-world examples.

S
StaticBlock Editorial
16 min read

Why Container Security Matters

Containers revolutionized application deployment, but their ephemeral nature and shared kernel architecture introduce unique security challenges. A single misconfigured container can expose your entire infrastructure.

The threat landscape:

  • 76% of organizations experienced container security incidents in 2024
  • Average cost of a container breach: $4.1 million
  • 60% of container images contain known vulnerabilities

This guide covers production-ready security patterns that go beyond basic Docker tutorials.

Image Security: Building Trustworthy Containers

Use Minimal Base Images

Bloated images increase attack surface. Every package is a potential vulnerability.

# ❌ BAD: Full OS with unnecessary packages
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
    python3 python3-pip curl wget vim nano

✅ GOOD: Minimal distroless image

FROM gcr.io/distroless/python3-debian11 COPY requirements.txt . COPY app.py . CMD ["app.py"]

Why distroless?

  • No shell (prevents shell injection attacks)
  • No package managers (can't install malware at runtime)
  • Minimal dependencies (smaller attack surface)
  • 50-80% smaller image sizes

Alternative minimal bases:

  • alpine:3.18 - 5MB base, package manager included
  • scratch - Empty image, add only your binary
  • gcr.io/distroless/* - Language-specific minimal images

Multi-Stage Builds for Security

Separate build-time dependencies from runtime:

# Stage 1: Build with all tools
FROM node:20-alpine AS builder
WORKDIR /build
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

Stage 2: Production runtime (no build tools)

FROM gcr.io/distroless/nodejs20-debian11 WORKDIR /app COPY --from=builder /build/dist ./dist COPY --from=builder /build/node_modules ./node_modules CMD ["dist/server.js"]

Benefits:

  • Build tools never reach production
  • Final image contains only runtime dependencies
  • Reduces image size by 70-90%
  • Eliminates unnecessary attack vectors

Image Scanning with Trivy

Scan images for vulnerabilities before deployment:

# Install Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin

Scan local image

trivy image my-app:latest

Scan with severity filtering (fail on HIGH/CRITICAL)

trivy image --severity HIGH,CRITICAL --exit-code 1 my-app:latest

Scan specific vulnerability types

trivy image --vuln-type os,library my-app:latest

Output to JSON for CI integration

trivy image --format json --output results.json my-app:latest

CI/CD integration example:

# .github/workflows/security-scan.yml
name: Container Security Scan
on: [push, pull_request]

jobs: scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3

  - name: Build image
    run: docker build -t test-image .

  - name: Run Trivy vulnerability scanner
    uses: aquasecurity/trivy-action@master
    with:
      image-ref: test-image
      severity: 'CRITICAL,HIGH'
      exit-code: '1'  # Fail build on vulnerabilities

Content Trust with Docker Notary

Ensure image integrity and provenance:

# Enable Docker Content Trust
export DOCKER_CONTENT_TRUST=1

Push signed images

docker push myregistry.com/myapp:v1.0

Pull verifies signatures automatically

docker pull myregistry.com/myapp:v1.0

View image signatures

docker trust inspect myregistry.com/myapp:v1.0

Why content trust?

  • Prevents man-in-the-middle attacks
  • Guarantees image hasn't been tampered with
  • Verifies publisher identity
  • Meets compliance requirements (PCI-DSS, HIPAA)

Runtime Security: Hardening Running Containers

Run as Non-Root User

Never run containers as root. Create a dedicated user:

FROM python:3.11-slim

Create non-root user

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

Set ownership

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

Switch to non-root user

USER appuser

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

Kubernetes enforcement:

apiVersion: v1
kind: Pod
metadata:
  name: secure-app
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    fsGroup: 1000
  containers:
  - name: app
    image: myapp:latest
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true

Read-Only Filesystems

Prevent runtime modifications:

# Docker run with read-only root filesystem
docker run --read-only \
  --tmpfs /tmp:rw,noexec,nosuid \
  --tmpfs /var/run:rw,noexec,nosuid \
  myapp:latest

Docker Compose

services: app: image: myapp:latest read_only: true tmpfs: - /tmp:rw,noexec,nosuid - /var/run:rw,noexec,nosuid

Why read-only?

  • Prevents malware persistence
  • Stops unauthorized file modifications
  • Forces stateless application design
  • Simplifies security audits

Drop Unnecessary Capabilities

Linux capabilities provide fine-grained privilege control:

# Drop all capabilities, add only what's needed
docker run --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  myapp:latest

Kubernetes security context

securityContext: capabilities: drop: - ALL add: - NET_BIND_SERVICE

Common capabilities to avoid:

  • SYS_ADMIN - Essentially root access
  • NET_RAW - Packet sniffing/spoofing
  • SYS_PTRACE - Process debugging
  • DAC_OVERRIDE - Bypass file permissions

Safe capabilities:

  • NET_BIND_SERVICE - Bind to ports < 1024
  • CHOWN - Change file ownership
  • SETUID/SETGID - Change user/group IDs

Security Profiles with AppArmor/Seccomp

Restrict syscalls available to containers:

# Use default seccomp profile
docker run --security-opt seccomp=default.json myapp:latest

Custom seccomp profile (whitelist approach)

{ "defaultAction": "SCMP_ACT_ERRNO", "architectures": ["SCMP_ARCH_X86_64"], "syscalls": [ { "names": ["read", "write", "open", "close", "stat"], "action": "SCMP_ACT_ALLOW" } ] }

Apply custom profile

docker run --security-opt seccomp=./custom-seccomp.json myapp:latest

Kubernetes Pod Security Standards:

apiVersion: v1
kind: Pod
metadata:
  name: restricted-pod
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: myapp:latest
    securityContext:
      runAsNonRoot: true
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]

Network Security: Isolation and Segmentation

Custom Bridge Networks

Isolate container groups:

# Create isolated networks
docker network create --driver bridge frontend-net
docker network create --driver bridge backend-net
docker network create --driver bridge db-net

Run containers on specific networks

docker run --network=frontend-net nginx:alpine docker run --network=backend-net --network=db-net api-server:latest docker run --network=db-net postgres:15-alpine

Network segmentation rules:

  • Frontend → Backend only
  • Backend → Database only
  • Database isolated (no external access)
  • Use network policies to enforce

Network Policies in Kubernetes

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-policy
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: database
    ports:
    - protocol: TCP
      port: 5432

Encrypt Inter-Container Communication

Use service meshes for mTLS:

# Istio sidecar injection
apiVersion: v1
kind: Pod
metadata:
  name: secure-app
  labels:
    app: myapp
  annotations:
    sidecar.istio.io/inject: "true"
spec:
  containers:
  - name: app
    image: myapp:latest

Benefits:

  • Automatic TLS certificate rotation
  • Encrypted traffic between all services
  • Zero-trust networking
  • Traffic observability

Secrets Management: Never Hardcode Credentials

Docker Secrets (Swarm Mode)

# Create secret from file
echo "db_password_here" | docker secret create db_password -

Use secret in service

docker service create
--name api
--secret db_password
myapp:latest

Access in container (read-only file)

cat /run/secrets/db_password

Kubernetes Secrets with Encryption at Rest

# Enable encryption at rest (kube-apiserver flag)
--encryption-provider-config=/etc/kubernetes/encryption-config.yaml

Encryption config

apiVersion: apiserver.config.k8s.io/v1 kind: EncryptionConfiguration resources:

  • resources:
    • secrets providers:
    • aescbc: keys:
      • name: key1 secret: <base64-encoded-32-byte-key>
    • identity: {}

Use secrets in pods:

apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: myapp:latest
    env:
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: password

External Secrets Operators

Integrate with vault services:

# External Secrets Operator with AWS Secrets Manager
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-secrets
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: SecretStore
  target:
    name: app-secret
  data:
  - secretKey: db-password
    remoteRef:
      key: prod/db/password

Supported backends:

  • AWS Secrets Manager
  • HashiCorp Vault
  • Azure Key Vault
  • Google Secret Manager
  • 1Password, Doppler, etc.

Resource Limits: Preventing DoS

CPU and Memory Constraints

# Docker resource limits
docker run -d \
  --memory="512m" \
  --memory-swap="512m" \
  --cpus="0.5" \
  --pids-limit=100 \
  myapp:latest

Kubernetes resource quotas:

apiVersion: v1
kind: Pod
metadata:
  name: resource-limited
spec:
  containers:
  - name: app
    image: myapp:latest
    resources:
      requests:
        memory: "256Mi"
        cpu: "250m"
      limits:
        memory: "512Mi"
        cpu: "500m"

Prevent Fork Bombs

# Limit number of processes
docker run --pids-limit=200 myapp:latest

Kubernetes

securityContext: sysctls:

  • name: kernel.pid_max value: "200"

Compliance and Auditing

CIS Docker Benchmark

Automated compliance checking:

# Install Docker Bench for Security
git clone https://github.com/docker/docker-bench-security.git
cd docker-bench-security
sudo sh docker-bench-security.sh

Example output

[PASS] 1.1.1 Ensure a separate partition for containers has been created [WARN] 1.1.2 Ensure only trusted users are allowed to control Docker daemon [PASS] 4.1 Ensure that a user for the container has been created [FAIL] 4.5 Ensure Content trust for Docker is Enabled

Runtime Monitoring with Falco

Detect anomalous container behavior:

# Falco rules for container security
- rule: Write below root
  desc: Detect writes below root directory
  condition: >
    container and evt.type = open and
    evt.arg.flags contains O_WRONLY and
    fd.name startswith /
  output: "Write below root (user=%user.name command=%proc.cmdline file=%fd.name)"
  priority: WARNING
  • rule: Container run as root desc: Detect container running as root condition: container and user.uid = 0 output: "Container running as root (container=%container.name image=%container.image.repository)" priority: WARNING

Audit Logging

Enable comprehensive logging:

# Docker daemon audit logging
dockerd --log-driver=syslog --log-opt syslog-address=udp://logserver:514

Kubernetes audit policy

apiVersion: audit.k8s.io/v1 kind: Policy rules:

  • level: Metadata resources:
    • group: "" resources: ["pods", "secrets"]

Production Security Checklist

Before deploying to production, verify:

Image Security:

  • Use minimal base images (distroless/alpine)
  • Multi-stage builds implemented
  • No secrets in image layers
  • Images scanned for vulnerabilities (Trivy/Snyk)
  • Content trust enabled
  • Images signed and verified

Runtime Security:

  • Run as non-root user
  • Read-only root filesystem
  • Dropped unnecessary capabilities
  • Seccomp/AppArmor profiles applied
  • Resource limits configured
  • PID limits enforced

Network Security:

  • Network segmentation implemented
  • Network policies enforced
  • No host network mode
  • mTLS between services

Secrets Management:

  • No hardcoded secrets
  • External secrets manager integrated
  • Secrets encrypted at rest
  • Secret rotation implemented

Monitoring:

  • Runtime security monitoring (Falco)
  • Audit logging enabled
  • Anomaly detection configured
  • Incident response plan documented

Real-World Example: Secure Microservice

Complete production-ready configuration:

# Dockerfile
FROM golang:1.21-alpine AS builder
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o app

FROM gcr.io/distroless/static-debian11 COPY --from=builder /build/app /app USER nonroot:nonroot ENTRYPOINT ["/app"]

# kubernetes/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: secure-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: secure-api
  template:
    metadata:
      labels:
        app: secure-api
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 65532
        fsGroup: 65532
        seccompProfile:
          type: RuntimeDefault
      containers:
      - name: api
        image: myregistry.com/secure-api:v1.0
        securityContext:
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
          capabilities:
            drop: ["ALL"]
            add: ["NET_BIND_SERVICE"]
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"
        ports:
        - containerPort: 8080
        env:
        - name: DB_HOST
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: db-host
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: password
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5

Conclusion

Container security isn't optional—it's foundational to modern infrastructure. The patterns covered here represent industry best practices validated across thousands of production deployments.

Key takeaways:

  • Start with minimal images - Less code = fewer vulnerabilities
  • Enforce non-root users - Privilege escalation is the #1 attack vector
  • Scan continuously - New vulnerabilities emerge daily
  • Isolate networks - Assume breach, limit blast radius
  • Never hardcode secrets - Use external managers with rotation
  • Monitor runtime behavior - Detect anomalies before incidents

Security is a journey, not a destination. Review your configurations quarterly, update dependencies monthly, and scan on every commit.

Next steps:

  • Run Docker Bench Security against your infrastructure
  • Implement automated vulnerability scanning in CI/CD
  • Deploy runtime security monitoring (Falco)
  • Document incident response procedures

Resources:

Found this helpful? Share it!

Related Articles

S

Written by StaticBlock Editorial

StaticBlock Editorial is a technical writer and software engineer specializing in web development, performance optimization, and developer tooling.