Back to Blog
Security9 min

Docker Security Best Practices: Hardening Your Containers

Essential security practices for Docker containers and Kubernetes deployments to prevent vulnerabilities and attacks.

By DevOps TeamFebruary 16, 2026

Containers revolutionized application deployment, but they also introduced new security challenges. A misconfigured Docker container can expose your entire infrastructure to attack.

Docker Image Security

1. Use Official Base Images

# ❌ Bad: Unknown/untrusted image
FROM random-user/ubuntu:latest

# ✅ Good: Official minimal image
FROM node:18-alpine

2. Specify Exact Versions

# ❌ Bad: Latest tag (unpredictable updates)
FROM node:latest

# ✅ Good: Pinned version with digest
FROM node:18.19.0-alpine3.19@sha256:...

3. Use Minimal Images

Smaller images = smaller attack surface.

# Standard Ubuntu: 77MB
FROM ubuntu:22.04

# Alpine: 5.5MB (14x smaller!)
FROM alpine:3.19

# Distroless: Even smaller, no shell
FROM gcr.io/distroless/nodejs:18

4. Multi-Stage Builds

# ❌ Bad: Build tools in production image
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install  # Includes devDependencies
COPY . .
CMD ["node", "server.js"]

# ✅ Good: Build dependencies excluded from final image
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .

FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app .
USER node  # Non-root user
CMD ["node", "server.js"]

5. Scan for Vulnerabilities

# Scan with Trivy
trivy image your-image:tag

# Scan with Docker Scout
docker scout cves your-image:tag

# Scan with Snyk
snyk container test your-image:tag

Runtime Security

1. Run as Non-Root

# ❌ Bad: Runs as root (UID 0)
FROM node:18-alpine
WORKDIR /app
COPY . .
CMD ["node", "server.js"]

# ✅ Good: Runs as unprivileged user
FROM node:18-alpine
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001
WORKDIR /app
COPY --chown=nodejs:nodejs . .
USER nodejs
CMD ["node", "server.js"]

2. Read-Only Root Filesystem

docker run --read-only \
  --tmpfs /tmp \
  your-image:tag

3. Drop Unnecessary Capabilities

docker run \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  your-image:tag

4. Limit Resources

docker run \
  --memory=512m \
  --cpus=0.5 \
  --pids-limit=100 \
  your-image:tag

Secrets Management

❌ Never Hardcode Secrets

# ❌ NEVER DO THIS
ENV DB_PASSWORD=supersecret123
ENV API_KEY=abc123def456

✅ Use Docker Secrets or Environment Variables

# Docker Swarm secrets
docker service create \
  --secret db_password \
  your-service

# Kubernetes secrets
kubectl create secret generic db-credentials \
  --from-literal=password=supersecret

✅ Use .dockerignore

# .dockerignore
.env
.git
.github
node_modules
*.log
.DS_Store
secrets/

Network Security

1. Use Custom Networks

# Create isolated network
docker network create --driver bridge app-network

# Run containers on custom network
docker run --network=app-network app-container
docker run --network=app-network db-container

2. Limit Port Exposure

# ❌ Bad: Binds to all interfaces
docker run -p 3000:3000 app

# ✅ Good: Binds to localhost only
docker run -p 127.0.0.1:3000:3000 app

Kubernetes Security

1. Pod Security Standards

apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    fsGroup: 2000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: your-app:1.0
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop:
          - ALL
    resources:
      limits:
        memory: "512Mi"
        cpu: "500m"

2. Network Policies

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-network-policy
spec:
  podSelector:
    matchLabels:
      app: api
  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

3. RBAC

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]  # Read-only

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
subjects:
- kind: ServiceAccount
  name: app-service-account
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

Supply Chain Security

1. Sign Your Images

# Sign with Docker Content Trust
export DOCKER_CONTENT_TRUST=1
docker push your-image:tag

# Verify signature
docker trust inspect your-image:tag

2. Use Private Registries

# Use AWS ECR, Google Artifact Registry, or Harbor
docker tag your-app:latest your-registry.io/your-app:1.0
docker push your-registry.io/your-app:1.0

3. Scan on Build and Deploy

# GitHub Actions example
name: Build and Scan
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Build image
        run: docker build -t app:$GITHUB_SHA .
      - name: Scan image
        run: |
          docker run aquasec/trivy image app:$GITHUB_SHA
          # Fail if high/critical vulnerabilities found
      - name: Push to registry
        if: success()
        run: docker push registry/app:$GITHUB_SHA

Logging & Monitoring

# Enable Docker logging
docker run \
  --log-driver=json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  your-app

# Monitor container metrics
docker stats

# Audit Docker daemon events
docker events --filter 'event=start'

Security Checklist

  • ✅ Use official, minimal base images
  • ✅ Pin image versions with digests
  • ✅ Multi-stage builds to reduce image size
  • ✅ Run containers as non-root user
  • ✅ Read-only root filesystem where possible
  • ✅ Drop all capabilities, add only needed ones
  • ✅ Limit CPU/memory resources
  • ✅ No secrets in images or environment variables
  • ✅ Use .dockerignore to exclude sensitive files
  • ✅ Custom networks, limit port exposure
  • ✅ Regular vulnerability scanning
  • ✅ Image signing and verification
  • ✅ Network policies in Kubernetes
  • ✅ RBAC for least-privilege access

Conclusion

Container security is a multi-layered challenge. By following these best practices, you significantly reduce your attack surface and protect your infrastructure.

Need a comprehensive security audit of your Dockerfiles and Kubernetes configs? Get a Docker/K8s audit and receive detailed security recommendations within 24 hours.

DockersecuritycontainersKubernetesDevOps

Ready to improve your code?

Get an AI-powered code audit with actionable recommendations. Results in 24 hours.

Start Your Audit