Introduction
Container security spans the entire application lifecycleโfrom building images to runtime execution. With 72% of companies experiencing at least one container security incident in the past year, implementing comprehensive security measures is no longer optional.
Key Statistics:
- 75% of container images contain known vulnerabilities
- Average time to patch critical vulnerabilities: 69 days
- Runtime container attacks increased 300% in 2024
- Companies with mature container security reduce breach costs by 60%
Container Security Layers
1. Image Security
Building Secure Images
# Use specific versions, never :latest
FROM node:20-alpine3.19@sha256:abc123...
# Create non-root user
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
# Set ownership
COPY --chown=appuser:appgroup . /app
# Switch to non-root user
USER appuser
# Use read-only filesystem where possible
WORKDIR /app
Multi-Stage Builds
# Build stage
FROM node:20-alpine3.19 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# Production stage - minimal attack surface
FROM node:20-alpine3.19-slim
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
USER node
EXPOSE 3000
CMD ["node", "dist/index.js"]
2. Image Scanning
Trivy Integration
# .trivy.yaml
format: json
exit-code: 1
severity:
- CRITICAL
- HIGH
vuln-type:
- os
- library
scan:
security-checks:
- vuln
- misconfig
- secret
# Scan images in CI/CD
trivy image --severity CRITICAL,HIGH myapp:latest
# Scan registry
trivy image --severity CRITICAL registry.example.com/myapp:latest
# Scan filesystem
trivy fs --security-checks vuln,config,secret .
GitHub Actions Integration
name: Container Security Scan
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 scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'image'
image-ref: 'myapp:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload results to GitHub
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
CI/CD Pipeline Integration
# GitLab CI example
container_scan:
stage: security
image: docker:24-cli
services:
- docker:24-dind
script:
- docker build -t $IMAGE_TAG .
- |
trivy image \
--severity CRITICAL,HIGH \
--exit-code 1 \
--ignore-unfixed \
$IMAGE_TAG || true
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
3. Runtime Security
Kubernetes Security Context
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10000
runAsGroup: 10000
fsGroup: 10000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
resources:
limits:
memory: "256Mi"
cpu: "500m"
requests:
memory: "128Mi"
cpu: "250m"
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
Falco Runtime Detection
# falco-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: falco-config
namespace: falco
data:
falco.yaml: |
load_plugins: []
rules_file:
- /etc/falco/falco-rules.yaml
- /etc/falco/falco-rules.local.yaml
syscall_event_drops:
actions:
- log
- alert
threshold: 0.1
syscall_event_timeout:
milliseconds: 1000
falco-rules.yaml: |
- rule: Terminal shell in container
desc: A shell was spawned in a container with an attached terminal
condition: >
evt.type = execve and
evt.dir = < and
(container.id != host or (container.id exists and container.id != "")) and
(proc.name = bash or proc.name = sh) and
proc.tty != 0
output: "Terminal shell detected in container (user=%user.name container_id=%container.id container_name=%container.name shell=%proc.name pid=%proc.pid)"
priority: WARNING
tags: [container, shell, mitre_execution]
- rule: Detect write below /etc
desc: An attempt to write to /etc directory
condition: >
evt.type = open_write and
evt.dir = < and
fd.dir = /etc
output: "File below /etc detected (user=%user.name file=%fd.name pid=%proc.pid)"
priority: WARNING
tags: [filesystem, mitre_persistence]
# Install Falco
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm install falco falcosecurity/falco \
--namespace falco \
--create-namespace \
--set falcosecurity.rules.overridefalcoRules=true
OPA Gatekeeper Policies
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-security-labels
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
labels:
- key: "security-level"
- key: "data-classification"
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: object
properties:
key:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.request.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_].key}
missing := required - provided
count(missing) > 0
msg := sprintf("Missing labels: %v", [missing])
}
4. Registry Security
Private Registry with Notary
# Initialize Notary server
docker-compose -f docker-compose-notary.yml up -d
# Sign an image
export DOCKER_CONTENT_TRUST=1
export DOCKER_NOTARY_SERVER="https://notary.example.com"
docker pull myapp:latest
docker tag myapp:latest notary.example.com/myapp:latest
docker push notary.example.com/myapp:latest
Harbor Registry
# harbor-values.yaml
expose:
type: ingress
ingress:
hostname: harbor.example.com
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
tls:
secretName: harbor-tls
persistence:
enabled: true
persistentVolumeClaim:
size: 100Gi
harborAdminPassword: "ChangeThisPassword123!"
5. Secrets Management
External Secrets Operator
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-secrets
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: prod/database
property: username
- secretKey: password
remoteRef:
key: prod/database
property: password
Vault Integration
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
annotations:
vault.hashicorp.com/role: "app-role"
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-inject-secret-config: "secret/data/myapp"
vault.hashicorp.com/agent-inject-template-config: |
{{- with secret "secret/data/myapp" -}}
export DB_PASSWORD="{{ .Data.data.db_password }}"
{{- end }}
Vulnerability Management
Scanning Tools Comparison
| Tool | Type | CI/CD Integration | Enterprise Support | Cost |
|---|---|---|---|---|
| Trivy | Open Source | Excellent | Limited | Free |
| Anchore | Commercial | Excellent | Excellent | $$$ |
| Qualys | Commercial | Good | Excellent | $$$$ |
| Snyk | Commercial | Excellent | Excellent | $$$ |
| Clair | Open Source | Good | Limited | Free |
Patch Management Strategy
# Renovate config for automated updates
{
"packageRules": [
{
"matchPackagePatterns": ["*"],
"matchUpdateTypes": ["patch", "minor"],
"automerge": true,
"requiredStatusChecks": ["security/scan"]
},
{
"matchPackagePatterns": ["node", "nginx"],
"matchUpdateTypes": ["major"],
"schedule": ["before 5am on the first day of the month"]
}
]
}
Best Practices Summary
Build Time
- Use minimal base images (Alpine, distroless)
- Never use
:latesttag - Scan images in CI/CD pipeline
- Sign images with Notary/cosign
- Use multi-stage builds
Runtime
- Run containers as non-root
- Use read-only root filesystem
- Implement network policies
- Enable seccomp and AppArmor
- Set resource limits
Registry
- Use private registry with access control
- Enable vulnerability scanning
- Sign and verify images
- Implement retention policies
Orchestration
- Enable RBAC with least privilege
- Use Pod Security Standards
- Implement network policies
- Enable audit logging
- Regular security assessments
External Resources
- Docker Security Documentation
- Kubernetes Security Best Practices
- CIS Docker Benchmark
- Falco Runtime Security
- Trivy Vulnerability Scanner
Related Articles
- Kubernetes Cost Optimization
- Infrastructure as Code: Terraform
- GitOps: Infrastructure as Code with Git
Comments