Docker-Container bieten Isolation, aber Sicherheit ist kein Automatismus. Diese Best Practices helfen, Container sicher zu betreiben.

Grundprinzipien

Least Privilege

Minimale Rechte:
- Nicht als root laufen
- Nur nötige Capabilities
- Read-only Dateisysteme wo möglich
- Minimale Images

Defense in Depth

Mehrere Schutzebenen:
- Sichere Images
- Netzwerk-Isolation
- Ressourcen-Limits
- Host-Hardening

Sichere Images

Offizielle Images verwenden

# GUT: Offizielle Images
FROM nginx:alpine
FROM node:20-slim

# SCHLECHT: Unbekannte Quellen
FROM randomuser/nginx-custom

Minimale Base Images

# Alpine ist klein und hat weniger Angriffsfläche
FROM alpine:3.19

# Oder Distroless für noch weniger
FROM gcr.io/distroless/static

Image-Größen

| Base Image | Größe | Anmerkung | |------------|-------|-----------| | ubuntu | ~77 MB | Viele Pakete | | debian:slim | ~30 MB | Reduziert | | alpine | ~5 MB | Minimal | | distroless | ~2 MB | Nur Runtime |

Images scannen

# Mit Docker Scout
docker scout cves nginx:latest

# Mit Trivy
trivy image nginx:latest

# Mit Snyk
snyk container test nginx:latest

Feste Tags verwenden

# GUT: Feste Version
FROM nginx:1.25.3-alpine

# SCHLECHT: Kann sich ändern
FROM nginx:latest
FROM nginx:alpine

Nicht als Root laufen

Im Dockerfile

FROM node:20-alpine

# Benutzer erstellen
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# Arbeitsverzeichnis mit richtigen Rechten
WORKDIR /app
COPY --chown=appuser:appgroup . .

# Als Nicht-Root ausführen
USER appuser

CMD ["node", "app.js"]

Zur Laufzeit

# Benutzer beim Start setzen
docker run --user 1000:1000 nginx

# Oder in docker-compose.yml
services:
  app:
    image: myapp
    user: "1000:1000"

Root verhindern

# Container mit Rootless Docker
dockerd-rootless-setuptool.sh install

# Oder root komplett blockieren
docker run --user nobody nginx

Capabilities einschränken

Alle Capabilities entfernen

docker run --cap-drop=ALL nginx

Nur nötige hinzufügen

docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE nginx

In Docker Compose

services:
  web:
    image: nginx
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE

Wichtige Capabilities

| Capability | Bedeutung | Braucht wer? | |------------|-----------|--------------| | NET_BIND_SERVICE | Ports < 1024 | Webserver | | CHOWN | Besitzer ändern | Selten | | SETUID/SETGID | User wechseln | Selten | | SYS_ADMIN | Viele Rechte | Fast nie! |

Read-Only Dateisystem

Container read-only starten

docker run --read-only nginx

Mit tmpfs für temporäre Dateien

docker run --read-only \
  --tmpfs /tmp \
  --tmpfs /var/run \
  nginx

In Docker Compose

services:
  web:
    image: nginx
    read_only: true
    tmpfs:
      - /tmp
      - /var/run

Netzwerk-Sicherheit

Interne Netzwerke

# docker-compose.yml
services:
  app:
    networks:
      - frontend
      - backend

  db:
    networks:
      - backend  # Nur intern erreichbar!

networks:
  frontend:
  backend:
    internal: true  # Kein Internet-Zugang

Ports nur lokal

# Nur auf localhost
docker run -p 127.0.0.1:3306:3306 mysql

# Statt
docker run -p 3306:3306 mysql  # Auf allen IPs!

Inter-Container-Kommunikation

# ICC deaktivieren (Docker-Daemon)
dockerd --icc=false

Ressourcen-Limits

CPU und RAM begrenzen

docker run --memory=512m --cpus=1.0 nginx

In Docker Compose

services:
  app:
    image: myapp
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M

Prozesse begrenzen

docker run --pids-limit=100 nginx

Secrets sicher handhaben

NICHT: Secrets in Umgebungsvariablen

# SCHLECHT: Sichtbar in docker inspect
services:
  app:
    environment:
      - DB_PASSWORD=geheim123

Docker Secrets (Swarm)

# Secret erstellen
echo "mein-passwort" | docker secret create db_password -

# In Service verwenden
docker service create \
  --secret db_password \
  --env DB_PASSWORD_FILE=/run/secrets/db_password \
  myapp

Secrets in Compose (Swarm Mode)

services:
  app:
    secrets:
      - db_password
    environment:
      - DB_PASSWORD_FILE=/run/secrets/db_password

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

Alternative: Externe Secret-Manager

# Mit HashiCorp Vault
services:
  app:
    environment:
      - VAULT_ADDR=http://vault:8200
    # App liest Secrets von Vault

Host-Bindungen vermeiden

Gefährliche Mounts

# SEHR GEFÄHRLICH: Docker-Socket
docker run -v /var/run/docker.sock:/var/run/docker.sock ...
# Erlaubt Container-Ausbruch!

# GEFÄHRLICH: Wichtige Host-Verzeichnisse
docker run -v /:/host ...

Sichere Alternativen

# Nur nötige Verzeichnisse
docker run -v /app/data:/data:ro myapp  # read-only wenn möglich

# Named Volumes statt Bind Mounts
docker run -v mydata:/data myapp

Security-Optionen

Seccomp-Profile

# Standard-Profil blockiert gefährliche Syscalls
docker run --security-opt seccomp=default.json nginx

# Eigenes Profil
docker run --security-opt seccomp=my-profile.json nginx

AppArmor

# Mit AppArmor-Profil
docker run --security-opt apparmor=docker-default nginx

No New Privileges

# Verhindert Privilege Escalation
docker run --security-opt no-new-privileges nginx

In Docker Compose

services:
  app:
    security_opt:
      - no-new-privileges:true
      - seccomp:./seccomp-profile.json

Docker-Daemon absichern

TLS aktivieren

// /etc/docker/daemon.json
{
  "tls": true,
  "tlscacert": "/etc/docker/ca.pem",
  "tlscert": "/etc/docker/server-cert.pem",
  "tlskey": "/etc/docker/server-key.pem",
  "tlsverify": true
}

Live-Restore aktivieren

{
  "live-restore": true
}

User Namespaces

{
  "userns-remap": "default"
}

Checkliste

□ Offizielle/minimale Base Images
□ Feste Image-Tags (keine :latest)
□ Images auf Vulnerabilities scannen
□ Nicht als root laufen
□ Capabilities einschränken
□ Read-only Dateisystem
□ Ressourcen-Limits setzen
□ Interne Netzwerke für Backend
□ Ports nur lokal binden
□ Secrets sicher handhaben
□ Keine Docker-Socket-Mounts
□ Security-Optionen (no-new-privileges)

Scanning automatisieren

GitHub Actions

# .github/workflows/scan.yml
name: Container Scan

on: push

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build image
        run: docker build -t myapp:${{ github.sha }} .

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

Fazit

Container-Sicherheit erfordert mehrere Maßnahmen: sichere Images, minimale Rechte, Netzwerk-Isolation und Ressourcen-Limits. Scannen Sie Images regelmäßig, vermeiden Sie root und schützen Sie Secrets. Die meisten Maßnahmen sind einfach umzusetzen und verbessern die Sicherheit erheblich.