Introduction
Container security requires a defense-in-depth approach that spans the entire container lifecycleโfrom image creation to runtime execution. With containers powering modern microservices architectures, understanding how to secure each layer of the container stack is essential for any DevOps engineer or security professional.
This comprehensive guide covers securing containerized applications at every layer: building secure images, scanning for vulnerabilities, implementing runtime protection, managing secrets, and configuring Kubernetes security policies.
The Container Security Landscape
Understanding Container Attack Surfaces
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Container Attack Surface โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Build Time Runtime Network โ
โ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โ
โ โ Base โ โ App โ โ Ingressโ โ
โ โ Image โ โ Process โ โ /Egressโ โ
โ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โ
โ โ โ โ โ
โ โผ โผ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Container Runtime โ โ
โ โ (Docker, containerd, cri-o) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Kubernetes / Orchestrator โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Each phase presents unique security challenges that must be addressed comprehensively.
Image Security
Building Secure Dockerfiles
The foundation of container security starts with how you build your images. Follow these essential practices:
# BAD: Using latest tag and running as root
FROM python:latest
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
# GOOD: Specific version, minimal base, non-root user
FROM python:3.11-slim-bookworm@sha256:abc123...
# Create dedicated user and group
RUN groupadd --gid 1000 appgroup && \
useradd --uid 1000 --gid appgroup --shell /bin/bash --create-home appuser
# Install only what's needed
COPY requirements.txt .
RUN pip install --no-cache-dir --user=appuser -r requirements.txt
# Set working directory
WORKDIR /home/appuser
# Copy application code with correct ownership
COPY --chown=appuser:appgroup . .
# Switch to non-root user
USER appuser
# Expose only necessary ports
EXPOSE 8080
# Use exec form for CMD
CMD ["python", "app.py"]
Multi-Stage Builds for Minimal Images
# Build stage
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
# Final stage - minimal runtime image
FROM gcr.io/distroless/static:nonroot
# Copy only the binary
COPY --from=builder /app/main /main
USER nonroot:root
EXPOSE 8080
ENTRYPOINT ["/main"]
Image Scanning in CI/CD
Integrate security scanning into your pipeline to catch vulnerabilities before deployment:
# .github/workflows/security.yml
name: Container Security
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'image'
image-ref: 'myapp:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
- name: Fail on critical vulnerabilities
run: |
# Check for critical/high vulnerabilities
trivy image --exit-code 1 \
--severity CRITICAL,HIGH \
myapp:${{ github.sha }}
Docker Content Trust
Enable Docker Content Trust to ensure image integrity:
# Enable DCT
export DOCKER_CONTENT_TRUST=1
# Sign images on push
docker push myregistry.io/myapp:latest
# Verify image signatures
docker trust inspect myregistry.io/myapp:latest
Runtime Security
Kubernetes Security Context
Define security contexts at pod and container level:
apiVersion: v1
kind: Pod
metadata:
name: secure-application
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:secure
securityContext:
# Prevent privilege escalation
allowPrivilegeEscalation: false
# Run as non-root
readOnlyRootFilesystem: true
# Drop all capabilities
capabilities:
drop:
- ALL
# Prevent running as privileged
privileged: false
# Seccomp profile
seccompProfile:
type: RuntimeDefault
resources:
limits:
memory: "128Mi"
cpu: "500m"
requests:
memory: "64Mi"
cpu: "250m"
Pod Security Standards
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
spec:
privileged: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
- 'persistentVolumeClaim'
runAsUser:
rule: 'MustRunAsNonRoot'
seLinux:
rule: 'RunAsAny'
supplementalGroups:
rule: 'RunAsAny'
fsGroup:
rule: 'RunAsAny'
Resource Limits and QoS
apiVersion: v1
kind: LimitRange
metadata:
name: resource-limits
spec:
limits:
- max:
memory: "1Gi"
cpu: "1000m"
min:
memory: "64Mi"
cpu: "100m"
default:
memory: "256Mi"
cpu: "500m"
defaultRequest:
memory: "128Mi"
cpu: "250m"
type: Container
Network Security
Kubernetes Network Policies
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: app-network-policy
spec:
podSelector:
matchLabels:
app: myapp
policyTypes:
- Ingress
- Egress
# Ingress rules
ingress:
# Allow from ingress controller
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 8080
# Allow from same namespace
- from:
- podSelector: {}
# Egress rules
egress:
# Allow DNS
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# Allow to API server
- to:
- kubernetes.io/hostname: master
ports:
- protocol: TCP
port: 6443
# Allow to same namespace
- to:
- podSelector: {}
Service Mesh Security
With Istio, you can implement mutual TLS (mTLS):
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
spec:
mtls:
mode: STRICT
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: app-authz
spec:
selector:
matchLabels:
app: myapp
rules:
- from:
- source:
principals: ["cluster.local/ns/default/sa/allowed-service"]
to:
- operation:
methods: ["GET"]
paths: ["/api/*"]
Secrets Management
Kubernetes Secrets vs External Secrets
# Native Kubernetes Secret (base64 encoded, not encrypted at rest by default)
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
username: YWRtaW4=
password: cGFzc3dvcmQ=
---
# Better: Use External Secrets Operator with Vault
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: vault-db-credentials
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: database/prod
property: username
- secretKey: password
remoteRef:
key: database/prod
property: password
HashiCorp Vault Integration
# Vault Secret Engine
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: vault-backend
spec:
provider:
vault:
server: "https://vault.example.com:8200"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: kubernetes
role: "myapp-role"
Docker Secrets for Swarm
# Create secret
echo "my-password" | docker secret create db_password -
# Use in service
docker service create \
--name myapp \
--secret db_password \
myapp:latest
# Access in container
# /run/secrets/db_password
Container Runtime Security
gVisor for Container Isolation
gVisor provides an additional security layer by intercepting system calls:
# Kubernetes with gVisor (runsc)
apiVersion: v1
kind: Pod
metadata:
name: sandboxed-app
spec:
runtimeClassName: gvisor
containers:
- name: app
image: myapp:latest
Falco for Runtime Monitoring
# Falco rules for suspicious activity
apiVersion: falco.org/v1
kind: FalcoRules
metadata:
name: falco-rules
rules:
- rule: Terminal shell in container
desc: >
Detect terminal shell spawn in container
condition: >
container.id != host and
proc.name = bash and
proc.tty != 0
output: >
Terminal shell in container (user=%user.name
container_id=%container.id
container_name=%container.name
proc.name=%proc.name)
priority: WARNING
- rule: Write to root filesystem
desc: >
Detect write to root filesystem
condition: >
container.id != host and
fd.directory = / and
evt.type = write
output: >
Write to root filesystem (user=%user.name
container_id=%container.id
proc.name=%proc.name)
priority: CRITICAL
Supply Chain Security
Sigstore and Cosign for Image Signing
# Generate key pair
cosign generate-key-pair
# Sign container image
cosign sign myregistry.io/myapp:latest
# Verify signature
cosign verify myregistry.io/myapp:latest
# Store in transparency log
cosign sign --tlog-upload myregistry.io/myapp:latest
SBOM (Software Bill of Materials)
# Generate SBOM with Syft
syft myapp:latest -o cyclonedx-json > sbom.json
# Scan SBOM for vulnerabilities
grype sbom.json
Best Practices Summary
Build Time Security
- Use specific image tags - Never use
latest - Scan for vulnerabilities - Integrate Trivy/Snyk in CI
- Sign images - Use Cosign for supply chain security
- Minimal images - Use distroless or Alpine
- Multi-stage builds - Reduce final image size
Runtime Security
- Run as non-root - Always use security context
- Read-only filesystem - Prevent runtime modifications
- Drop capabilities - Never run with CAP_SYS_ADMIN
- Resource limits - Prevent DoS attacks
- Network policies - Implement zero-trust networking
Secrets Management
- External secrets - Use Vault or AWS Secrets Manager
- Never bake secrets - Don’t include in images
- Rotate secrets - Implement automatic rotation
- Network policies - Limit secret access
Conclusion
Container security is not a single product or configurationโit’s a comprehensive approach that spans the entire container lifecycle. By implementing defense in depth, following the principle of least privilege, and continuously scanning for vulnerabilities, you can significantly reduce the attack surface of your containerized applications.
Key takeaways:
- Start with secure base images and build patterns
- Implement runtime security through Kubernetes policies
- Use secrets management solutions, never bake secrets
- Monitor runtime behavior for anomalies
- Sign and verify images throughout the supply chain
Resources
- Docker Security Documentation - Official Docker security guide
- Kubernetes Security Documentation - K8s security best practices
- CIS Docker Benchmark - Docker security configuration standards
- NIST Container Security Guide - Government container security standards
- Falco - Runtime security monitoring
- Trivy - Vulnerability scanner for containers
- Cosign - Container image signing
Comments