name: container-security-guide description: Docker and Kubernetes security hardening guide covering images, builds, runtime, and orchestration tags: [ai, security]
Container Security Guide
Overview
Containers introduce a unique attack surface spanning the image build pipeline, container runtime, orchestration layer, and host OS. Security must be applied at every stage of the container lifecycle:
| Stage | Threats | Key Controls |
|---|---|---|
| Build | Malicious base images, embedded secrets, bloat | Minimal images, scanning, signing |
| Registry | Image tampering, unauthorized access | Content trust, access control |
| Deploy | Misconfigured pods, excessive privileges | Admission control, PodSecurity |
| Runtime | Container escape, lateral movement, cryptomining | Seccomp, AppArmor, monitoring |
| Orchestration | RBAC bypass, API server exposure, secret leaks | NetworkPolicy, RBAC, external secrets |
This guide covers Docker image and build security, Kubernetes-specific hardening, and runtime protection. It is tailored for teams running containerized workloads on GKE but applies broadly to any Kubernetes distribution.
When to Use This Skill
- You are writing or reviewing Dockerfiles for production services
- You are configuring Kubernetes security settings (PodSecurityStandards, NetworkPolicies, RBAC)
- You need to harden a container runtime environment against known attack vectors
- You are building a CI/CD pipeline and need image scanning and signing gates
- You are investigating a container security incident or preparing for an audit
- You want to implement defense-in-depth for containerized microservices
How It Works
Step 1: Scan Container Images
Scan images for known vulnerabilities before they reach production:
# Trivy: comprehensive vulnerability scanner
trivy image --severity HIGH,CRITICAL faos-api:latest
# Grype: fast alternative scanner
grype faos-api:latest --only-fixed --fail-on high
# Snyk: SaaS scanner with fix suggestions
snyk container test faos-api:latest --severity-threshold=high
# GCP Artifact Registry: automatic scanning
gcloud artifacts docker images list-vulnerabilities \
REGION-docker.pkg.dev/PROJECT/REPO/my-app:latest \
--format=json | jq '.[] | select(.vulnerability.effectiveSeverity == "CRITICAL")'
Integrate scanning into CI/CD pipelines with a gate:
# GitHub Actions: scan and fail on critical vulnerabilities
- name: Scan container image
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.IMAGE }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1' # Fail the build on findings
Step 2: Harden Dockerfiles
Build minimal, secure container images following these principles:
- Use minimal base images (distroless, Alpine, or scratch)
- Run as non-root user
- Use multi-stage builds to exclude build tools from production images
- Never embed secrets, credentials, or private keys in images
- Pin base image versions with digest for reproducibility
- Set the filesystem to read-only where possible
- Include health checks for orchestrator integration
Step 3: Apply PodSecurityStandards
Kubernetes PodSecurityStandards (PSS) define three progressive security profiles:
| Profile | Level of Security | Use Case |
|---|---|---|
| Privileged | None | System-level workloads (kube-system) |
| Baseline | Moderate | General workloads, prevents known escalations |
| Restricted | Strict | Security-sensitive and multi-tenant workloads |
Apply PSS enforcement at the namespace level:
# Enforce restricted profile on production namespace
kubectl label namespace faos-api \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restricted
Step 4: Configure NetworkPolicies
By default, all pods in a Kubernetes cluster can communicate with all other pods. NetworkPolicies implement microsegmentation:
- Start with a default-deny policy for all namespaces
- Explicitly allow only required communication paths
- Use label selectors for fine-grained pod-to-pod rules
- Allow DNS resolution (kube-dns) in every policy
Step 5: Enable Runtime Monitoring
Runtime security tools detect anomalous container behavior:
# Falco: runtime threat detection
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm install falco falcosecurity/falco \
--namespace falco-system \
--create-namespace \
--set falcosidekick.enabled=true \
--set falcosidekick.config.slack.webhookurl=$SLACK_WEBHOOK
# Falco detects:
# - Shell spawned in container
# - Sensitive file access (/etc/shadow, /etc/passwd)
# - Unexpected network connections
# - Privilege escalation attempts
# - Cryptocurrency miner signatures
Runtime protection layers:
| Technology | Protection Level | Performance Impact |
|---|---|---|
| Seccomp | System call filtering | Minimal |
| AppArmor | Mandatory access control profiles | Low |
| SELinux | Type enforcement and RBAC | Low |
| gVisor | User-space kernel (full syscall interception) | Moderate |
| Kata | VM-level isolation per container | Higher |
Examples
Example 1: Secure Dockerfile for Python FastAPI App
# ============================================
# Stage 1: Build dependencies
# ============================================
FROM python:3.12-slim AS builder
# Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# Create virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r requirements.txt
# ============================================
# Stage 2: Production image
# ============================================
FROM python:3.12-slim AS production
# Security: Create non-root user
RUN groupadd -r appuser && useradd -r -g appuser -d /app -s /sbin/nologin appuser
# Install only runtime dependencies (no gcc/build tools)
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 \
curl \
&& rm -rf /var/lib/apt/lists/*
# Copy virtual environment from builder
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Copy application code
WORKDIR /app
COPY --chown=appuser:appuser ./src ./src
# Security: No secrets in image (use env vars or mounted secrets)
# Security: Read-only filesystem compatible
# Security: Drop all capabilities in K8s securityContext
# Health check for Kubernetes probes
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# Security: Run as non-root user
USER appuser
# Security: Use exec form (no shell injection risk)
ENTRYPOINT ["uvicorn", "src.main:app"]
CMD ["--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
Security features in this Dockerfile:
| Feature | Security Benefit |
|---|---|
| Multi-stage build | Build tools not in production image (smaller attack surface) |
python:3.12-slim |
Minimal base image (fewer vulnerabilities) |
| Non-root user | Prevents container escape to host root |
--no-cache-dir |
No pip cache (smaller image, no cached credentials) |
--no-install-recommends |
Minimal OS packages |
rm -rf /var/lib/apt |
No package manager cache |
| Exec form ENTRYPOINT | No shell injection via CMD override |
| HEALTHCHECK | Enables K8s liveness/readiness probes |
Example 2: Kubernetes NetworkPolicy for Namespace Isolation
# 1. Default deny all ingress and egress traffic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: faos-api
spec:
podSelector: {} # Applies to all pods in namespace
policyTypes:
- Ingress
- Egress
---
# 2. Allow DNS resolution (required for service discovery)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: faos-api
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
# 3. Allow ingress from gateway to API pods only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-gateway-to-api
namespace: faos-api
spec:
podSelector:
matchLabels:
app: faos-api
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: faos-gateway
podSelector:
matchLabels:
app: gateway
ports:
- protocol: TCP
port: 8000
---
# 4. Allow API pods to reach database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-to-database
namespace: faos-api
spec:
podSelector:
matchLabels:
app: faos-api
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/8 # Cloud SQL private IP range
ports:
- protocol: TCP
port: 5432 # PostgreSQL
---
# 5. Allow API pods to reach Redis
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-to-redis
namespace: faos-api
spec:
podSelector:
matchLabels:
app: faos-api
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: faos-cache
podSelector:
matchLabels:
app: redis
ports:
- protocol: TCP
port: 6379
Example 3: PodSecurityStandard (Restricted) Configuration
# Pod specification compliant with PSS restricted profile
apiVersion: apps/v1
kind: Deployment
metadata:
name: faos-api
namespace: faos-api
spec:
replicas: 3
selector:
matchLabels:
app: faos-api
template:
metadata:
labels:
app: faos-api
spec:
# Security: Use dedicated service account (not default)
serviceAccountName: my-app-sa
automountServiceAccountToken: false # Disable unless needed
# Security: Prevent privilege escalation via hostPID/hostNetwork
hostPID: false
hostNetwork: false
hostIPC: false
# Security: Set filesystem group for shared volumes
securityContext:
runAsNonRoot: true
runAsUser: 65534 # nobody user
runAsGroup: 65534
fsGroup: 65534
seccompProfile:
type: RuntimeDefault
containers:
- name: faos-api
image: REGION-docker.pkg.dev/PROJECT/REPO/my-app@sha256:abc123...
ports:
- containerPort: 8000
protocol: TCP
# Security: Container-level security context
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65534
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
# Resource limits (prevent resource abuse)
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
# Health probes
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
# Mount secrets from external secret manager (not K8s Secrets)
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: faos-api-secrets
key: database-url
# Writable directories for temp files (read-only root filesystem)
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /app/.cache
volumes:
- name: tmp
emptyDir:
sizeLimit: 100Mi
- name: cache
emptyDir:
sizeLimit: 50Mi
Best Practices
Do This
- Use distroless or slim base images to minimize the attack surface
- Scan images in CI/CD and block deployments with critical or high vulnerabilities
- Sign images with cosign or Notary and verify signatures with Binary Authorization
- Run all containers as non-root with
runAsNonRoot: true - Drop all Linux capabilities with
capabilities.drop: [ALL] - Set
readOnlyRootFilesystem: trueand mount writable directories as emptyDir volumes - Apply PodSecurityStandards at
restrictedlevel for production namespaces - Implement default-deny NetworkPolicies in every namespace
- Use external secret managers (GCP Secret Manager, HashiCorp Vault) instead of K8s Secrets
- Pin image references by digest (
@sha256:...) rather than mutable tags - Enable Seccomp profiles (at minimum
RuntimeDefault) for all containers - Implement pod disruption budgets alongside security to maintain availability
Don't Do This
- Do not use
latesttag for production images -- it is mutable and unpredictable - Do not run containers as root or with
privileged: true - Do not embed secrets, API keys, or credentials in Docker images
- Do not expose the Docker socket (
/var/run/docker.sock) to containers - Do not use
hostPID,hostNetwork, orhostIPCunless absolutely required - Do not skip NetworkPolicies -- default Kubernetes networking allows unrestricted pod-to-pod traffic
- Do not grant
cluster-adminClusterRole to application service accounts - Do not auto-mount service account tokens (
automountServiceAccountToken: falseby default) - Do not install shells (bash, sh) in production images if they are not needed
- Do not use package managers (apt, apk) at runtime -- install everything at build time
Security Checklist
Image Security
- Base images are minimal (distroless, Alpine, or slim variants)
- Images are scanned for vulnerabilities in CI/CD with a blocking gate
- No secrets, credentials, or private keys are embedded in images
- Multi-stage builds exclude build tools from production images
- Images are signed and verified before deployment (cosign / Binary Authorization)
- Image references use digests (
@sha256:...) not mutable tags - Base images are regularly updated to include security patches
Build Security
- Dockerfiles use a non-root USER directive
-
.dockerignoreexcludes.env,.git,node_modules, and other sensitive files - Build arguments do not contain secrets (use BuildKit secret mounts instead)
- Layer caching does not leak sensitive data between stages
- HEALTHCHECK is defined for orchestrator integration
Runtime Security
- All containers run as non-root (
runAsNonRoot: true) - All capabilities are dropped (
capabilities.drop: [ALL]) - Root filesystem is read-only (
readOnlyRootFilesystem: true) - Seccomp profile is set to
RuntimeDefaultor a custom profile - Resource requests and limits are defined for all containers
- Service account tokens are not auto-mounted unless required
Kubernetes Security
- PodSecurityStandards are enforced at
restrictedorbaselinelevel - Default-deny NetworkPolicies are applied in every namespace
- RBAC follows least privilege -- no broad ClusterRole bindings
- Admission controllers validate configurations before deployment
- Secrets are managed externally (GCP Secret Manager, Vault, ExternalSecrets)
- Pod disruption budgets ensure availability during security updates
- Runtime monitoring (Falco or equivalent) is deployed
Related Skills
- @cis-benchmarks -- CIS Docker and Kubernetes Benchmark automated scanning
- @cloud-security-patterns -- cloud-native security architectures for GCP, AWS, Azure
- @owasp-top10 -- application-layer security for containerized web applications
Additional Resources
- NIST SP 800-190 -- Application Container Security Guide
- Kubernetes Pod Security Standards -- official PSS documentation
- Docker Security Best Practices -- official Docker guidance
- Kubernetes Network Policies -- NetworkPolicy reference
- Falco Runtime Security -- open-source container runtime threat detection
- cosign -- container image signing and verification
- Trivy -- comprehensive container vulnerability scanner
- GKE Security Overview -- GKE-specific security features