Loading...
Alle Artikel
Security · 6 min read

Docker Security Hardening: 10 essenzielle Praktiken

Ein umfassender Leitfaden zur Absicherung von Docker-Containern in der Produktion – von Image-Scanning über Runtime-Schutz bis hin zu Secrets-Management.

Warum Container-Security kein nachträglicher Gedanke sein darf

Container bieten Isolation, sind aber standardmäßig keine Sicherheitsgrenze. Ein fehlkonfigurierter Container kann Host-Dateisysteme freilegen, Zugangsdaten preisgeben oder einem Angreifer einen Ansatzpunkt bieten, um sich lateral durch Ihre Infrastruktur zu bewegen. Das Shared-Kernel-Modell bedeutet: Ein Container-Escape betrifft jeden Workload auf dem Host.

Bei DevOpsVibe auditieren wir regelmäßig Container-Umgebungen. Dies sind die zehn Praktiken, die den größten Unterschied zwischen einem verwundbaren und einem gehärteten Deployment ausmachen.

1. Minimale Base Images verwenden

Jedes Paket in Ihrem Image ist eine potenzielle Angriffsfläche. Beginnen Sie mit dem kleinsten Image, das funktioniert:

Vorher (verwundbar):

FROM ubuntu:22.04
RUN apt-get update && apt-get install -y python3 python3-pip curl wget vim
COPY . /app
RUN pip3 install -r /app/requirements.txt
CMD ["python3", "/app/main.py"]

Nachher (gehärtet):

FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

FROM gcr.io/distroless/python3-debian12
COPY --from=builder /install /usr/local
COPY . /app
WORKDIR /app
CMD ["main.py"]

Das Distroless-Image hat keine Shell, keinen Paketmanager und keine unnötigen Werkzeuge. Wenn ein Angreifer Codeausführung erlangt, gibt es kaum etwas, womit er arbeiten könnte.

Vergleich der Image-Größen:

  • ubuntu:22.04 mit Abhängigkeiten: ~450MB
  • python:3.12-slim Multi-Stage mit Distroless: ~85MB

2. Niemals als root laufen

Standardmäßig führt Docker Prozesse im Container als root aus. Das ist gefährlich, weil ein Container-Escape dem Angreifer root auf dem Host verschafft:

FROM node:20-slim

# Create a non-root user
RUN groupadd -r appuser && useradd -r -g appuser -d /app -s /sbin/nologin appuser

WORKDIR /app
COPY --chown=appuser:appuser . .
RUN npm ci --only=production

USER appuser
EXPOSE 3000
CMD ["node", "server.js"]

Zur Laufzeit verifizieren:

docker run --rm my-app whoami
# Output: appuser

3. Images auf Schwachstellen scannen

Integrieren Sie Image-Scanning in Ihre CI/CD-Pipeline. Wir empfehlen Trivy wegen seiner Geschwindigkeit und umfassenden Datenbank:

# Scan during build
trivy image --severity HIGH,CRITICAL --exit-code 1 my-app:latest

# Example output:
# my-app:latest (debian 12.4)
# Total: 0 (HIGH: 0, CRITICAL: 0)

Automatisieren Sie das in Ihrer Pipeline:

# .github/workflows/security.yml
- name: Scan image
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: ${{ env.IMAGE }}
    format: sarif
    output: trivy-results.sarif
    severity: HIGH,CRITICAL
    exit-code: "1"

- name: Upload scan results
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: trivy-results.sarif

Scannen Sie auch Ihre Dockerfiles auf Fehlkonfigurationen:

trivy config --severity HIGH,CRITICAL ./Dockerfile

4. Image-Digests pinnen, nicht nur Tags

Tags sind veränderbar. Jemand kann ein kompromittiertes Image unter python:3.12-slim pushen und jeder Build, der diesen Tag zieht, bekommt die bösartige Version:

# Risky: tag can be overwritten
FROM python:3.12-slim

# Secure: digest is immutable
FROM python:3.12-slim@sha256:a3e58c29e4a3b692a57d0e68b4b9c4f2e63e547e1b3f0a8e93c2a4fb7d3e1c9a

Den Digest erhalten Sie mit:

docker inspect --format='{{index .RepoDigests 0}}' python:3.12-slim

5. Read-Only-Dateisysteme implementieren

Verhindern Sie Laufzeit-Modifikationen des Container-Dateisystems. Wenn Ihre Anwendung schreiben muss, mounten Sie spezifische beschreibbare Volumes:

docker run --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  --volume /app/data:/data:rw \
  my-app:latest

In Kubernetes:

securityContext:
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  runAsNonRoot: true
  runAsUser: 1000
  capabilities:
    drop:
      - ALL

6. Secrets sauber verwalten

Backen Sie niemals Secrets in Images. Sie bleiben in der Layer-History erhalten, selbst wenn Sie sie in einem späteren Layer löschen:

Falsch:

# NEVER do this
ENV DATABASE_URL=postgres://admin:password@db:5432/myapp
COPY .env /app/.env

Richtig -- Build-Time-Secrets nutzen (BuildKit):

# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=db_url \
    export DATABASE_URL=$(cat /run/secrets/db_url) && \
    python manage.py collectstatic
docker buildx build --secret id=db_url,src=./db_url.txt -t my-app .

Zur Laufzeit nutzen Sie das Secrets-Management Ihres Orchestrators:

# Kubernetes secret injection
env:
  - name: DATABASE_URL
    valueFrom:
      secretKeyRef:
        name: api-secrets
        key: database-url

7. Container-Ressourcen begrenzen

Ein unbegrenzter Container kann sämtliche Host-Ressourcen verbrauchen und so einen Denial-of-Service verursachen:

docker run \
  --memory=512m \
  --memory-swap=512m \
  --cpus=1.0 \
  --pids-limit=256 \
  --ulimit nofile=1024:1024 \
  my-app:latest

In Kubernetes setzen Sie immer Resource Requests und Limits:

resources:
  requests:
    memory: "256Mi"
    cpu: "250m"
  limits:
    memory: "512Mi"
    cpu: "1000m"

8. Alle Capabilities entfernen

Linux Capabilities geben feingranulare Kontrolle darüber, was ein Prozess tun darf. Entfernen Sie alle und fügen Sie nur das wieder hinzu, was Sie wirklich brauchen:

docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE my-app:latest

Häufige Capabilities und wann Sie sie tatsächlich brauchen:

CapabilityAnwendungsfall
NET_BIND_SERVICEAn Ports unter 1024 binden
CHOWNDateieigentümer ändern (selten nötig)
SYS_PTRACENur zum Debugging, niemals in Produktion
SYS_ADMINFast nie -- das ist nahezu gleichwertig mit root

9. Multi-Stage-Builds nutzen, um Build-Tools auszuschließen

Build-Abhängigkeiten wie Compiler, Debugger und Quellcode haben in Ihrem Produktions-Image nichts zu suchen:

# Stage 1: Build
FROM golang:1.22 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server

# Stage 2: Production
FROM scratch
COPY --from=builder /app/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
ENTRYPOINT ["/server"]

Das finale Image enthält nur das kompilierte Binary und die TLS-Zertifikate. Keine Go-Toolchain, kein Quellcode, keine Shell.

10. Docker Content Trust aktivieren und Images signieren

Stellen Sie sicher, dass in Ihrer Umgebung nur verifizierte Images laufen:

# Enable content trust
export DOCKER_CONTENT_TRUST=1

# Sign and push
docker trust sign myregistry.com/my-app:v1.2.3

# Verify
docker trust inspect myregistry.com/my-app:v1.2.3

Verwenden Sie in Kubernetes-Umgebungen Admission Controller wie Kyverno oder OPA Gatekeeper, um Image-Policies zu erzwingen:

# Kyverno policy: require signed images
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signatures
spec:
  validationFailureAction: Enforce
  rules:
    - name: verify-signature
      match:
        resources:
          kinds:
            - Pod
      verifyImages:
        - imageReferences:
            - "myregistry.com/*"
          attestors:
            - entries:
                - keys:
                    publicKeys: |-
                      -----BEGIN PUBLIC KEY-----
                      ...
                      -----END PUBLIC KEY-----

Security-Checkliste

Bevor Sie irgendeinen Container in Produktion deployen, verifizieren Sie:

  • Base Image ist minimal (Distroless, Alpine oder scratch)
  • Image ist auf CVEs gescannt, keine HIGH/CRITICAL-Funde
  • Container läuft als Non-Root-Benutzer
  • Dateisystem ist wo möglich read-only
  • Keine Secrets ins Image eingebaut
  • Resource Limits sind gesetzt
  • Alle unnötigen Capabilities sind entfernt
  • Image-Tags sind auf Digests gepinnt
  • Images sind signiert und verifiziert
  • Network Policies beschränken die Container-Kommunikation

Fazit

Container-Security ist kein einzelnes Tool und kein einmaliges Audit. Sie ist ein Bündel von Praktiken, das in jeder Phase Ihrer Pipeline verankert ist -- vom Dockerfile bis zur Runtime-Konfiguration. Die zehn oben beschriebenen Praktiken adressieren die häufigsten Angriffsvektoren, die wir in Produktionsumgebungen sehen.

Bei DevOpsVibe helfen wir Teams, sichere Container-Pipelines von Grund auf aufzubauen. Ob Sie ein Security-Audit Ihres bestehenden Setups benötigen oder DevSecOps-Praktiken organisationsweit etablieren wollen -- unser Team verfügt über die Expertise, es richtig umzusetzen. Nehmen Sie Kontakt auf, um das Gespräch zu beginnen.

abgelegt unter
dockerdockersecuritycontainersdevsecopshardeningkuberneteskubernetes
mit uns arbeiten

Soll unser Team Ihrer Infrastruktur helfen?

talk to an engineerFree 30-min discovery callBook
close