Docker Tutorial #10: Security Best Practices – Container sicher betreiben
Die wichtigsten Sicherheitsrisiken
- Root-User in Containern
- Veraltete Base Images
- Secrets in Images gespeichert
- Unnötige Berechtigungen
- Ungesicherte Docker-Socket
Best Practice 1: Nie als Root ausführen
Problem
dockerfile
FROM node:18
WORKDIR /app
COPY . .
CMD ["node", "server.js"]
<em># Container läuft als root! ❌</em>
Lösung
dockerfile
FROM node:18-alpine
<em># User erstellen</em>
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
WORKDIR /app
<em># Files mit richtigem Owner kopieren</em>
COPY --chown=nodejs:nodejs package*.json ./
RUN npm ci --only=production
COPY --chown=nodejs:nodejs . .
<em># Als non-root user ausführen</em>
USER nodejs
EXPOSE 3000
CMD ["node", "server.js"]
Warum wichtig?
- Root-User kann Host-System beeinflussen
- Bei Exploit hat Angreifer volle Rechte
- Prinzip der minimalen Berechtigungen
Best Practice 2: Minimale Base Images
Problem
dockerfile
FROM ubuntu:22.04 # 77 MB, viele Pakete
Lösung
dockerfile
FROM alpine:3.18 # 7 MB, minimal
<em># Oder noch besser:</em>
FROM scratch # 0 MB, nur Binary
Alpine vs. Ubuntu:
Ubuntu: 77 MB + zahlreiche Pakete = größere Angriffsfläche
Alpine: 7 MB + minimale Pakete = kleinere Angriffsfläche
Vergleich
dockerfile
<em># ❌ Groß</em>
FROM php:8.2-apache # 500+ MB
<em># ✅ Klein</em>
FROM php:8.2-fpm-alpine # ~100 MB
Best Practice 3: Image-Versionen fixieren
Problem
dockerfile
FROM node:latest # ❌ Nicht reproduzierbar!
Risiken:
- Builds nicht reproduzierbar
- Breaking Changes ohne Warnung
- Sicherheitslücken in neuen Versionen
Lösung
dockerfile
FROM node:18.17.1-alpine3.18 # ✅ Exakte Version
Versionierungs-Schema:
node:18.17.1-alpine3.18
| | | | |
| | | | └─ Alpine-Version
| | | └────── OS-Variante
| | └─────────── Patch-Version
| └────────────── Minor-Version
└────────────────── Major-Version
Best Practice 4: Keine Secrets im Image
Problem
dockerfile
FROM node:18
COPY .env /app/.env # ❌ Secrets im Image!
RUN npm install
CMD ["node", "server.js"]
Gefahr: Jeder mit Zugriff auf das Image kann Secrets auslesen!
Lösung 1: Environment Variables
yaml
<em># docker-compose.yml</em>
services:
app:
image: myapp
environment:
DATABASE_PASSWORD: ${DB_PASSWORD} <em># Aus .env laden</em>
API_KEY: ${API_KEY}
Lösung 2: Docker Secrets (Swarm)
yaml
services:
app:
secrets:
- db_password
- api_key
secrets:
db_password:
external: true
api_key:
external: true
bash
<em># Secrets erstellen</em>
echo "my_secret_password" | docker secret create db_password -
Lösung 3: Secrets Management (Production)
- HashiCorp Vault
- AWS Secrets Manager
- Azure Key Vault
- Google Secret Manager
Best Practice 5: Multi-Stage Builds
Entfernen Sie Build-Tools aus dem finalen Image:
dockerfile
<em># ❌ Build-Tools bleiben im Image</em>
FROM node:18
WORKDIR /app
COPY . .
RUN npm install # Auch devDependencies!
CMD ["node", "dist/server.js"]
<em># ✅ Multi-Stage Build</em>
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
<em># Production Stage</em>
FROM node:18-alpine
RUN addgroup -g 1001 nodejs && adduser -S nodejs -u 1001
WORKDIR /app
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
USER nodejs
CMD ["node", "dist/server.js"]
Vorteile:
- Kleineres finales Image
- Keine Build-Tools im Production-Image
- Weniger Angriffsfläche
Best Practice 6: Vulnerability Scanning
Docker Scout (offiziell)
bash
<em># Image scannen</em>
docker scout cves myapp:latest
<em># Detaillierter Report</em>
docker scout quickview myapp:latest
<em># Empfehlungen</em>
docker scout recommendations myapp:latest
Trivy (Open Source)
bash
<em># Installation</em>
docker pull aquasec/trivy
<em># Image scannen</em>
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image myapp:latest
<em># Nur HIGH und CRITICAL</em>
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image --severity HIGH,CRITICAL myapp:latest
Snyk
bash
<em># Installation</em>
npm install -g snyk
<em># Login</em>
snyk auth
<em># Docker Image scannen</em>
snyk container test myapp:latest
Best Practice 7: Read-Only Root Filesystem
yaml
services:
app:
image: myapp
read_only: true
tmpfs:
- /tmp
- /run
Warum?
- Verhindert Änderungen am Dateisystem
- Malware kann sich nicht installieren
- Angreifer kann keine Backdoors einrichten
Best Practice 8: Ressourcen-Limits
yaml
services:
app:
image: myapp
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
Schutz vor:
- DoS-Attacken
- Memory Leaks
- CPU-intensiven Angriffen
Best Practice 9: Network Isolation
yaml
services:
frontend:
networks:
- frontend-net
backend:
networks:
- frontend-net
- backend-net
database:
networks:
- backend-net <em># Nur Backend hat Zugriff!</em>
networks:
frontend-net:
backend-net:
Best Practice 10: Capabilities einschränken
yaml
services:
app:
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE <em># Nur Port 80/443 binden</em>
Standard Capabilities entfernen:
yaml
cap_drop:
- SETUID
- SETGID
- NET_RAW
- SYS_CHROOT
- MKNOD
- AUDIT_WRITE
- SETFCAP
Best Practice 11: Healthchecks
dockerfile
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
Compose:
yaml
services:
app:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Best Practice 12: Logging Security Events
yaml
services:
app:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
labels: "security,audit"
Wichtige Security Events loggen:
- Login-Versuche
- API-Zugriffe
- Datenbankzugriffe
- Fehler und Exceptions
Best Practice 13: Regelmäßige Updates
bash
#!/bin/bash
<em># update-images.sh</em>
<em># Base Images aktualisieren</em>
docker compose pull
<em># Neu bauen</em>
docker compose build --no-cache
<em># Mit neuen Images starten</em>
docker compose up -d
<em># Alte Images aufräumen</em>
docker image prune -a -f
Automation:
yaml
<em># .gitlab-ci.yml</em>
scheduled-update:
schedule:
- cron: "0 2 * * 0" <em># Jeden Sonntag um 2 Uhr</em>
script:
- ./update-images.sh
Best Practice 14: Docker Socket absichern
Problem
yaml
<em># ❌ GEFÄHRLICH!</em>
volumes:
- /var/run/docker.sock:/var/run/docker.sock
Risiko: Container kann Host-System übernehmen!
Lösung 1: Vermeiden
Nutzen Sie Alternativen wie Docker-in-Docker oder Buildah.
Lösung 2: Socket Proxy
yaml
services:
docker-proxy:
image: tecnativa/docker-socket-proxy
environment:
CONTAINERS: 1
IMAGES: 1
VOLUMES: 0
POST: 0
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
app:
environment:
DOCKER_HOST: tcp://docker-proxy:2375
Best Practice 15: .dockerignore nutzen
# .dockerignore
node_modules
npm-debug.log
.git
.env
.env.*
*.md
.DS_Store
.vscode
.idea
coverage
__pycache__
*.pyc
.pytest_cache
dist
build
secrets/
*.key
*.pem
Warum?
- Verhindert Secrets im Image
- Kleinere Images
- Schnellere Builds
Security Checklist für Production
Image
- Minimales Base Image (Alpine/Distroless)
- Fixierte Versionen (kein
latest) - Non-Root User
- Multi-Stage Build
- Vulnerability Scan durchgeführt
- Keine Secrets im Image
- .dockerignore vorhanden
- Health Check implementiert
Container
- Read-Only Filesystem
- Ressourcen-Limits gesetzt
- Capabilities eingeschränkt
- Logging konfiguriert
- Network Isolation
- Restart Policy gesetzt
Infrastruktur
- TLS/SSL aktiviert
- Firewall konfiguriert
- Regelmäßige Updates
- Monitoring aktiv
- Backup-Strategie
- Secrets Management
- Audit Logging
Tools und Automation
Hadolint – Dockerfile Linter
bash
<em># Installation</em>
docker pull hadolint/hadolint
<em># Dockerfile prüfen</em>
docker run --rm -i hadolint/hadolint < Dockerfile
<em># In CI/CD</em>
docker run --rm -i hadolint/hadolint < Dockerfile || exit 1
Dockle – Container Linter
bash
<em># Installation</em>
docker pull goodwithtech/dockle
<em># Image prüfen</em>
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
goodwithtech/dockle myapp:latest
GitHub Actions Security
yaml
name: Docker Security
on: [push]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build image
run: docker build -t myapp .
- name: Run Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:latest
severity: CRITICAL,HIGH
- name: Run Hadolint
uses: hadolint/hadolint-action@v3.1.0
with:
dockerfile: Dockerfile
Incident Response
Container kompromittiert?
bash
<em># 1. Container isolieren</em>
docker network disconnect bridge suspicious-container
<em># 2. Logs sichern</em>
docker logs suspicious-container > incident.log
docker inspect suspicious-container > incident-details.json
<em># 3. Forensik</em>
docker export suspicious-container > container-filesystem.tar
<em># 4. Container stoppen</em>
docker stop suspicious-container
<em># 5. Analyse durchführen</em>
<em># 6. Neue, sichere Version deployen</em>
Zusammenfassung
Die 15 wichtigsten Security-Maßnahmen:
- ✅ Non-Root User verwenden
- ✅ Minimale Base Images
- ✅ Fixierte Versionen
- ✅ Keine Secrets im Image
- ✅ Multi-Stage Builds
- ✅ Vulnerability Scanning
- ✅ Read-Only Filesystem
- ✅ Ressourcen-Limits
- ✅ Network Isolation
- ✅ Capabilities einschränken
- ✅ Health Checks
- ✅ Security Logging
- ✅ Regelmäßige Updates
- ✅ Docker Socket absichern
- ✅ .dockerignore nutzen
Security ist kein Zustand, sondern ein Prozess!

Author: Andreas Lang
Andreas Lang konzentriert sich seit zwei Jahrzehnten auf die Webentwicklung und Webdesign mit dem Schwerpunkt PHP, Laravel und Javascript und betreut seine Kunden mit Herz und Seele in allen Bereichen von Entwicklung, Design, Suchmaschinenoptimierung, IT-Recht, IT-Sicherheit etc.

