Container Security and Vulnerability Management
Container security kept me awake at night during my first production deployment. A single vulnerable base image could expose your entire application stack. I’ve since learned that security isn’t something you add later - it must be built into every step of your container workflow.
Understanding Container Attack Surfaces
Containers share the host kernel, which creates unique security considerations. The attack surface includes:
- Base image vulnerabilities
- Application dependencies
- Container runtime configuration
- Host system security
- Network exposure
- Secrets management
I’ve seen organizations focus only on application security while ignoring base image vulnerabilities. This is like locking your front door while leaving windows open.
Vulnerability Scanning in CI/CD Pipelines
Automated vulnerability scanning catches security issues before they reach production. I integrate scanning at build time and runtime monitoring:
# GitHub Actions workflow with security scanning
name: Container Security Pipeline
on:
push:
branches: [ main, develop ]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build container image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myapp:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH,MEDIUM'
exit-code: '1'
- name: Upload scan results
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: 'trivy-results.sarif'
For detailed scanning, I use Trivy’s comprehensive modes:
# Scan for vulnerabilities and misconfigurations
trivy image --severity HIGH,CRITICAL myapp:latest
# Include secret detection
trivy image --scanners vuln,secret myapp:latest
# Scan Dockerfile for best practices
trivy config Dockerfile
Implementing Image Signing and Verification
Image signing ensures the integrity and authenticity of your container images. I use Cosign for its simplicity:
# Generate a key pair for signing
cosign generate-key-pair
# Sign an image after building
docker build -t myregistry.io/myapp:v1.0.0 .
cosign sign --key cosign.key myregistry.io/myapp:v1.0.0
# Verify image signature before deployment
cosign verify --key cosign.pub myregistry.io/myapp:v1.0.0
For production environments, I integrate signature verification into deployment pipelines:
# Kubernetes admission controller policy
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signatures
spec:
validationFailureAction: enforce
rules:
- name: verify-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "myregistry.io/*"
attestors:
- entries:
- keys:
publicKeys: |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----
Secure Base Image Selection
Choosing secure base images is your first line of defense:
# Avoid generic latest tags
FROM node:latest # Bad - unpredictable
# Use specific, recent versions
FROM node:18.17.1-alpine3.18 # Better
# Even better - use digest for immutability
FROM node:18.17.1-alpine3.18@sha256:f77a1aef2da8d83e45ec990f45df906f9c3e8b8c0c6b2b5b5c5c5c5c5c5c5c5c
For maximum security, I prefer distroless images:
# Multi-stage build with distroless runtime
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM gcr.io/distroless/nodejs18-debian11
COPY --from=builder /app/node_modules ./node_modules
COPY . .
CMD ["server.js"]
Distroless images contain only your application and runtime dependencies. No shell, no package manager, no debugging tools that attackers could exploit.
Runtime Security Configuration
Never run containers as root unless absolutely necessary:
# Create and use non-root user
FROM python:3.11-slim
# Create app user with specific UID/GID
RUN groupadd -r -g 1001 appuser && \
useradd -r -u 1001 -g appuser appuser
WORKDIR /app
# Install dependencies as root
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy app files and set ownership
COPY . .
RUN chown -R appuser:appuser /app
# Switch to non-root user
USER appuser
CMD ["python", "app.py"]
When deploying, I use security contexts to enforce additional restrictions:
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
spec:
template:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1001
runAsGroup: 1001
containers:
- name: app
image: myregistry.io/myapp:v1.0.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
limits:
memory: "512Mi"
cpu: "500m"
Secrets Management
Never include secrets in container images:
# Bad - secret in image
FROM alpine
ENV API_KEY=sk-1234567890abcdef
CMD ["./app"]
# Good - secret injected at runtime
FROM alpine
ENV API_KEY=""
CMD ["./app"]
For Kubernetes deployments, use Secrets:
# Kubernetes Secret
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
api-key: c2stMTIzNDU2Nzg5MGFiY2RlZg== # base64 encoded
---
# Deployment using the secret
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
template:
spec:
containers:
- name: app
image: myapp:latest
env:
- name: API_KEY
valueFrom:
secretKeyRef:
name: app-secrets
key: api-key
Network Security and Isolation
Use NetworkPolicies to control traffic flow:
# Network policy for database isolation
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: database-policy
spec:
podSelector:
matchLabels:
app: database
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: api
ports:
- protocol: TCP
port: 5432
egress:
- to: []
ports:
- protocol: TCP
port: 53 # DNS only
This policy allows only API pods to connect to the database and restricts database egress to DNS queries only.
Security isn’t a one-time setup - it’s an ongoing process. Regular scanning, monitoring, and updates are essential for maintaining a secure container environment. In the next part, I’ll cover container orchestration and how to manage containers at scale while maintaining security and reliability.