Docker Container Security Nightmares: Defending Against Supply Chain Attacks
Docker has revolutionized software deployment, but it's also created new attack vectors that cybercriminals actively exploit. This cookbook addresses the most critical container security threats of 2024-2025 and provides actionable defense strategies.
The Current Threat Landscape (2024-2025)
Why Containers Are Under Attack
- Supply Chain Complexity: Modern containers pull from dozens of sources
- Developer Convenience Over Security: Fast deployment often skips security checks
- Runtime Privilege Escalation: Container breakouts are increasingly common
- Image Registry Compromise: Attackers target central repositories
- CI/CD Pipeline Infiltration: Automated deployments spread malware quickly
Real-World Attack Statistics
- 78% of organizations experienced container security incidents in 2024
- Average breach cost: $4.45M when containers were involved
- Attack vector distribution: 45% supply chain, 30% runtime exploits, 25% misconfigurations
1. Supply Chain Poisoning Attacks
The Attack Vector: Malicious Base Images
# ❌ DANGEROUS: Popular but compromised image
FROM node:18-alpine
# This image was compromised in Q3 2024 supply chain attack
RUN npm install -g express
COPY . .
CMD ["node", "app.js"]
What happens: Attackers upload lookalike images or compromise popular repositories. When developers pull these images, they unknowingly install backdoors.
Defense Strategy: Image Verification and Signing
# 1. Install cosign for image verification
curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64"
sudo mv cosign-linux-amd64 /usr/local/bin/cosign
sudo chmod +x /usr/local/bin/cosign
# 2. Generate signing keys
cosign generate-key-pair
# 3. Sign your images after building
docker build -t myapp:latest .
cosign sign --key cosign.key myapp:latest
# 4. Verify images before deployment
cosign verify --key cosign.pub myapp:latest
Automated Supply Chain Protection
# .github/workflows/secure-build.yml
name: Secure Container Build
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t ${{ github.repository }}:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ github.repository }}:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
exit-code: '1' # Fail build on HIGH/CRITICAL vulnerabilities
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: 'trivy-results.sarif'
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: ${{ github.repository }}:${{ github.sha }}
format: spdx-json
output-file: sbom.spdx.json
- name: Sign image and SBOM
env:
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
run: |
cosign sign --key env://COSIGN_PRIVATE_KEY ${{ github.repository }}:${{ github.sha }}
cosign attach sbom --sbom sbom.spdx.json ${{ github.repository }}:${{ github.sha }}
2. Runtime Privilege Escalation
The Attack: Container Breakout to Host
# ❌ DANGEROUS: Running as root with excessive privileges
FROM ubuntu:20.04
# Installing everything as root
RUN apt-get update && apt-get install -y \
curl wget sudo systemd
# Application runs as root - major security risk
COPY app.py /app/
CMD ["python3", "/app/app.py"]
What happens: If an attacker compromises the application inside the container, they have root access and can potentially escape to the host system.
Defense: Non-Root Containers with Minimal Privileges
# ✅ SECURE: Distroless image with non-root user
FROM gcr.io/distroless/python3-debian12
# Create non-root user
USER 65534:65534
# Copy only necessary files
COPY --chown=65534:65534 app.py /app/
COPY --chown=65534:65534 requirements.txt /app/
WORKDIR /app
ENTRYPOINT ["python3", "app.py"]
Runtime Security with AppArmor/SELinux
# docker-compose.security.yml
version: '3.8'
services:
app:
image: myapp:latest
security_opt:
- no-new-privileges:true
- apparmor:docker-default
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # Only if needed for port binding
read_only: true
tmpfs:
- /tmp:noexec,nosuid,size=100m
ulimits:
nproc: 65535
nofile:
soft: 65535
hard: 65535
sysctls:
- net.ipv4.ip_unprivileged_port_start=0
Advanced: gVisor Sandboxing
# Install gVisor for additional sandboxing
curl -fsSL https://gvisor.dev/archive.key | sudo gpg --dearmor -o /usr/share/keyrings/gvisor-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/gvisor-archive-keyring.gpg] https://storage.googleapis.com/gvisor/releases release main" | sudo tee /etc/apt/sources.list.d/gvisor.list > /dev/null
sudo apt-get update && sudo apt-get install -y runsc
# Configure Docker to use gVisor
sudo mkdir -p /etc/docker
cat <<EOF | sudo tee /etc/docker/daemon.json
{
"runtimes": {
"runsc": {
"path": "/usr/bin/runsc"
}
}
}
EOF
sudo systemctl restart docker
# Run containers with gVisor
docker run --runtime=runsc -it myapp:latest
3. Secrets Management Disasters
The Problem: Hardcoded Secrets
# ❌ DANGEROUS: Secrets in Dockerfile
FROM node:18-alpine
ENV DATABASE_PASSWORD=super_secret_password_123
ENV API_KEY=sk-1234567890abcdef
COPY . .
RUN npm install
CMD ["npm", "start"]
What happens: Secrets end up in image layers, container environment variables, and logs. They can be extracted even from "removed" layers.
Defense: Proper Secrets Management
# ✅ SECURE: Using build secrets (Docker Buildx)
# syntax=docker/dockerfile:1
FROM node:18-alpine
# Use build-time secrets
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) npm install
COPY . .
CMD ["npm", "start"]
Runtime Secrets with HashiCorp Vault
# app/secrets.py
import hvac
import os
class SecretManager:
def __init__(self):
self.vault_url = os.getenv('VAULT_URL', 'http://vault:8200')
self.vault_token = self._get_vault_token()
self.client = hvac.Client(
url=self.vault_url,
token=self.vault_token
)
def _get_vault_token(self):
# Use Kubernetes service account or AWS IAM role
if os.path.exists('/var/run/secrets/kubernetes.io/serviceaccount/token'):
with open('/var/run/secrets/kubernetes.io/serviceaccount/token') as f:
return f.read().strip()
return os.getenv('VAULT_TOKEN')
def get_secret(self, path, key):
try:
response = self.client.secrets.kv.v2.read_secret_version(path=path)
return response['data']['data'][key]
except Exception as e:
print(f"Failed to retrieve secret: {e}")
return None
# Usage in application
secret_manager = SecretManager()
db_password = secret_manager.get_secret('myapp/database', 'password')
Docker Compose with External Secrets
# docker-compose.secrets.yml
version: '3.8'
services:
app:
image: myapp:latest
secrets:
- database_password
- api_key
environment:
- DATABASE_HOST=postgres
depends_on:
- vault
vault:
image: vault:1.13
cap_add:
- IPC_LOCK
environment:
- VAULT_DEV_ROOT_TOKEN_ID=myroot
- VAULT_DEV_LISTEN_ADDRESS=0.0.0.0:8200
secrets:
database_password:
external: true
api_key:
external: true
4. Image Registry Attacks
The Threat: Registry Compromise
Attackers target container registries to: - Replace legitimate images with malicious versions - Inject malware into popular images - Harvest credentials and secrets from uploaded images
Defense: Private Registry with Access Controls
# Set up secure private registry with Harbor
curl -L https://github.com/goharbor/harbor/releases/download/v2.8.0/harbor-online-installer-v2.8.0.tgz -o harbor.tgz
tar xzf harbor.tgz
cd harbor
# Configure Harbor with HTTPS and RBAC
cat > harbor.yml << 'EOF'
hostname: registry.company.com
http:
port: 80
https:
port: 443
certificate: /data/cert/registry.company.com.crt
private_key: /data/cert/registry.company.com.key
harbor_admin_password: $(openssl rand -base64 32)
database:
password: $(openssl rand -base64 32)
data_volume: /data
trivy:
ignore_unfixed: false
skip_update: false
offline_scan: false
insecure: false
jobservice:
max_job_workers: 10
notification:
webhook_job_max_retry: 10
log:
level: info
local:
rotate_count: 50
rotate_size: 200M
location: /var/log/harbor
_version: 2.8.0
EOF
# Install and start Harbor
sudo ./install.sh --with-notary --with-trivy --with-chartmuseum
Image Scanning and Policy Enforcement
# scripts/policy_enforcement.py
"""
Harbor webhook receiver for policy enforcement
"""
import json
import requests
from flask import Flask, request
import hashlib
app = Flask(__name__)
ALLOWED_VULNERABILITIES = {
'HIGH': 0, # No high vulnerabilities allowed
'MEDIUM': 5, # Max 5 medium vulnerabilities
'LOW': 20 # Max 20 low vulnerabilities
}
REQUIRED_LABELS = [
'maintainer',
'version',
'description'
]
@app.route('/webhook/scan', methods=['POST'])
def handle_scan_result():
"""Process vulnerability scan results from Harbor"""
data = request.json
if data['type'] == 'SCANNING_COMPLETED':
scan_result = data['event_data']['scan_overview']
# Check vulnerability counts
for severity, count in scan_result['summary'].items():
if severity in ALLOWED_VULNERABILITIES:
if count > ALLOWED_VULNERABILITIES[severity]:
# Block deployment
quarantine_image(data['event_data']['repository'])
send_alert(f"Image blocked: too many {severity} vulnerabilities")
return {'status': 'blocked'}, 403
# Check required labels
image_labels = get_image_labels(data['event_data']['repository'])
missing_labels = [label for label in REQUIRED_LABELS if label not in image_labels]
if missing_labels:
quarantine_image(data['event_data']['repository'])
send_alert(f"Image blocked: missing labels {missing_labels}")
return {'status': 'blocked'}, 403
return {'status': 'allowed'}, 200
def quarantine_image(repository):
"""Move image to quarantine project"""
# Implementation depends on your registry API
pass
def get_image_labels(repository):
"""Retrieve image labels from registry"""
# Implementation depends on your registry API
return {}
def send_alert(message):
"""Send alert to security team"""
# Send to Slack, email, or monitoring system
print(f"SECURITY ALERT: {message}")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
5. CI/CD Pipeline Security
The Vulnerability: Compromised Build Environment
# ❌ DANGEROUS: Insecure CI/CD pipeline
name: Deploy
on: push
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: |
# Dangerous: Running untrusted code
curl -sSL https://unknown-site.com/install.sh | sh
docker build -t myapp .
docker push myapp:latest
# No security scanning or verification
Secure CI/CD Pipeline
# ✅ SECURE: Hardened CI/CD pipeline
name: Secure Deploy
on:
push:
branches: [main]
permissions:
contents: read
security-events: write
packages: write
jobs:
security-checks:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for better scanning
- name: Run secret scanning
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: main
head: HEAD
extra_args: --debug --only-verified
- name: Code security scan
uses: github/codeql-action/analyze@v2
with:
languages: python
- name: Build image
run: |
docker build -t ${{ github.repository }}:${{ github.sha }} .
docker tag ${{ github.repository }}:${{ github.sha }} ${{ github.repository }}:latest
- name: Container security scan
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ github.repository }}:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Check image size
run: |
SIZE=$(docker images --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}" | grep ${{ github.repository }} | awk '{print $2}')
if [[ $SIZE =~ "GB" ]]; then
echo "Image too large: $SIZE"
exit 1
fi
- name: Sign container image
env:
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
run: |
cosign sign --key env://COSIGN_PRIVATE_KEY ${{ github.repository }}:${{ github.sha }}
- name: Generate SLSA Provenance
uses: slsa-framework/slsa-github-generator/.github/workflows/[email protected]
with:
image: ${{ github.repository }}
digest: ${{ github.sha }}
registry-username: ${{ github.actor }}
registry-password: ${{ secrets.GITHUB_TOKEN }}
6. Network Security and Runtime Protection
Container Network Isolation
# docker-compose.network-security.yml
version: '3.8'
networks:
frontend:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/24
backend:
driver: bridge
internal: true # No external access
ipam:
config:
- subnet: 172.21.0.0/24
services:
web:
image: nginx:alpine
networks:
- frontend
ports:
- "80:80"
- "443:443"
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
app:
image: myapp:latest
networks:
- frontend
- backend
depends_on:
- database
deploy:
resources:
limits:
memory: 1G
cpus: '1.0'
database:
image: postgres:15-alpine
networks:
- backend # Only internal access
environment:
- POSTGRES_PASSWORD_FILE=/run/secrets/db_password
secrets:
- db_password
volumes:
- db_data:/var/lib/postgresql/data
deploy:
resources:
limits:
memory: 2G
cpus: '1.0'
volumes:
db_data:
secrets:
db_password:
external: true
Runtime Monitoring with Falco
# falco-rules.yaml
- rule: Unexpected Network Activity
desc: Detect network activity from non-web containers
condition: >
(k8s_audit and ka_target_resource="pods") and
(ka_verb="create") and
(not ka_image_repository in (web_repositories))
output: >
Unexpected network activity from container
(user=%ka.user.name verb=%ka.verb
resource=%ka.target.resource image=%ka.image.repository)
priority: WARNING
- rule: Sensitive File Access
desc: Detect access to sensitive files
condition: >
open_read and
(fd.filename=/etc/passwd or
fd.filename=/etc/shadow or
fd.filename startswith /proc/ or
fd.filename startswith /sys/)
output: >
Sensitive file opened for reading
(user=%user.name command=%proc.cmdline file=%fd.name)
priority: WARNING
- rule: Shell in Container
desc: Detect shell execution in container
condition: >
spawned_process and
container and
shell_procs
output: >
Shell spawned in container
(user=%user.name shell=%proc.name parent=%proc.pname
cmdline=%proc.cmdline container_id=%container.id)
priority: WARNING
7. Emergency Response Procedures
Incident Response Playbook
#!/bin/bash
# incident_response.sh - Container security incident response
# 1. Immediate containment
isolate_container() {
local container_id=$1
echo "🚨 Isolating container $container_id"
# Stop network traffic
docker network disconnect bridge $container_id
# Create forensic snapshot
docker commit $container_id forensic-$(date +%Y%m%d-%H%M%S)
# Stop container
docker stop $container_id
echo "✅ Container isolated"
}
# 2. Evidence collection
collect_evidence() {
local container_id=$1
local evidence_dir="evidence-$(date +%Y%m%d-%H%M%S)"
mkdir -p $evidence_dir
# Container metadata
docker inspect $container_id > $evidence_dir/container_inspect.json
# Image information
docker history $(docker inspect --format='{{.Image}}' $container_id) > $evidence_dir/image_history.txt
# File system changes
docker diff $container_id > $evidence_dir/filesystem_changes.txt
# Running processes
docker exec $container_id ps aux > $evidence_dir/processes.txt 2>/dev/null || true
# Network connections
docker exec $container_id netstat -tulpn > $evidence_dir/network.txt 2>/dev/null || true
# Logs
docker logs $container_id > $evidence_dir/container_logs.txt 2>&1
echo "📁 Evidence collected in $evidence_dir"
}
# 3. System-wide scan
scan_all_containers() {
echo "🔍 Scanning all containers for indicators of compromise"
for container in $(docker ps -q); do
echo "Scanning container: $container"
# Check for suspicious processes
docker exec $container ps aux | grep -E "(nc|netcat|wget|curl|/tmp/)" || true
# Check for unusual network activity
docker exec $container netstat -tulpn | grep -E ":(4444|5555|6666|7777|8888|9999)" || true
# Check for privilege escalation attempts
docker exec $container find /tmp -perm 4755 2>/dev/null || true
done
}
# 4. Recovery procedures
cleanup_and_recover() {
echo "🛠️ Starting cleanup and recovery"
# Remove compromised images
docker rmi $(docker images | grep "compromised" | awk '{print $3}')
# Update all base images
docker images --format "table {{.Repository}}:{{.Tag}}" | grep -v TAG | while read image; do
echo "Updating $image"
docker pull $image
done
# Restart with fresh images
docker-compose down
docker-compose pull
docker-compose up -d
echo "✅ Recovery complete"
}
# Main incident response workflow
case "${1:-help}" in
isolate)
isolate_container $2
;;
collect)
collect_evidence $2
;;
scan)
scan_all_containers
;;
recover)
cleanup_and_recover
;;
full)
echo "🚨 Full incident response initiated"
isolate_container $2
collect_evidence $2
scan_all_containers
echo "⚠️ Manual review required before recovery"
;;
help|*)
echo "Usage: $0 {isolate|collect|scan|recover|full} [container_id]"
;;
esac
8. Security Monitoring and Alerting
Automated Threat Detection
# security_monitor.py
import docker
import json
import time
import logging
from datetime import datetime
import requests
class SecurityMonitor:
def __init__(self):
self.client = docker.from_env()
self.alert_webhook = "https://hooks.slack.com/your-webhook"
def monitor_containers(self):
"""Continuously monitor containers for security issues"""
while True:
try:
for container in self.client.containers.list():
self.check_container_security(container)
time.sleep(30) # Check every 30 seconds
except Exception as e:
logging.error(f"Monitoring error: {e}")
def check_container_security(self, container):
"""Check individual container for security issues"""
# Check if running as root
if self.is_running_as_root(container):
self.send_alert(f"🚨 Container {container.name} running as root")
# Check for privileged mode
if self.is_privileged(container):
self.send_alert(f"⚠️ Container {container.name} running in privileged mode")
# Check for excessive resource usage
stats = container.stats(stream=False)
memory_usage = stats['memory_stats']['usage'] / stats['memory_stats']['limit']
if memory_usage > 0.9:
self.send_alert(f"📈 Container {container.name} high memory usage: {memory_usage:.2%}")
# Check for suspicious network activity
if self.check_network_activity(container):
self.send_alert(f"🌐 Suspicious network activity in {container.name}")
def is_running_as_root(self, container):
"""Check if container is running as root user"""
try:
result = container.exec_run("id -u")
return result.output.decode().strip() == "0"
except:
return False
def is_privileged(self, container):
"""Check if container is running in privileged mode"""
return container.attrs['HostConfig']['Privileged']
def check_network_activity(self, container):
"""Check for suspicious network connections"""
try:
result = container.exec_run("netstat -tulpn")
output = result.output.decode()
# Check for common malware ports
suspicious_ports = ['4444', '5555', '6666', '7777', '8888', '9999']
for port in suspicious_ports:
if f":{port}" in output:
return True
return False
except:
return False
def send_alert(self, message):
"""Send security alert"""
payload = {
"text": f"🔒 Container Security Alert: {message}",
"username": "SecurityBot",
"timestamp": datetime.now().isoformat()
}
try:
requests.post(self.alert_webhook, json=payload)
logging.warning(message)
except Exception as e:
logging.error(f"Failed to send alert: {e}")
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
monitor = SecurityMonitor()
monitor.monitor_containers()
9. Compliance and Audit Trail
Security Compliance Automation
# compliance_checker.py
"""
Container security compliance checker
Supports CIS Docker Benchmark, NIST, SOC 2
"""
import docker
import json
import yaml
from datetime import datetime
class ComplianceChecker:
def __init__(self):
self.client = docker.from_env()
self.checks = self.load_compliance_rules()
def load_compliance_rules(self):
"""Load compliance rules from configuration"""
return {
'CIS_4.1': {
'title': 'Create a user for the container',
'check': self.check_non_root_user,
'severity': 'HIGH'
},
'CIS_4.5': {
'title': 'Do not use privileged containers',
'check': self.check_no_privileged,
'severity': 'HIGH'
},
'CIS_4.6': {
'title': 'Do not mount sensitive host directories',
'check': self.check_no_sensitive_mounts,
'severity': 'MEDIUM'
},
'CIS_5.7': {
'title': 'Do not map privileged ports',
'check': self.check_no_privileged_ports,
'severity': 'LOW'
}
}
def run_compliance_check(self):
"""Run compliance checks on all containers"""
report = {
'timestamp': datetime.now().isoformat(),
'containers': {},
'summary': {'passed': 0, 'failed': 0, 'total': 0}
}
for container in self.client.containers.list():
container_report = self.check_container_compliance(container)
report['containers'][container.name] = container_report
# Update summary
for check in container_report['checks']:
report['summary']['total'] += 1
if check['status'] == 'PASS':
report['summary']['passed'] += 1
else:
report['summary']['failed'] += 1
return report
def check_container_compliance(self, container):
"""Check compliance for a single container"""
container_report = {
'id': container.id[:12],
'name': container.name,
'image': container.attrs['Config']['Image'],
'checks': []
}
for rule_id, rule in self.checks.items():
try:
result = rule['check'](container)
container_report['checks'].append({
'rule_id': rule_id,
'title': rule['title'],
'severity': rule['severity'],
'status': 'PASS' if result else 'FAIL',
'details': getattr(result, 'details', '') if hasattr(result, 'details') else ''
})
except Exception as e:
container_report['checks'].append({
'rule_id': rule_id,
'title': rule['title'],
'severity': rule['severity'],
'status': 'ERROR',
'details': str(e)
})
return container_report
def check_non_root_user(self, container):
"""CIS 4.1: Check if container runs as non-root"""
try:
result = container.exec_run("id -u")
uid = result.output.decode().strip()
return uid != "0"
except:
return False
def check_no_privileged(self, container):
"""CIS 4.5: Check container is not privileged"""
return not container.attrs['HostConfig']['Privileged']
def check_no_sensitive_mounts(self, container):
"""CIS 4.6: Check for sensitive host directory mounts"""
sensitive_paths = ['/etc', '/proc', '/sys', '/dev', '/var/run/docker.sock']
mounts = container.attrs['Mounts']
for mount in mounts:
if mount['Type'] == 'bind':
source = mount['Source']
for sensitive_path in sensitive_paths:
if source.startswith(sensitive_path):
return False
return True
def check_no_privileged_ports(self, container):
"""CIS 5.7: Check no privileged ports (< 1024) are mapped"""
ports = container.attrs['NetworkSettings']['Ports']
for port_mapping in ports.values():
if port_mapping:
for mapping in port_mapping:
host_port = int(mapping['HostPort'])
if host_port < 1024:
return False
return True
def generate_report(self, format='json'):
"""Generate compliance report"""
report = self.run_compliance_check()
if format == 'json':
return json.dumps(report, indent=2)
elif format == 'html':
return self.generate_html_report(report)
def generate_html_report(self, report):
"""Generate HTML compliance report"""
html = f"""
<!DOCTYPE html>
<html>
<head>
<title>Container Security Compliance Report</title>
<style>
body {{ font-family: Arial, sans-serif; margin: 20px; }}
.summary {{ background: #f0f0f0; padding: 15px; margin-bottom: 20px; }}
.container {{ border: 1px solid #ddd; margin: 10px 0; padding: 15px; }}
.pass {{ color: green; }}
.fail {{ color: red; }}
.error {{ color: orange; }}
table {{ width: 100%; border-collapse: collapse; }}
th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
th {{ background-color: #f2f2f2; }}
</style>
</head>
<body>
<h1>Container Security Compliance Report</h1>
<div class="summary">
<h2>Summary</h2>
<p>Generated: {report['timestamp']}</p>
<p>Total Checks: {report['summary']['total']}</p>
<p>Passed: <span class="pass">{report['summary']['passed']}</span></p>
<p>Failed: <span class="fail">{report['summary']['failed']}</span></p>
</div>
"""
for container_name, container_data in report['containers'].items():
html += f"""
<div class="container">
<h3>Container: {container_name}</h3>
<p>Image: {container_data['image']}</p>
<table>
<tr><th>Rule ID</th><th>Title</th><th>Severity</th><th>Status</th></tr>
"""
for check in container_data['checks']:
status_class = check['status'].lower()
html += f"""
<tr>
<td>{check['rule_id']}</td>
<td>{check['title']}</td>
<td>{check['severity']}</td>
<td class="{status_class}">{check['status']}</td>
</tr>
"""
html += "</table></div>"
html += "</body></html>"
return html
if __name__ == "__main__":
checker = ComplianceChecker()
report = checker.generate_report('json')
print(report)
10. Best Practices Summary
Security Checklist ✅
Image Security
- [ ] Use official, minimal base images (distroless, Alpine)
- [ ] Scan images for vulnerabilities before deployment
- [ ] Sign and verify all images with cosign
- [ ] Generate and attach SBOMs to images
- [ ] Regular base image updates and rebuilds
Runtime Security
- [ ] Run containers as non-root users
- [ ] Use read-only file systems where possible
- [ ] Drop all capabilities, add only necessary ones
- [ ] Implement resource limits (CPU, memory, disk)
- [ ] Network segmentation and firewall rules
Secrets Management
- [ ] Never hardcode secrets in images or environment variables
- [ ] Use external secret management systems (Vault, AWS Secrets Manager)
- [ ] Rotate secrets regularly
- [ ] Audit secret access and usage
Pipeline Security
- [ ] Implement security scanning in CI/CD
- [ ] Use signed commits and verified builds
- [ ] Enforce approval processes for production deployments
- [ ] Maintain audit trails of all deployments
Emergency Contacts
# security-contacts.yml
emergency_contacts:
security_team:
slack: "#security-alerts"
email: [email protected]
phone: "+1-555-SECURITY"
incident_commander:
name: "Jane Doe"
email: [email protected]
phone: "+1-555-0123"
escalation:
level_1: [email protected]
level_2: [email protected]
level_3: [email protected]
procedures:
immediate_response: "Run: ./incident_response.sh full [container_id]"
escalation_trigger: "Critical vulnerabilities or active exploitation"
communication: "Use security Slack channel for coordination"
Container security is not optional in 2024-2025. With attackers increasingly targeting containerized applications and supply chains, implementing these defenses is critical for protecting your infrastructure, data, and customers. Start with the basics (non-root containers, image scanning) and gradually implement advanced protections based on your risk profile.