Table of Contents
Docker Security Best Practices
1. Don't Run as Root
❌ Bad Practice
FROM ubuntu:latest
RUN apt-get update && apt-get install -y nginx
# Running as root by default!
✅ Good Practice
FROM ubuntu:latest
RUN apt-get update && apt-get install -y nginx
RUN useradd -r -s /bin/false appuser
USER appuser
2. Use Minimal Base Images
# Instead of ubuntu (77MB), use alpine (5MB)
FROM alpine:3.18
# Or distroless for production
FROM gcr.io/distroless/python3-debian11
Secure Image Building
Complete Secure Dockerfile
# Multi-stage build for minimal final image
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Final minimal image
FROM gcr.io/distroless/nodejs18-debian11
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
# Non-root user
USER nonroot:nonroot
# Health check
HEALTHCHECK --interval=30s CMD ["node", "healthcheck.js"]
EXPOSE 3000
CMD ["dist/server.js"]
Image Scanning
# Scan with Trivy
trivy image myapp:latest
# Scan with Docker Scout
docker scout cves myapp:latest
# Scan with Grype
grype myapp:latest
Kubernetes Security
Pod Security Standards
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
Secure Pod Specification
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
limits:
memory: "128Mi"
cpu: "500m"
requests:
memory: "64Mi"
cpu: "250m"
RBAC Configuration
# Read-only Role for developers
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: developer-readonly
rules:
- apiGroups: [""]
resources: ["pods", "services", "configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list"]
---
# Bind role to group
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: developer-binding
namespace: production
subjects:
- kind: Group
name: developers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: developer-readonly
apiGroup: rbac.authorization.k8s.io
Network Policies
# Default deny all ingress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
---
# Allow specific traffic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-api
spec:
podSelector:
matchLabels:
app: api
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- port: 8080
Runtime Security
Tools for Runtime Protection
| Falco | Runtime threat detection |
| Sysdig | Container monitoring |
| Cilium | eBPF-based security |
| OPA/Gatekeeper | Policy enforcement |
Falco Rule Example
- rule: Shell Spawned in Container
desc: Detect shell being spawned in container
condition: >
spawned_process and container and
shell_procs and proc.pname exists and
not proc.pname in (shell_binaries)
output: >
Shell spawned in container
(user=%user.name container=%container.name
shell=%proc.name parent=%proc.pname)
priority: WARNING
Updated: December 2024