Day 20: Security and Best Practices

Learning Objectives: By the end of this lesson, you will be able to:

  • Implement security best practices for Docker containers and images
  • Configure proper access controls and user permissions
  • Scan images for vulnerabilities and manage security risks
  • Implement secrets management and secure configuration practices
  • Design secure container architectures and network policies

1. Welcome and Introduction (5 minutes)

Icebreaker Activity

“Imagine your containerized application handles sensitive customer data and financial transactions. One day, you discover that an attacker has gained access to your production environment through a vulnerable container. What security measures could have prevented this breach? Today we’ll learn how to build security into every layer of your containerized applications.”

Lesson Overview

  • Duration: 90 minutes
  • Prerequisites: Docker fundamentals, container networking, basic security concepts
  • Materials Needed: Docker Desktop, vulnerability scanning tools, sample applications

2. Learning Objectives and Session Outline (5 minutes)

What We’ll Cover Today:

  1. Container Security Fundamentals - Understanding the security model
  2. Image Security - Building and scanning secure container images
  3. Runtime Security - Securing running containers and hosts
  4. Access Control - Managing permissions and authentication
  5. Secrets Management - Handling sensitive data securely

Success Criteria:

By the end of this session, you should be able to identify common security vulnerabilities in containerized applications and implement comprehensive security measures to protect your applications in development and production.

3. Foundation Knowledge (15-20 minutes)

Core Concepts

Container Security Model

Containers share the host kernel but provide process isolation through namespaces and cgroups. This shared kernel model creates unique security considerations compared to traditional virtual machines.

Real-World Analogy: Think of containers like apartments in a building. While each apartment (container) is separate and private, they all share the same infrastructure (kernel). A security issue in the building’s foundation (kernel) affects all apartments, and poorly secured apartments can impact their neighbors.

Why This Matters: Understanding the container security model helps you make informed decisions about when and how to use containers securely.

Defense in Depth

Container security requires multiple layers of protection:

  1. Image Security: Secure base images and build processes
  2. Runtime Security: Proper container configuration and isolation
  3. Host Security: Securing the underlying infrastructure
  4. Network Security: Controlling communication between containers
  5. Access Control: Managing who can do what with containers

Common Attack Vectors

Understanding common attack patterns helps build better defenses:

  • Vulnerable Dependencies: Outdated packages with known vulnerabilities
  • Privilege Escalation: Containers running with unnecessary privileges
  • Container Escape: Breaking out of container isolation
  • Supply Chain Attacks: Compromised base images or build tools
  • Secrets Exposure: Hardcoded passwords or exposed configuration

Key Terminology

  • CVE: Common Vulnerabilities and Exposures database
  • RBAC: Role-Based Access Control for managing permissions
  • Principle of Least Privilege: Granting minimal necessary permissions
  • Zero Trust: Security model that trusts nothing by default
  • Supply Chain Security: Protecting the software development and deployment pipeline

4. Detailed Explanation and Examples (25-30 minutes)

Image Security Best Practices

Secure Base Images

Choose base images carefully and keep them updated:

  • Official Images: Use official images from Docker Hub when possible
  • Minimal Images: Use distroless or alpine-based images to reduce attack surface
  • Regular Updates: Keep base images and dependencies current
  • Image Scanning: Regularly scan images for vulnerabilities

Dockerfile Security

Writing secure Dockerfiles is crucial for image security:

# Use specific, minimal base image
FROM node:16-alpine

# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nextjs -u 1001

# Install dependencies as root, then switch to non-root user
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force

# Copy application code and set ownership
COPY --chown=nextjs:nodejs . .

# Switch to non-root user
USER nextjs

# Expose port (not running as root)
EXPOSE 3000

# Use exec form for proper signal handling
CMD ["node", "server.js"]

Runtime Security Configuration

Container Isolation

Proper container configuration prevents privilege escalation and limits potential damage:

version: '3.8'

services:
  app:
    build: .
    # Security configurations
    read_only: true  # Read-only filesystem
    user: "1001:1001"  # Non-root user
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE  # Only necessary capabilities
    security_opt:
      - no-new-privileges:true
    tmpfs:
      - /tmp
      - /var/cache
    volumes:
      - app_data:/app/data:rw
      - ./config:/app/config:ro
    networks:
      - app-network
    environment:
      - NODE_ENV=production
    # Resource limits prevent DoS
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

networks:
  app-network:
    driver: bridge
    internal: true  # No external access

volumes:
  app_data:

Network Security

Network Segmentation

Isolate services using custom networks:

version: '3.8'

services:
  frontend:
    image: nginx:alpine
    networks:
      - public
      - internal
    ports:
      - "443:443"

  api:
    build: ./api
    networks:
      - internal
      - database
    # No external ports exposed

  database:
    image: postgres:13
    networks:
      - database  # Isolated network
    environment:
      - POSTGRES_PASSWORD_FILE=/run/secrets/db_password
    secrets:
      - db_password

networks:
  public:
    driver: bridge
  internal:
    driver: bridge
    internal: true
  database:
    driver: bridge
    internal: true

secrets:
  db_password:
    file: ./secrets/db_password.txt

Secrets Management

Docker Secrets

Docker provides built-in secrets management for sensitive data:

# Create a secret
echo "my_secret_password" | docker secret create db_password -

# Use secret in service
docker service create \
  --name myapp \
  --secret db_password \
  myapp:latest

Environment-Specific Secrets

version: '3.8'

services:
  app:
    image: myapp:latest
    environment:
      - DATABASE_URL_FILE=/run/secrets/database_url
      - API_KEY_FILE=/run/secrets/api_key
    secrets:
      - database_url
      - api_key

secrets:
  database_url:
    external: true
  api_key:
    external: true

Common Misconceptions

  1. Misconception: Containers are inherently secure Reality: Containers require proper configuration and security practices to be secure

  2. Misconception: Running containers as non-root is sufficient for security Reality: User permissions are just one layer; comprehensive security requires multiple measures

5. Live Coding Demonstration (20-25 minutes)

Example 1: Secure Multi-tier Application

version: '3.8'

services:
  # Web server with security hardening
  nginx:
    image: nginx:1.21-alpine
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - nginx_cache:/var/cache/nginx
      - nginx_logs:/var/log/nginx
    networks:
      - frontend
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    read_only: true
    tmpfs:
      - /tmp
      - /var/run
    deploy:
      resources:
        limits:
          memory: 128M
        reservations:
          memory: 64M

  # Application server with minimal privileges
  app:
    build: 
      context: ./app
      dockerfile: Dockerfile.secure
    networks:
      - frontend
      - backend
    environment:
      - NODE_ENV=production
      - DATABASE_URL_FILE=/run/secrets/database_url
      - JWT_SECRET_FILE=/run/secrets/jwt_secret
    secrets:
      - database_url
      - jwt_secret
    volumes:
      - app_uploads:/app/uploads
    read_only: true
    tmpfs:
      - /tmp
    user: "1001:1001"
    cap_drop:
      - ALL
    security_opt:
      - no-new-privileges:true
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '0.5'

  # Database with network isolation
  database:
    image: postgres:13-alpine
    networks:
      - backend
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=appuser
      - POSTGRES_PASSWORD_FILE=/run/secrets/db_password
    secrets:
      - db_password
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./database/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    read_only: true
    tmpfs:
      - /tmp
      - /var/run/postgresql
    security_opt:
      - no-new-privileges:true
    deploy:
      resources:
        limits:
          memory: 1G
        reservations:
          memory: 512M

  # Security scanning service
  trivy:
    image: aquasec/trivy:latest
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - trivy_cache:/root/.cache
    command: ["server", "--listen", "0.0.0.0:8080"]
    networks:
      - monitoring

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true
  monitoring:
    driver: bridge
    internal: true

volumes:
  postgres_data:
  nginx_cache:
  nginx_logs:
  app_uploads:
  trivy_cache:

secrets:
  database_url:
    file: ./secrets/database_url.txt
  jwt_secret:
    file: ./secrets/jwt_secret.txt
  db_password:
    file: ./secrets/db_password.txt

Secure Dockerfile (app/Dockerfile.secure):

# Multi-stage build for smaller, more secure images
FROM node:16-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force

FROM node:16-alpine AS runtime

# Install security updates
RUN apk update && apk upgrade && apk add --no-cache dumb-init

# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nextjs -u 1001 -G nodejs

# Create app directory with correct permissions
WORKDIR /app
RUN chown nextjs:nodejs /app

# Copy dependencies from builder stage
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules

# Copy application code
COPY --chown=nextjs:nodejs . .

# Remove unnecessary files
RUN rm -rf .git .gitignore README.md Dockerfile* && \
    npm prune --production

# Switch to non-root user
USER nextjs

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

# Use dumb-init for proper signal handling
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "server.js"]

Example 2: Security Scanning and Compliance

#!/bin/bash
# Security scanning script

set -e

IMAGE_NAME="myapp:latest"
DOCKERFILE_PATH="./Dockerfile"

echo "=== Docker Security Scanning ==="

# 1. Dockerfile linting with hadolint
echo "Scanning Dockerfile for best practices..."
docker run --rm -i hadolint/hadolint < $DOCKERFILE_PATH

# 2. Image vulnerability scanning with Trivy
echo "Scanning image for vulnerabilities..."
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  -v $(pwd):/root/.cache/ aquasec/trivy:latest image $IMAGE_NAME

# 3. Container configuration scanning
echo "Checking container security configuration..."
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  -v $(pwd):/tmp anchore/grype:latest $IMAGE_NAME

# 4. Secrets scanning
echo "Scanning for exposed secrets..."
docker run --rm -v $(pwd):/path \
  zricethezav/gitleaks:latest detect --source="/path" --verbose

# 5. Custom security checks
echo "Running custom security checks..."

# Check for running containers with privileged mode
PRIVILEGED_CONTAINERS=$(docker ps --filter "label=privileged=true" -q)
if [ ! -z "$PRIVILEGED_CONTAINERS" ]; then
    echo "WARNING: Found containers running in privileged mode:"
    docker ps --filter "label=privileged=true"
fi

# Check for containers running as root
ROOT_CONTAINERS=$(docker ps --format "table {{.Names}}\t{{.Image}}" | while read name image; do
    if [ "$name" != "NAMES" ]; then
        USER=$(docker exec $name whoami 2>/dev/null || echo "unknown")
        if [ "$USER" = "root" ]; then
            echo "$name ($image)"
        fi
    fi
done)

if [ ! -z "$ROOT_CONTAINERS" ]; then
    echo "WARNING: Found containers running as root:"
    echo "$ROOT_CONTAINERS"
fi

echo "Security scanning completed."

Example 3: Secrets Management with External Tools

# Using external secrets management
version: '3.8'

services:
  app:
    image: myapp:latest
    environment:
      # Reference to external secret store
      - DATABASE_URL=vault:secret/data/myapp#DATABASE_URL
      - API_KEY=vault:secret/data/myapp#API_KEY
    volumes:
      - vault_token:/vault/token:ro
    depends_on:
      - vault-agent

  vault-agent:
    image: vault:latest
    command: ["vault", "agent", "-config=/vault/config/agent.hcl"]
    volumes:
      - ./vault/agent.hcl:/vault/config/agent.hcl:ro
      - vault_token:/vault/token
    environment:
      - VAULT_ADDR=https://vault.company.com
      - VAULT_ROLE_ID_FILE=/vault/role-id
      - VAULT_SECRET_ID_FILE=/vault/secret-id
    secrets:
      - source: vault_role_id
        target: /vault/role-id
      - source: vault_secret_id
        target: /vault/secret-id

volumes:
  vault_token:

secrets:
  vault_role_id:
    external: true
  vault_secret_id:
    external: true

6. Hands-On Practice (30-40 minutes)

Exercise 1: Security Audit and Hardening (20 minutes)

Objective: Identify and fix security vulnerabilities in a container setup

Provided Insecure Application:

version: '3.8'

services:
  web:
    build: .
    ports:
      - "80:80"
    volumes:
      - /:/host:rw  # Dangerous host mount
    privileged: true  # Unnecessary privileges
    environment:
      - DB_PASSWORD=plaintext_password  # Exposed secret
      - DEBUG=true
    user: root  # Running as root

  database:
    image: postgres:9.6  # Outdated version
    ports:
      - "5432:5432"  # Exposed to host
    environment:
      - POSTGRES_PASSWORD=admin123
      - POSTGRES_HOST_AUTH_METHOD=trust  # No authentication

Insecure Dockerfile:

FROM ubuntu:18.04

# Running as root
RUN apt-get update && apt-get install -y \
    curl \
    wget \
    sudo

# Copying sensitive files
COPY secrets.txt /app/
COPY private_key.pem /app/

# Exposing unnecessary ports
EXPOSE 22 3000 8080 9090

# Running with full privileges
CMD ["./app"]

Instructions:

  1. Identify all security issues in the provided configuration
  2. Create a secure version addressing each vulnerability
  3. Implement proper secrets management
  4. Add security scanning to the build process
  5. Document the security improvements made

Exercise 2: Implementing Zero-Trust Architecture (15 minutes)

Objective: Design a container architecture with zero-trust principles

Requirements:

  • No service trusts any other service by default
  • All communication must be authenticated and encrypted
  • Implement proper network segmentation
  • Add monitoring and auditing for all access

Hints:

  • Use mutual TLS for service-to-service communication
  • Implement service mesh for traffic control
  • Add authentication at every layer
  • Monitor and log all network traffic

Exercise 3: Compliance and Governance (5 minutes)

Objective: Create policies for container security compliance

Challenge: Define policies for:

  • Image approval process
  • Runtime security requirements
  • Access control and permissions
  • Monitoring and incident response

Extension Ideas:

  • Implement automated policy enforcement
  • Create security dashboards and reporting
  • Set up vulnerability management workflows

7. Common Pitfalls and Troubleshooting (10 minutes)

Frequent Security Mistakes

  1. Problem: Running containers as root user Cause: Default Docker behavior and lack of security awareness Solution: Always create and use non-root users in containers

  2. Problem: Exposing unnecessary ports and services Cause: Over-permissive network configuration Solution: Follow principle of least privilege for network access

  3. Problem: Hardcoded secrets in images or environment variables Cause: Lack of proper secrets management strategy Solution: Use Docker secrets, external secret stores, or runtime secret injection

Security Scanning and Monitoring

# Regular security maintenance commands

# Update all images
docker images --format "{{.Repository}}:{{.Tag}}" | \
  xargs -I {} docker pull {}

# Clean up unused images and containers
docker system prune -af

# Check for containers with security issues
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}" | \
  while read name image status; do
    if [[ "$status" == *"Exited"* ]]; then
        echo "Investigate: $name ($image) - $status"
    fi
  done

# Monitor for privilege escalation attempts
docker events --filter event=exec | \
  while read event; do
    echo "Exec event detected: $event"
  done

Incident Response Procedures

  1. Detection: Automated monitoring and alerting
  2. Containment: Isolate affected containers and networks
  3. Investigation: Analyze logs and system state
  4. Recovery: Restore from known good state
  5. Lessons Learned: Update security practices based on findings

8. Best Practices and Professional Tips (5 minutes)

Security Development Lifecycle

  • Secure by Design: Build security into the development process
  • Shift Left: Implement security checks early in the development cycle
  • Continuous Monitoring: Monitor security posture continuously
  • Regular Updates: Keep images, dependencies, and tools updated
  • Training: Ensure team members understand security best practices

Production Security Checklist

  • All images scanned for vulnerabilities
  • No containers running as root
  • Secrets managed securely
  • Network segmentation implemented
  • Resource limits configured
  • Monitoring and alerting in place
  • Backup and recovery procedures tested
  • Incident response plan documented

Compliance Considerations

  • Regulatory Requirements: Understand applicable regulations (SOC 2, PCI DSS, HIPAA)
  • Audit Trails: Maintain comprehensive logs for compliance auditing
  • Data Protection: Implement proper data encryption and access controls
  • Regular Assessments: Conduct periodic security assessments and penetration testing

9. Summary and Review (5 minutes)

Key Takeaways

  1. Defense in Depth: Security requires multiple layers of protection
  2. Least Privilege: Grant minimal necessary permissions and access
  3. Continuous Monitoring: Security is an ongoing process, not a one-time setup
  4. Supply Chain Security: Secure your entire development and deployment pipeline

Quick Review Questions

  1. What are the key differences between container security and traditional VM security?
  2. How would you securely manage database passwords in a multi-container application?
  3. What steps would you take to investigate a suspected container security breach?

For Instructors

Teaching Tips

  • Use real-world security scenarios and case studies
  • Demonstrate both secure and insecure configurations for comparison
  • Emphasize the business impact of security vulnerabilities
  • Show how security practices integrate with development workflows

Assessment Ideas

  • Security audit of provided insecure applications
  • Design secure architectures for given requirements
  • Create incident response procedures for container security breaches

Adaptation Notes

  • Beginners: Focus on fundamental security concepts and basic hardening
  • Advanced: Cover advanced topics like service mesh security and compliance frameworks
  • Time Management: Balance hands-on security auditing with theoretical concepts
  • Industry Focus: Adapt examples to specific industry requirements (healthcare, finance, etc.)