RAPID CYBER AI
WEEK 3 OF 8
0 steps completed
WEEK 3

Docker & Container Security

Harden containers from Dockerfile to runtime to supply chain

8-10 hours

Lab 1: Dockerfile Hardening

~90 min

Create a deliberately insecure Dockerfile

This Dockerfile has 7 security issues. We'll fix every one.

BAD Dockerfile
$ mkdir -p ~/bootcamp-docker && cd ~/bootcamp-docker
$ cat > Dockerfile.insecure << 'EOF'
FROM ubuntu:latest
ENV DB_PASSWORD=supersecret123
ADD . /app
RUN apt-get update && apt-get install -y python3 curl wget vim
EXPOSE 8080
CMD ["python3", "/app/server.py"]
EOF

Build the hardened version

Multi-stage build, non-root user, distroless base, no secrets, health check, pinned image.

GOOD Dockerfile
$ cat > Dockerfile << 'EOF'
# Pin base image by digest
FROM python:3.12-slim@sha256:abcd1234... AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

# Production stage — minimal image
FROM gcr.io/distroless/python3-debian12:nonroot
COPY --from=builder /app /app
WORKDIR /app
USER 1001
HEALTHCHECK --interval=30s CMD ["python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')"]
EXPOSE 8080
CMD ["server.py"]
EOF

Compare the two

The secure image is 8x smaller with a dramatically reduced attack surface.

Comparison
$ docker build -f Dockerfile.insecure -t myapp:insecure .
$ docker build -t myapp:secure .

# Compare image sizes
$ docker images myapp
# insecure: ~400MB  |  secure: ~50MB

# Check for root user
$ docker inspect myapp:insecure --format '{{.Config.User}}'
# (empty = root!)
$ docker inspect myapp:secure --format '{{.Config.User}}'
# 1001

Lab 2: Image Scanning & CVE Management

~60 min

Scan with Trivy

Scan both images. The insecure image will have significantly more vulnerabilities.

Trivy scan
$ trivy image myapp:insecure
# Review CVE findings

$ trivy image myapp:secure
# Should show significantly fewer findings

# CI gate — fail on HIGH/CRITICAL:
$ trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:secure

Integrate into CI

Add to your GitHub Actions workflow. Pipeline fails if dangerous CVEs are found.

GitHub Action
- name: Scan image with Trivy
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: myapp:${{ github.sha }}
    severity: HIGH,CRITICAL
    exit-code: 1

Lab 3: Runtime Security Controls

~60 min

Apply runtime hardening

Read-only filesystem, dropped capabilities, no privilege escalation, resource limits.

Hardened run
$ docker run -d \
    --name secure-app \
    --read-only \
    --tmpfs /tmp \
    --cap-drop ALL \
    --cap-add NET_BIND_SERVICE \
    --security-opt no-new-privileges \
    --memory 512m \
    --cpus 1.0 \
    --pids-limit 100 \
    myapp:secure

Test the controls

Verify each control is working as expected.

Verification
# Try to write to the filesystem (should fail)
$ docker exec secure-app touch /etc/test
# Read-only file system

# Check capabilities
$ docker exec secure-app cat /proc/1/status | grep Cap
# Should show minimal capabilities

Lab 4: Container Networking & Secrets

~60 min

Demonstrate the ENV secret problem

ENV secrets are visible to anyone who can pull the image. Never do this.

Exposed secrets
# Build with secret in ENV
$ docker build -f Dockerfile.insecure -t myapp:insecure .

# Secret is visible in inspect:
$ docker inspect myapp:insecure --format '{{.Config.Env}}'
# [DB_PASSWORD=supersecret123]

# Secret persists in history:
$ docker history myapp:insecure

Use BuildKit secrets

BuildKit secrets are available during build but don't persist in any layer.

Secure secrets
# Create a secret file
$ echo 'supersecret123' > db_password.txt

# Build with mounted secret (doesn't persist in image)
$ DOCKER_BUILDKIT=1 docker build \
    --secret id=db_pass,src=db_password.txt \
    -t myapp:secure .

# In Dockerfile:
# RUN --mount=type=secret,id=db_pass cat /run/secrets/db_pass

Lab 5: Image Signing & Supply Chain

~60 min

Install and use Cosign

Cryptographic signatures prove images haven't been tampered with.

Cosign
# Install
$ curl -LO https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
$ chmod +x cosign-linux-amd64 && sudo mv cosign-linux-amd64 /usr/local/bin/cosign

# Generate key pair
$ cosign generate-key-pair

# Sign an image (after pushing to registry)
$ cosign sign --key cosign.key myregistry.io/myapp:v1

# Verify
$ cosign verify --key cosign.pub myregistry.io/myapp:v1

Generate SBOM

SBOM gives you visibility into every dependency. Scan it for vulnerabilities.

Software bill of materials
# Install Syft
$ curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin

# Generate SBOM
$ syft myapp:secure -o json > sbom.json

# Scan SBOM for vulnerabilities
$ grype sbom:sbom.json