Skip to content

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

  1. Supply Chain Complexity: Modern containers pull from dozens of sources
  2. Developer Convenience Over Security: Fast deployment often skips security checks
  3. Runtime Privilege Escalation: Container breakouts are increasingly common
  4. Image Registry Compromise: Attackers target central repositories
  5. 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"]
# Build with secrets
echo "my_npm_token" | docker build --secret id=npm_token,src=- .

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.