Container-Sicherheit - die wichtigsten Stellschrauben
Container sind keine Sicherheitsgrenze — sie sind eine Isolationsschicht. Wer das verwechselt, hat ein Problem.
Das Missverständnis aufräumen
Ein Container läuft auf demselben Kernel wie der Host. Keine VM, kein Hypervisor dazwischen. Das bedeutet: eine Kernel-Schwachstelle, ein falsch konfigurierter Mount, zu viele Rechte — und die Isolierung ist weg.
Images aktuell halten und schlank halten
Das größte Risiko sitzt oft im Basis-Image. latest zu pullen und darauf zu vertrauen ist keine Strategie — der Tag ändert sich still, und niemand weiß was drin ist.
Images vor dem Deploy auf bekannte Sicherheitslücken scannen:
# Trivy scannt Images auf bekannte CVEs
trivy image nginx:latest
# Nur kritische und schwerwiegende Lücken anzeigen
trivy image --severity HIGH,CRITICAL myapp:1.2.3
Alpine- oder Distroless-Basis-Images helfen zusätzlich: weniger vorinstallierte Pakete bedeuten weniger potenzielle Angriffspunkte. Ein vollständiges Ubuntu als Basis für eine einzelne Go-Binary zu verwenden ist schlicht unnötig.
Multi-Stage Builds verhindern außerdem, dass Build-Tools oder Zugangsdaten im finalen Image landen:
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN go build -o /app/server
FROM gcr.io/distroless/base-debian12
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]
Container mit minimalen Rechten starten
Der falsche Weg: Container mit --privileged starten. Das gibt dem Container nahezu vollen Zugriff auf den Host — wird trotzdem regelmäßig gemacht, weil irgendwas "nicht funktioniert hat" und das die schnelle Lösung war.
Der richtige Weg: kein Root, kein --privileged, so wenig Rechte wie möglich:
services:
app:
image: myapp:1.0.0
user: "1000:1000"
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp
no-new-privileges verhindert, dass Prozesse im Container sich über Umwege zusätzliche Rechte verschaffen. read_only: true macht das Dateisystem des Containers schreibgeschützt — /tmp wird per tmpfs trotzdem beschreibbar gehalten, weil viele Anwendungen das brauchen.
Secrets nicht ins Image backen
Klassischer Fehler: Passwörter oder API-Keys direkt im Dockerfile via ENV setzen oder .env-Dateien ins Image kopieren. Beides landet in den Image-Layern und ist im Nachhinein auslesbar:
docker history --no-trunc myapp:1.0.0
Zugangsdaten gehören zur Laufzeit injiziert — nicht beim Build. Mindestens eine .dockerignore die verhindert dass sensible Dateien überhaupt ins Image gelangen:
.env
.env.*
*.pem
*.key
Netzwerk zwischen Containern einschränken
Standardmäßig können alle Container im selben Docker-Netzwerk miteinander kommunizieren. In einem Compose-Stack mit mehreren Services bedeutet das: jeder Service kann jeden anderen direkt ansprechen — auch die Datenbank.
Explizite Netzwerke lösen das:
networks:
frontend:
backend:
services:
nginx:
networks:
- frontend
app:
networks:
- frontend
- backend
db:
networks:
- backend # nur app erreicht die DB
Checkliste
- Images: regelmäßig scannen, minimale Basis, Multi-Stage Builds
- Laufzeit: kein Root, kein
--privileged,no-new-privileges, read-only Filesystem - Secrets: nie im Image, immer zur Laufzeit injiziert
- Netzwerk: explizite Segmentierung statt Default-Netz