{"id":255,"date":"2026-06-20T16:22:35","date_gmt":"2026-06-20T16:22:35","guid":{"rendered":"https:\/\/shattered.io\/de\/2026\/06\/20\/docker-container-absichern-tutorial\/"},"modified":"2026-06-20T16:24:02","modified_gmt":"2026-06-20T16:24:02","slug":"docker-container-absichern-tutorial","status":"publish","type":"post","link":"https:\/\/shattered.io\/de\/2026\/06\/20\/docker-container-absichern-tutorial\/","title":{"rendered":"Docker Container absichern: 12 Schritte, 45 Min [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Im Juni 2026 betreffen 60 % aller Cloud-Umgebungen noch immer die runc-Schwachstelle <strong>CVE-2024-21626<\/strong> (CVSS 8.6), die es Angreifern erlaubt, aus einem Container auszubrechen und Root-Rechte auf dem Host zu erlangen. Docker Desktop enthielt in diesem Jahr allein f\u00fcnf kritische Sicherheitsl\u00fccken, darunter <strong>CVE-2026-33990<\/strong> mit einem CVSS-Score von 9.1. Wer Container produktiv betreibt, ohne eine durchdachte Sicherheitsstrategie, setzt seinen gesamten Server auf Spiel. Dieses Tutorial zeigt, wie Sie Docker-Container in 12 konkreten Schritten absichern, mit Codebeispielen, typischen Fehlern und einem vollst\u00e4ndigen Praxisprojekt.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"warum-docker-sicherheit-2026-nicht-optional-ist\">Warum Docker-Sicherheit 2026 nicht optional ist<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Docker ist das am h\u00e4ufigsten genutzte Container-Format weltweit. Hinter der Bequemlichkeit von <code>docker run<\/code> verbergen sich jedoch erhebliche Angriffsrisiken, wenn Standard-Konfigurationen unver\u00e4ndert bleiben. Die Schwachstelle CVE-2024-21626 im runc-Container-Runtime zeigte 2024, wie ein einziges manipuliertes Image den Ausbruch aus der Container-Isolation erm\u00f6glicht. Laut Orca Security waren 60 % aller gescannten Cloud-Umgebungen betroffen. Der Fix erfordert runc 1.1.12 oder neuer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Docker Desktop selbst listete in der ersten H\u00e4lfte 2026 mehrere kritische CVEs: <strong>CVE-2026-33990<\/strong> (CVSS 9.1, kritische L\u00fccke im OCI Registry Client des Docker Model Runners), <strong>CVE-2026-28400<\/strong> (Runtime-Flag-Injektion, behoben in Docker Desktop 4.67.0) und <strong>CVE-2025-9074<\/strong> (unauthentifizierter Zugriff auf die Docker Engine API, behoben in Docker Desktop 4.44.3). Ein weiteres Problem: <strong>CVE-2025-3911<\/strong> legte sensible Daten einschlie\u00dflich Umgebungsvariablen in Docker-Desktop-Logdateien offen. Das ist der direkte Beweis daf\u00fcr, warum Geheimnisse niemals als Umgebungsvariablen gespeichert werden d\u00fcrfen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Supply-Chain-Angriffe auf Container-Images nehmen zu. Angreifer schleusen Schadcode in popul\u00e4re Basis-Images ein, die Millionen von Deployments ziehen. Die OWASP Container Security Cheat Sheet und das CIS Docker Benchmark bieten strukturierte Gegenma\u00dfnahmen. Dieses Tutorial setzt beides um, Schritt f\u00fcr Schritt, mit produktionsreifem Code.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"voraussetzungen\">Voraussetzungen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr dieses Tutorial ben\u00f6tigen Sie:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Docker Engine 27.x oder neuer<\/strong> (Docker Desktop 4.67.0+ f\u00fcr macOS\/Windows)<\/li>\n<li><strong>Linux-Server oder -VM<\/strong> mit Ubuntu 22.04\/24.04 oder Debian 12 (empfohlen f\u00fcr Produktion)<\/li>\n<li><strong>Docker Compose 2.x<\/strong> f\u00fcr Mehrcontainer-Setups<\/li>\n<li><strong>Trivy 0.52+<\/strong> f\u00fcr Image-Scanning (wird in Schritt 9 installiert)<\/li>\n<li><strong>cosign 2.x<\/strong> f\u00fcr Image-Signing (wird in Schritt 10 installiert)<\/li>\n<li>Grundkenntnisse in Dockerfile-Syntax und Linux-Benutzerrechten<\/li>\n<li>Root- oder sudo-Zugang zum Host-System<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Die gesch\u00e4tzte Zeit f\u00fcr dieses Tutorial betr\u00e4gt 45 Minuten bei Befolgung aller Schritte. Produktionsumgebungen sollten alle 12 Schritte umsetzen. F\u00fcr Entwicklungsumgebungen sind mindestens die Schritte 1 bis 8 empfehlenswert.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-1-docker-aktuell-halten-und-daemon-absichern\">Schritt 1: Docker aktuell halten und Daemon absichern<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Die wichtigste Sicherheitsma\u00dfnahme ist die aktuellste Docker-Version. CVE-2024-21626 war monatelang ungepatcht in Produktionsumgebungen aktiv, weil Administratoren keine automatischen Updates eingerichtet hatten. Pr\u00fcfen Sie zun\u00e4chst Ihre aktuelle Version und konfigurieren Sie den Docker-Daemon mit Sicherheitsoptionen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Docker-Version und runc-Version pr\u00fcfen\ndocker version\nrunc --version\n\n# Docker-Daemon-Konfiguration absichern\nsudo mkdir -p \/etc\/docker\nsudo tee \/etc\/docker\/daemon.json &lt;&lt;'EOF'\n{\n  \"no-new-privileges\": true,\n  \"userns-remap\": \"default\",\n  \"live-restore\": true,\n  \"userland-proxy\": false,\n  \"icc\": false,\n  \"log-driver\": \"json-file\",\n  \"log-opts\": {\n    \"max-size\": \"10m\",\n    \"max-file\": \"3\"\n  }\n}\nEOF\nsudo systemctl restart docker\n\n# Automatische Sicherheitsupdates einrichten (Ubuntu\/Debian)\nsudo apt-get install -y unattended-upgrades\necho 'Unattended-Upgrade::Allowed-Origins {\n    \"Docker:${distro_codename}\";\n};' | sudo tee \/etc\/apt\/apt.conf.d\/51docker-security<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Die Option <code>\"no-new-privileges\": true<\/code> verhindert, dass Prozesse innerhalb von Containern \u00fcber setuid-Bin\u00e4rdateien zus\u00e4tzliche Privilegien erlangen. <code>\"userns-remap\": \"default\"<\/code> mappt den Root-User im Container auf einen unprivilegierten Benutzer auf dem Host, was den Impact von Container-Escapes erheblich reduziert. <code>\"icc\": false<\/code> deaktiviert die Inter-Container-Kommunikation standardm\u00e4\u00dfig und erzwingt explizite Netzwerkregeln. <code>\"userland-proxy\": false<\/code> entfernt den Docker Proxy-Prozess f\u00fcr Port-Mappings und nutzt stattdessen direkte iptables-Regeln, was die Angriffsfl\u00e4che verkleinert.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pr\u00fcfen Sie nach dem Neustart, ob die Konfiguration korrekt geladen wurde: <code>docker info | grep -E \"Security|Runtimes|remap\"<\/code>. Das Ergebnis sollte <code>userns-remap: dockremap<\/code> und die konfigurierten Security-Optionen anzeigen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-2-minimale-basis-images-verwenden\">Schritt 2: Minimale Basis-Images verwenden<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Das gr\u00f6\u00dfte Angriffspotenzial in Docker-Images liegt in unn\u00f6tigen Paketen. Ein <code>ubuntu:latest<\/code>-Image enth\u00e4lt Hunderte von Bin\u00e4rdateien, die ein Angreifer nach einem erfolgreichen Exploit nutzen kann. Distroless-Images von Google und Alpine Linux reduzieren die Angriffsfl\u00e4che auf ein Minimum.<\/p>\n\n\n\n<table class=\"wp-block-table\"><thead><tr><th>Basis-Image<\/th><th>Komprimierte Gr\u00f6\u00dfe<\/th><th>Pakete (ca.)<\/th><th>Typische kritische CVEs<\/th><th>Empfehlung<\/th><\/tr><\/thead><tbody><tr><td><code>ubuntu:24.04<\/code><\/td><td>~29 MB<\/td><td>~400<\/td><td>5-15<\/td><td>Nur f\u00fcr Entwicklung<\/td><\/tr><tr><td><code>debian:bookworm-slim<\/code><\/td><td>~31 MB<\/td><td>~100<\/td><td>2-8<\/td><td>Akzeptabel<\/td><\/tr><tr><td><code>alpine:3.20<\/code><\/td><td>~3,3 MB<\/td><td>~14<\/td><td>0-2<\/td><td>Gut geeignet<\/td><\/tr><tr><td><code>gcr.io\/distroless\/nodejs22-debian12<\/code><\/td><td>~60 MB<\/td><td>0 (nur Node.js)<\/td><td>0-1<\/td><td>Beste Wahl f\u00fcr Node.js<\/td><\/tr><tr><td><code>scratch<\/code><\/td><td>0 MB<\/td><td>0<\/td><td>0<\/td><td>Nur f\u00fcr statische Bin\u00e4rdateien<\/td><\/tr><\/tbody><\/table>\n\n\n\n<p class=\"wp-block-paragraph\">Distroless-Images enthalten keine Shell, keinen Paketmanager und keine unn\u00f6tigen Systemwerkzeuge. Ein Angreifer, der in einen Distroless-Container einbricht, findet weder <code>bash<\/code> noch <code>curl<\/code> noch <code>wget<\/code>, was Post-Exploitation erheblich erschwert. F\u00fcr Node.js-Anwendungen ist <code>gcr.io\/distroless\/nodejs22-debian12<\/code> die erste Wahl f\u00fcr Produktionsumgebungen. Das <code>:nonroot<\/code>-Tag verwendet automatisch den Benutzer mit UID 65532 ohne Root-Rechte.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Alpine Linux ist ebenfalls eine gute Wahl, nutzt jedoch musl libc statt glibc, was bei manchen npm-Paketen mit nativen Erweiterungen zu Kompatibilit\u00e4tsproblemen f\u00fchren kann. Testen Sie Alpine immer gr\u00fcndlich vor dem Einsatz in der Produktion. F\u00fcr Python-Workloads eignet sich <code>python:3.12-slim-bookworm<\/code> als sichere Alternative zu <code>python:3.12<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-3-multi-stage-builds-fuer-sichere-schlanke-images\">Schritt 3: Multi-Stage Builds f\u00fcr sichere, schlanke Images<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Multi-Stage Builds trennen die Build-Umgebung von der Laufzeitumgebung. Build-Tools wie Compiler, npm, pip und Make verbleiben ausschlie\u00dflich im Build-Stage und erscheinen nicht im finalen Image. Das reduziert die Angriffsfl\u00e4che erheblich und verkleinert Images typischerweise um 60 bis 90 Prozent. Au\u00dferdem landen keine Build-Geheimnisse (SSH-Keys, npm-Tokens) im fertigen Image.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Dockerfile mit Multi-Stage Build f\u00fcr eine Node.js-Anwendung\n\n# Stage 1: Abh\u00e4ngigkeiten installieren und Build erstellen\nFROM node:22-alpine AS builder\n\nWORKDIR \/app\n\n# Nur package.json zuerst kopieren (besseres Layer-Caching)\nCOPY package.json package-lock.json .\/\nRUN npm ci --only=production &amp;&amp; npm cache clean --force\n\n# Quellcode kopieren und kompilieren\nCOPY src\/ .\/src\/\n\n# Stage 2: Minimales Produktions-Image (Distroless, kein Root)\nFROM gcr.io\/distroless\/nodejs22-debian12:nonroot AS production\n\nWORKDIR \/app\n\n# Nur die ben\u00f6tigten Artefakte aus Stage 1 kopieren\nCOPY --from=builder \/app\/node_modules .\/node_modules\nCOPY --from=builder \/app\/src .\/src\nCOPY --from=builder \/app\/package.json .\/\n\nEXPOSE 3000\n\nCMD [\"src\/server.js\"]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ein h\u00e4ufiger Fehler bei Multi-Stage Builds ist das Kopieren des gesamten Build-Kontexts mit <code>COPY . .<\/code>, bevor die <code>.dockerignore<\/code>-Datei korrekt konfiguriert wurde. Das f\u00fchrt dazu, dass <code>.env<\/code>-Dateien, private Schl\u00fcssel oder Git-Historien in den Build-Kontext gelangen. Die <code>.dockerignore<\/code>-Datei (Schritt 3b) ist daher ebenso wichtig wie das Dockerfile selbst.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-4-container-als-nicht-root-benutzer-ausfuehren\">Schritt 4: Container als Nicht-Root-Benutzer ausf\u00fchren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Standardm\u00e4\u00dfig f\u00fchrt Docker Container als Root-Benutzer (UID 0) aus. Das ist das Container-Sicherheits\u00e4quivalent von &#8220;als Administrator im Internet surfen&#8221;. Wenn ein Angreifer Remote-Code-Execution im Container erlangt, handelt er mit Root-Rechten innerhalb des Namespace. Bei einem Container-Escape erh\u00e4lt er potenziell Root-Rechte auf dem Host. Jeder Container sollte als dedizierter Nicht-Root-Benutzer laufen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Dockerfile: Nicht-Root-Benutzer anlegen und verwenden\nFROM node:22-alpine\n\nWORKDIR \/app\n\n# Systembenutzer und -gruppe anlegen (kein Home-Verzeichnis, keine Login-Shell)\nRUN addgroup -S appgroup -g 1001 &amp;&amp; \\\n    adduser -S appuser -u 1001 -G appgroup -s \/sbin\/nologin\n\n# Abh\u00e4ngigkeiten als Root installieren (npm ben\u00f6tigt ggf. Schreibrechte)\nCOPY package*.json .\/\nRUN npm ci --only=production &amp;&amp; npm cache clean --force\n\n# App-Dateien mit korrekten Berechtigungen kopieren\nCOPY --chown=appuser:appgroup src\/ .\/src\/\nRUN chown -R appuser:appgroup \/app\n\n# Zu Nicht-Root-Benutzer wechseln\nUSER appuser\n\nEXPOSE 3000\nCMD [\"node\", \"src\/server.js\"]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Beim Start k\u00f6nnen Sie den Benutzer zus\u00e4tzlich per Flag erzwingen und pr\u00fcfen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Container mit explizitem Benutzer starten\ndocker run --user 1001:1001 --rm myapp:latest\n\n# Pr\u00fcfen, welcher Benutzer im laufenden Container aktiv ist\ndocker exec CONTAINER_ID whoami\ndocker exec CONTAINER_ID id\n\n# Alle laufenden Container auf Root-Ausf\u00fchrung pr\u00fcfen\ndocker ps -q | xargs docker inspect --format='{{.Name}}: User={{.Config.User}}'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Wichtig: Manche Basis-Images setzen <code>USER root<\/code> als letzten Befehl. Pr\u00fcfen Sie mit <code>docker inspect IMAGE_NAME | grep -A2 '\"User\"'<\/code>, welcher Benutzer tats\u00e4chlich beim Start aktiv ist. Wenn das Ergebnis leer ist, bedeutet das Root. Setzen Sie immer explizit <code>USER<\/code> im Dockerfile als letzten Befehl vor <code>CMD<\/code> oder <code>ENTRYPOINT<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-5-read-only-dateisystem-und-tmpfs-konfigurieren\">Schritt 5: Read-Only-Dateisystem und tmpfs konfigurieren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ein Read-Only-Dateisystem verhindert, dass Angreifer Persistenz im Container herstellen, indem sie Dateien modifizieren oder neue Executables ablegen. Mit <code>--read-only<\/code> wird das gesamte Container-Dateisystem schreibgesch\u00fctzt. Verzeichnisse, die zur Laufzeit Schreibzugriff ben\u00f6tigen (z. B. f\u00fcr tempor\u00e4re Dateien), werden als <code>tmpfs<\/code> gemountet, also im RAM ohne Persistenz nach dem Container-Stopp.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Container mit Read-Only-Dateisystem starten\ndocker run \\\n  --read-only \\\n  --tmpfs \/tmp:rw,noexec,nosuid,size=64m \\\n  --tmpfs \/var\/run:rw,noexec,nosuid,size=10m \\\n  --user 1001:1001 \\\n  --rm \\\n  myapp:latest\n\n# In Docker Compose (compose.yml)\nservices:\n  app:\n    image: myapp:latest\n    read_only: true\n    tmpfs:\n      - \/tmp:size=64m,mode=1777,exec=0\n      - \/var\/run:size=10m,mode=755,exec=0\n    user: \"1001:1001\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Die Optionen <code>noexec<\/code> und <code>nosuid<\/code> f\u00fcr tmpfs verhindern die Ausf\u00fchrung von Bin\u00e4rdateien aus dem tempor\u00e4ren Verzeichnis und unterbinden SUID-Bit-Exploits. Kombiniert mit <code>--read-only<\/code> entsteht eine Umgebung, in der ein Angreifer keine dauerhaften \u00c4nderungen vornehmen kann. Manche Anwendungen ben\u00f6tigen Schreibzugriff auf bestimmte Verzeichnisse wie <code>\/app\/logs<\/code>. Mounten Sie diese als Named Volumes, nicht als Bind Mounts vom Host, um versehentliche Host-Dateisystem-Exposition zu vermeiden.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-6-docker-netzwerke-segmentieren\">Schritt 6: Docker-Netzwerke segmentieren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Standardm\u00e4\u00dfig verbindet Docker alle Container im selben <code>bridge<\/code>-Netzwerk, was Inter-Container-Kommunikation ohne Einschr\u00e4nkungen erlaubt. Ein kompromittierter Datenbankcontainer kann dann direkt mit dem API-Container kommunizieren und umgekehrt. Mit der Daemon-Option <code>\"icc\": false<\/code> aus Schritt 1 und dedizierten Netzwerken werden alle Kommunikationswege explizit definiert.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># compose.yml mit isolierten Netzwerken\nservices:\n  nginx:\n    image: nginx:alpine\n    ports:\n      - \"443:443\"\n    networks:\n      - frontend\n    read_only: true\n\n  api:\n    image: myapp:latest\n    networks:\n      - frontend\n      - backend\n    read_only: true\n    user: \"1001:1001\"\n    environment:\n      NODE_ENV: production\n\n  postgres:\n    image: postgres:16-alpine\n    networks:\n      - backend\n    environment:\n      POSTGRES_PASSWORD_FILE: \/run\/secrets\/db_password\n    secrets:\n      - db_password\n    volumes:\n      - pgdata:\/var\/lib\/postgresql\/data\n\nnetworks:\n  frontend:\n    driver: bridge\n  backend:\n    driver: bridge\n    internal: true  # Kein Internetzugang fuer Backend-Container\n\nsecrets:\n  db_password:\n    file: .\/secrets\/db_password.txt\n\nvolumes:\n  pgdata:<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Das <code>internal: true<\/code>-Flag f\u00fcr das Backend-Netzwerk verhindert, dass die Datenbank selbst Verbindungen ins Internet aufbauen kann. Das ist entscheidend: Viele Malware-Samples exfiltrieren Daten nach dem Einbruch nach drau\u00dfen. Ein internes Netzwerk unterbricht diesen Kanal vollst\u00e4ndig. Der Nginx-Container ist das einzige Gateway nach au\u00dfen, der API-Container kommuniziert mit beiden Netzwerken, und die Datenbank ist vollst\u00e4ndig isoliert.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-7-linux-capabilities-einschraenken\">Schritt 7: Linux-Capabilities einschr\u00e4nken<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Docker-Container erhalten standardm\u00e4\u00dfig eine Reihe von Linux-Capabilities, die in den meisten Anwendungsf\u00e4llen nicht ben\u00f6tigt werden. Das Prinzip des minimalen Privilegs gilt auch hier: Entfernen Sie alle Capabilities mit <code>--cap-drop=ALL<\/code> und f\u00fcgen Sie nur die tats\u00e4chlich ben\u00f6tigten wieder hinzu.<\/p>\n\n\n\n<table class=\"wp-block-table\"><thead><tr><th>Capability<\/th><th>Standardm\u00e4\u00dfig aktiv<\/th><th>Beschreibung<\/th><th>Empfehlung f\u00fcr Web-API<\/th><\/tr><\/thead><tbody><tr><td>NET_BIND_SERVICE<\/td><td>Ja<\/td><td>Ports unter 1024 binden<\/td><td>Entfernen, wenn Port > 1024<\/td><\/tr><tr><td>CHOWN<\/td><td>Ja<\/td><td>Dateibesitzer \u00e4ndern<\/td><td>Entfernen nach dem Build<\/td><\/tr><tr><td>SETUID<\/td><td>Ja<\/td><td>Benutzer-ID wechseln<\/td><td>Immer entfernen<\/td><\/tr><tr><td>SETGID<\/td><td>Ja<\/td><td>Gruppen-ID wechseln<\/td><td>Immer entfernen<\/td><\/tr><tr><td>SYS_ADMIN<\/td><td>Nein<\/td><td>Systemadministration<\/td><td>Niemals hinzuf\u00fcgen<\/td><\/tr><tr><td>NET_RAW<\/td><td>Ja<\/td><td>RAW-Sockets (Ping etc.)<\/td><td>In Produktion entfernen<\/td><\/tr><tr><td>SYS_PTRACE<\/td><td>Nein<\/td><td>Prozesse debuggen<\/td><td>Nur in Entwicklung<\/td><\/tr><\/tbody><\/table>\n\n\n\n<pre class=\"wp-block-code\"><code># Alle Capabilities entfernen, nur benoetigte hinzufuegen\ndocker run \\\n  --cap-drop=ALL \\\n  --cap-add=NET_BIND_SERVICE \\\n  --security-opt no-new-privileges:true \\\n  --user 1001:1001 \\\n  --read-only \\\n  myapp:latest\n\n# In Docker Compose\nservices:\n  app:\n    image: myapp:latest\n    cap_drop:\n      - ALL\n    cap_add:\n      - NET_BIND_SERVICE  # Nur wenn Port unter 1024 benoetigt\n    security_opt:\n      - no-new-privileges:true<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr Webserver und API-Dienste, die auf Ports \u00fcber 1024 lauschen (z. B. 3000, 8080), ben\u00f6tigen Sie keine einzige Capability \u00fcber die Grundfunktionen hinaus. Wenn Ihre Anwendung auf Port 3000 l\u00e4uft, k\u00f6nnen Sie sogar auf <code>NET_BIND_SERVICE<\/code> verzichten. Verwenden Sie immer einen Reverse Proxy (Nginx, Traefik, Caddy) vor Ihrem Anwendungscontainer, der sich um die Portweiterleitung von 443 auf 3000 k\u00fcmmert.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-8-secrets-sicher-verwalten\">Schritt 8: Secrets sicher verwalten<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>CVE-2025-3911<\/strong> belegte es: Docker Desktop-Logdateien enthielten Klartextdarstellungen von Umgebungsvariablen, darunter Passw\u00f6rter, API-Schl\u00fcssel und Datenbankverbindungsstrings. Umgebungsvariablen sind eine der unsichersten Methoden zur Weitergabe von Geheimnissen an Container. Sie sind sichtbar in <code>docker inspect<\/code>, in CI\/CD-Ausgaben, in <code>\/proc\/PID\/environ<\/code> und in Logdateien.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code\"># Docker Secret erstellen\necho \"mein_sehr_sicheres_passwort\" | docker secret create db_password -\n\n# Fuer Docker Compose ohne Swarm: Secret-Datei verwenden\nmkdir -p .\/secrets\nchmod 700 .\/secrets\necho \"mein_sehr_sicheres_passwort\" > .\/secrets\/db_password.txt\nchmod 600 .\/secrets\/db_password.txt\n\n# Secrets in .gitignore eintragen\necho \"secrets\/\" >> .gitignore\n\n# In der Anwendung (Node.js): Secret aus \/run\/secrets\/ lesen\n\/\/ config\/database.js\nimport fs from 'node:fs';\n\nfunction readSecret(name) {\n  const secretPath = `\/run\/secrets\/${name}`;\n  try {\n    return fs.readFileSync(secretPath, 'utf8').trim();\n  } catch {\n    \/\/ Fallback auf Umgebungsvariable (nur fuer lokale Entwicklung)\n    const envKey = name.toUpperCase().replace(\/-\/g, '_');\n    if (process.env.NODE_ENV !== 'production') {\n      return process.env[envKey];\n    }\n    throw new Error(`Secret '${name}' nicht gefunden und Produktion aktiv`);\n  }\n}\n\nexport const dbConfig = {\n  host: process.env.DB_HOST || 'postgres',\n  port: parseInt(process.env.DB_PORT || '5432'),\n  database: process.env.DB_NAME || 'myapp',\n  password: readSecret('db_password'),\n};<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr komplexere Geheimnisverwaltung in gr\u00f6\u00dferen Infrastrukturen empfiehlt sich die Integration von <a href=\"\/de\/hashicorp-vault-tutorial\/\">HashiCorp Vault<\/a>, das dynamische Credentials, automatische Rotation und detailliertes Audit-Logging bietet. Vault unterst\u00fctzt native Integration mit Docker und Kubernetes \u00fcber den Vault Agent oder den Secrets Store CSI Driver.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-9-container-images-mit-trivy-scannen\">Schritt 9: Container-Images mit Trivy scannen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Trivy ist der f\u00fchrende Open-Source-Vulnerability-Scanner f\u00fcr Container-Images, IaC-Dateien, Git-Repositories und Secrets. Das Tool scannt Images auf bekannte CVEs in OS-Paketen und Sprachpaketen (npm, pip, Go-Module) und findet gleichzeitig Fehlkonfigurationen sowie hartcodierte Zugangsdaten.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Trivy installieren (Ubuntu\/Debian)\nsudo apt-get install -y wget apt-transport-https gnupg\nwget -qO - https:\/\/aquasecurity.github.io\/trivy-repo\/deb\/public.key | sudo apt-key add -\necho deb https:\/\/aquasecurity.github.io\/trivy-repo\/deb generic main | sudo tee \/etc\/apt\/sources.list.d\/trivy.list\nsudo apt-get update &amp;&amp; sudo apt-get install -y trivy\n\n# Image auf kritische und hohe Schwachstellen scannen\ntrivy image --severity CRITICAL,HIGH myapp:latest\n\n# Dockerfile auf Fehlkonfigurationen pruefen\ntrivy config .\/Dockerfile\n\n# Geheimnisse im Quellcode scannen (vor dem Build)\ntrivy fs --scanners secret .\n\n# Im CI\/CD-Pipeline: Build fehlschlagen lassen bei CRITICAL\ntrivy image --exit-code 1 --severity CRITICAL myapp:latest\n\n# JSON-Bericht fuer SIEM oder Compliance exportieren\ntrivy image --format json --output trivy-report.json myapp:latest<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Integrieren Sie Trivy in Ihre CI\/CD-Pipeline, damit jeder Build automatisch auf Sicherheitsl\u00fccken gepr\u00fcft wird. Das folgende GitHub Actions-Beispiel scannt das Image und l\u00e4dt die Ergebnisse in den GitHub Security Tab hoch:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># .github\/workflows\/security.yml\nname: Container Security Scan\n\non:\n  push:\n    branches: [main, develop]\n\njobs:\n  trivy-scan:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n\n      - name: Docker Image bauen\n        run: docker build -t myapp:${{ github.sha }} .\n\n      - name: Trivy Vulnerability Scan\n        uses: aquasecurity\/trivy-action@master\n        with:\n          image-ref: myapp:${{ github.sha }}\n          format: sarif\n          output: trivy-results.sarif\n          severity: CRITICAL,HIGH\n          exit-code: 1\n\n      - name: Ergebnisse hochladen\n        uses: github\/codeql-action\/upload-sarif@v3\n        if: always()\n        with:\n          sarif_file: trivy-results.sarif<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Trivy pr\u00fcft bei npm-Packages auch die <code>package-lock.json<\/code>, die genauere Versionsinformationen enth\u00e4lt als <code>package.json<\/code>. Stellen Sie sicher, dass die Lock-Datei im Build-Kontext verf\u00fcgbar ist und durch <code>npm ci<\/code> (nicht <code>npm install<\/code>) verwendet wird, um reproduzierbare, auditierbare Builds zu erhalten. Richten Sie au\u00dferdem w\u00f6chentliche automatische Rebuilds Ihrer Images ein, um Basis-Image-Patches aufzunehmen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-10-image-signing-mit-cosign-und-sigstore\">Schritt 10: Image-Signing mit Cosign und Sigstore<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Image-Signing stellt sicher, dass ein Container-Image tats\u00e4chlich von Ihrem CI\/CD-System erstellt und nicht durch einen Supply-Chain-Angriff manipuliert wurde. Sigstore und das zugeh\u00f6rige Tool Cosign bieten eine moderne, schl\u00fcssellose Signaturinfrastruktur, die auf Certificate Transparency basiert. Seit April 2018 m\u00fcssen alle \u00f6ffentlich vertrauensw\u00fcrdigen TLS-Zertifikate in CT-Logs protokolliert werden. Cosign nutzt dasselbe Prinzip f\u00fcr Container-Images.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># cosign installieren\ncurl -O -L \"https:\/\/github.com\/sigstore\/cosign\/releases\/latest\/download\/cosign-linux-amd64\"\nsudo mv cosign-linux-amd64 \/usr\/local\/bin\/cosign\nsudo chmod +x \/usr\/local\/bin\/cosign\n\n# Schl\u00fcsselpaar erstellen (klassische Methode)\ncosign generate-key-pair\n# Erstellt cosign.key (privat, sicher aufbewahren!) und cosign.pub (oeffentlich)\n\n# Image signieren (nach dem Push in die Registry)\ndocker build -t registry.example.com\/myapp:1.0.0 .\ndocker push registry.example.com\/myapp:1.0.0\ncosign sign --key cosign.key registry.example.com\/myapp:1.0.0\n\n# Signatur verifizieren (in CI\/CD oder Admission Controller)\ncosign verify --key cosign.pub registry.example.com\/myapp:1.0.0\n\n# Schluesselloser Modus in GitHub Actions (kein Schluessel erforderlich)\n# Die Identitaet wird durch GitHub-OIDC-Token bewiesen\ncosign sign --yes registry.example.com\/myapp:1.0.0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Im schl\u00fcssellosen Modus kommuniziert Cosign mit der \u00f6ffentlichen Sigstore Fulcio CA, die ein kurzlebiges Zertifikat ausstellt, das an Ihre GitHub-Identit\u00e4t gebunden ist. Das Zertifikat und die Signatur werden im \u00f6ffentlichen Rekor-Transparenz-Log unver\u00e4nderlich protokolliert. F\u00fcr eine vollst\u00e4ndige Supply-Chain-Sicherheit in Kubernetes kombinieren Sie Cosign mit dem Admission Controller Connaisseur oder Kyverno, die nur signierte Images aus vertrauensw\u00fcrdigen Quellen zulassen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-11-ressourcenbeschraenkungen-und-prozesslimits\">Schritt 11: Ressourcenbeschr\u00e4nkungen und Prozesslimits<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ohne Ressourcenlimits kann ein einzelner kompromittierter Container den gesamten Host durch CPU-Mining (Cryptojacking) oder Speicher-Exhaustion lahmlegen. Ressourcenlimits sind eine kritische Schutzma\u00dfnahme gegen Denial-of-Service von innen und begrenzen den Schaden eines kompromittierten Containers.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Container mit vollstaendigen Ressourcenlimits starten\ndocker run \\\n  --memory=\"512m\" \\\n  --memory-swap=\"512m\" \\\n  --cpus=\"0.5\" \\\n  --pids-limit=100 \\\n  --ulimit nofile=1024:1024 \\\n  --user 1001:1001 \\\n  --read-only \\\n  myapp:latest\n\n# In Docker Compose\nservices:\n  app:\n    image: myapp:latest\n    deploy:\n      resources:\n        limits:\n          cpus: \"0.5\"\n          memory: 512M\n        reservations:\n          cpus: \"0.25\"\n          memory: 256M\n    ulimits:\n      nofile:\n        soft: 1024\n        hard: 1024\n    pids_limit: 100\n\n  # Ressourcenverbrauch live ueberwachen\n  # docker stats --format \"table {{.Container}}\\t{{.CPUPerc}}\\t{{.MemUsage}}\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Die Option <code>--memory-swap=\"512m\"<\/code> auf denselben Wert wie <code>--memory<\/code> zu setzen, deaktiviert Swap f\u00fcr den Container. Das verhindert, dass ein Speicher-Exhaustion-Angriff auf Festplattenspeicher ausweicht und den gesamten Swap-Bereich des Hosts belegt. <code>--pids-limit=100<\/code> begrenzt die Anzahl der Prozesse im Container und sch\u00fctzt vor Fork-Bomb-Angriffen. Passen Sie diese Werte an den tats\u00e4chlichen Bedarf Ihrer Anwendung an: zu niedrige Limits f\u00fchren zu legitimen Fehlern, zu hohe Limits bieten keinen ausreichenden Schutz.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-12-apparmor-und-seccomp-profile-anpassen\">Schritt 12: AppArmor und Seccomp-Profile anpassen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">AppArmor und Seccomp sind zwei Kernel-Sicherheitsmechanismen, die Docker standardm\u00e4\u00dfig mit einem konservativen Default-Profil nutzt. Das Default-Seccomp-Profil von Docker blockiert bereits 44 der gef\u00e4hrlichsten Syscalls, darunter <code>ptrace<\/code>, <code>reboot<\/code> und <code>kexec_load<\/code>. F\u00fcr kritische Produktionsumgebungen lohnt es sich, noch restriktivere, anwendungsspezifische Profile zu erstellen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># AppArmor-Status pruefen\nsudo aa-status | grep docker\n\n# Container mit explizitem Seccomp-Profil starten\ndocker run \\\n  --security-opt seccomp=seccomp-node.json \\\n  --security-opt no-new-privileges:true \\\n  --security-opt apparmor=docker-default \\\n  --user 1001:1001 \\\n  myapp:latest\n\n# Seccomp-Profil im Audit-Modus testen (loggt statt zu blockieren)\ndocker run \\\n  --security-opt 'seccomp={\"defaultAction\":\"SCMP_ACT_LOG\",\"syscalls\":[]}' \\\n  myapp:latest\n\n# Verwendete Syscalls ermitteln (fuer eigenes Profil)\ndocker run --cap-add SYS_PTRACE myapp:latest &amp;\nstrace -p $(docker inspect -f '{{.State.Pid}}' CONTAINER_ID) \\\n  -e trace=syscalls -c 2>&amp;1 | head -30\n\n# Ergebnis: nur erlaubte Syscalls in finales Profil aufnehmen\n# und defaultAction auf SCMP_ACT_ERRNO setzen<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr Node.js-Anwendungen sind die kritischen Syscalls: <code>read<\/code>, <code>write<\/code>, <code>open<\/code>, <code>close<\/code>, <code>socket<\/code>, <code>connect<\/code>, <code>bind<\/code>, <code>listen<\/code>, <code>accept<\/code>, <code>epoll_*<\/code>, <code>futex<\/code>, <code>clone<\/code>, <code>getrandom<\/code> und Datei-I\/O. Gef\u00e4hrliche Calls wie <code>mount<\/code>, <code>pivot_root<\/code>, <code>keyctl<\/code> und <code>ptrace<\/code> m\u00fcssen gesperrt bleiben. Das Erstellen eines pr\u00e4zisen Seccomp-Profils erfordert einmalig etwas Aufwand, bietet aber langfristig erheblichen Schutz gegen Kernel-Exploits.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"haeufige-fehler-und-sicherheitsfallen\">H\u00e4ufige Fehler und Sicherheitsfallen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 1: <code>--privileged<\/code>-Flag in der Produktion.<\/strong> Das <code>--privileged<\/code>-Flag gibt dem Container nahezu alle Kernel-Capabilities und deaktiviert AppArmor und Seccomp. Es ist das \u00c4quivalent zu &#8220;Container-Sicherheit vollst\u00e4ndig deaktiviert&#8221;. Selbst wenn es f\u00fcr lokale Tests bequem ist, geh\u00f6rt es niemals in Produktions-Deployments. Pr\u00fcfen Sie alle laufenden Container: <code>docker ps -q | xargs docker inspect --format='{{.Name}}: {{.HostConfig.Privileged}}'<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 2: Secrets als Umgebungsvariablen.<\/strong> Umgebungsvariablen werden in <code>docker inspect<\/code>, in Logdateien und in CI\/CD-Ausgaben sichtbar. CVE-2025-3911 zeigte, dass selbst Docker-Desktop-interne Logdateien Umgebungsvariablen enth\u00fcllten. Verwenden Sie stattdessen Docker Secrets, externe Secrets-Manager oder lesen Sie Geheimnisse aus Dateien, die per Volume gemountet sind.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 3: <code>ADD<\/code> statt <code>COPY<\/code> im Dockerfile.<\/strong> <code>ADD<\/code> unterst\u00fctzt Remote-URLs und automatisches Entpacken von Tar-Archiven. Das macht es zu einem potenziellen Angriffspunkt, wenn URLs aus nicht vertrauensw\u00fcrdigen Quellen stammen oder Tar-Bombs verarbeitet werden. Verwenden Sie immer <code>COPY<\/code> f\u00fcr lokale Dateien. F\u00fcr Remote-Ressourcen nutzen Sie <code>curl<\/code> mit expliziter SHA256-Pr\u00fcfsummenvalidierung.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 4: <code>latest<\/code>-Tag f\u00fcr Basis-Images.<\/strong> Das <code>latest<\/code>-Tag ist undeterministisch. Heute funktioniert der Build, morgen \u00e4ndert sich das Basis-Image und bricht Ihre Anwendung oder f\u00fchrt unbekannte Vulnerabilities ein. Pinnen Sie auf einen spezifischen SHA256-Digest: <code>FROM node:22-alpine@sha256:abc123...<\/code>. Holen Sie den Digest mit <code>docker pull node:22-alpine &amp;&amp; docker inspect node:22-alpine | grep '\"Id\"'<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 5: Fehlende oder unvollst\u00e4ndige <code>.dockerignore<\/code>.<\/strong> Ohne <code>.dockerignore<\/code> landet der gesamte Projektordner im Build-Kontext, einschlie\u00dflich <code>.git<\/code>, <code>.env<\/code>, <code>node_modules<\/code> und privaten Schl\u00fcsseln. Mindest-Inhalt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code\"># .dockerignore - minimale Konfiguration\n.git\n.gitignore\n.env\n.env.*\nnode_modules\nnpm-debug.log*\n*.pem\n*.key\nsecrets\/\n.DS_Store\ncoverage\/\n*.test.js\nREADME.md\ndocker-compose*.yml\nDockerfile*<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 6: Docker-Socket in Container mounten.<\/strong> Das Mounten von <code>\/var\/run\/docker.sock<\/code> gibt dem Container vollst\u00e4ndige Kontrolle \u00fcber den Docker-Daemon, gleichbedeutend mit Root-Zugriff auf den Host. CI\/CD-Tools wie Jenkins oder GitLab Runner ben\u00f6tigen diesen Zugriff manchmal, aber er sollte auf dedizierte Build-Container beschr\u00e4nkt und durch strikte Firewall-Regeln abgesichert werden. Alternativ: rootless Docker oder externe Build-Dienste.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 7: Kein automatisches Scanning und keine Updates.<\/strong> CVE-2024-21626 war in 60 % der gescannten Cloud-Umgebungen noch aktiv, weil kein automatischer Scanning-Prozess existierte. Richten Sie w\u00f6chentliche Rebuilds und t\u00e4gliche Trivy-Scans aller Produktions-Images ein. Nutzen Sie Docker Scout oder Renovate Bot f\u00fcr automatische Update-Benachrichtigungen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"troubleshooting-8-haeufige-probleme-und-loesungen\">Troubleshooting: 8 h\u00e4ufige Probleme und L\u00f6sungen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem 1: Container startet nicht nach Aktivierung von <code>--read-only<\/code>.<\/strong><br>Symptom: <code>Error: EROFS: read-only file system<\/code> oder \u00e4hnliche Fehler beim Start.<br>Ursache: Anwendung schreibt beim Start in ein Verzeichnis, das nun schreibgesch\u00fctzt ist.<br>L\u00f6sung: Starten Sie den Container mit <code>strace<\/code> oder nutzen Sie <code>docker run --read-only --tmpfs \/tmp:exec myapp bash -c \"node src\/server.js 2>&amp;1 | grep -E 'EROFS|permission'\"<\/code>, um das betroffene Verzeichnis zu identifizieren. Mounten Sie es dann als <code>tmpfs<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem 2: Permission denied beim Start als Nicht-Root-Benutzer.<\/strong><br>Symptom: <code>Error: EACCES: permission denied, open '\/app\/config.json'<\/code><br>Ursache: Dateien im Image geh\u00f6ren Root, aber der Container l\u00e4uft als UID 1001.<br>L\u00f6sung: F\u00fcgen Sie im Dockerfile <code>COPY --chown=1001:1001 src\/ .\/src\/<\/code> oder <code>RUN chown -R 1001:1001 \/app<\/code> hinzu. Pr\u00fcfen Sie Berechtigungen mit <code>docker run --entrypoint ls myapp:latest -la \/app<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem 3: Netzwerkverbindungen zwischen Containern schlagen fehl.<\/strong><br>Symptom: <code>Error: connect ECONNREFUSED postgres:5432<\/code><br>Ursache: Container sind in unterschiedlichen Docker-Netzwerken oder <code>icc: false<\/code> blockiert Verbindungen.<br>L\u00f6sung: Stellen Sie sicher, dass beide Container im selben Docker Compose Netzwerk definiert sind. Testen Sie mit <code>docker exec api_container ping postgres<\/code> und <code>docker network inspect NETWORK_NAME<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem 4: Trivy findet keine Vulnerabilit\u00e4t, obwohl welche bekannt sind.<\/strong><br>Symptom: Trivy meldet 0 Vulnerabilties trotz bekanntem CVE.<br>Ursache: Veraltete Vulnerability-Datenbank oder fehlerhafte Package-Detektion.<br>L\u00f6sung: <code>trivy image --download-db-only &amp;&amp; trivy image myapp:latest<\/code>. Trivy aktualisiert die Datenbank automatisch, wenn eine Internetverbindung besteht. F\u00fcr Offline-Umgebungen: <code>trivy image --skip-db-update --offline-scan myapp:latest<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem 5: Docker-Daemon startet nicht nach \u00c4nderung von daemon.json.<\/strong><br>Symptom: <code>systemctl status docker<\/code> zeigt JSON-Parse-Fehler.<br>Ursache: Ung\u00fcltige JSON-Syntax in <code>\/etc\/docker\/daemon.json<\/code>.<br>L\u00f6sung: <code>python3 -m json.tool \/etc\/docker\/daemon.json<\/code> validiert die Datei und zeigt Syntaxfehler. Typische Fehler: fehlende Kommas, nicht geschlossene Klammern oder trailing commas nach dem letzten Feld.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem 6: <code>userns-remap<\/code> bricht Volume-Berechtigungen.<\/strong><br>Symptom: Named Volumes sind nach Aktivierung von <code>userns-remap: default<\/code> nicht mehr beschreibbar.<br>Ursache: User Namespace Remapping \u00e4ndert die effektiven UID-Mappings f\u00fcr Volume-Dateien.<br>L\u00f6sung: Bestehende Volumes m\u00fcssen nach Aktivierung von <code>userns-remap<\/code> neu erstellt werden. Entfernen Sie das alte Volume mit <code>docker volume rm VOLUME_NAME<\/code> und lassen Sie Docker Compose es neu erstellen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem 7: Cosign-Verifikation schl\u00e4gt fehl.<\/strong><br>Symptom: <code>Error: no signatures found for image<\/code><br>Ursache: Das Image wurde nach dem Signing neu getaggt oder in eine andere Registry verschoben. Signaturen sind an den Image-Digest gebunden, nicht an Tags.<br>L\u00f6sung: Signieren Sie immer nach dem Push in die Zielregistry. Verwenden Sie den Image-Digest direkt: <code>cosign sign --key cosign.key registry.example.com\/myapp@sha256:ABC123<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem 8: Seccomp-Profil verhindert legitime Syscalls.<\/strong><br>Symptom: Container startet, st\u00fcrzt aber nach kurzer Zeit mit <code>Operation not permitted<\/code> ab.<br>Ursache: Die Anwendung ruft einen Syscall auf, der nicht im Seccomp-Profil erlaubt ist.<br>L\u00f6sung: Setzen Sie das Profil vor\u00fcbergehend auf <code>SCMP_ACT_LOG<\/code> statt <code>SCMP_ACT_ERRNO<\/code> und pr\u00fcfen Sie <code>dmesg | grep seccomp<\/code> oder <code>auditd<\/code>-Logs, um die fehlenden Syscalls zu identifizieren und ins Profil aufzunehmen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"komplettes-sicherheitsprojekt-gehaertete-node-js-api\">Komplettes Sicherheitsprojekt: Geh\u00e4rtete Node.js-API<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Das folgende vollst\u00e4ndige Projekt kombiniert alle 12 Schritte in einem produktionsreifen Setup. Erstellen Sie die Verzeichnisstruktur:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code\">secure-api\/\n\u251c\u2500\u2500 src\/\n\u2502   \u2514\u2500\u2500 server.js\n\u251c\u2500\u2500 secrets\/\n\u2502   \u2514\u2500\u2500 db_password.txt   # In .gitignore!\n\u251c\u2500\u2500 .dockerignore\n\u251c\u2500\u2500 .github\/\n\u2502   \u2514\u2500\u2500 workflows\/\n\u2502       \u2514\u2500\u2500 security.yml\n\u251c\u2500\u2500 Dockerfile\n\u251c\u2500\u2500 compose.yml\n\u2514\u2500\u2500 seccomp-node.json<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Das Dockerfile kombiniert Multi-Stage Build, Nicht-Root-Benutzer und Distroless-Basis-Image. Ersetzen Sie <code>SHA256_HASH<\/code> mit dem aktuellen Digest von <code>docker pull node:22-alpine &amp;&amp; docker inspect node:22-alpine --format='{{index .RepoDigests 0}}'<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code\"># Dockerfile - produktionsreif mit allen Sicherheitsmassnahmen\nFROM node:22-alpine AS builder\n# Fuer reproduzierbare Builds: SHA256-Digest pinnen\n# FROM node:22-alpine@sha256:HASH AS builder\n\nWORKDIR \/build\nCOPY package*.json .\/\nRUN npm ci --only=production &amp;&amp; npm cache clean --force\nCOPY src\/ .\/src\/\n\nFROM gcr.io\/distroless\/nodejs22-debian12:nonroot\nWORKDIR \/app\nCOPY --from=builder \/build\/node_modules .\/node_modules\nCOPY --from=builder \/build\/src .\/src\nCOPY --from=builder \/build\/package.json .\/\nEXPOSE 3000\nCMD [\"src\/server.js\"]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Das vollst\u00e4ndige compose.yml mit allen Sicherheitsmassnahmen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code\"># compose.yml - alle 12 Sicherheitsmassnahmen kombiniert\nservices:\n  api:\n    build: { context: ., target: production }\n    image: secure-api:latest\n    read_only: true\n    user: \"65532:65532\"\n    tmpfs:\n      - \/tmp:size=32m,mode=1777,exec=0\n    cap_drop: [ALL]\n    security_opt:\n      - no-new-privileges:true\n      - seccomp:.\/seccomp-node.json\n    networks: [frontend, backend]\n    environment:\n      NODE_ENV: production\n      DB_HOST: postgres\n    secrets: [db_password]\n    deploy:\n      resources:\n        limits: { cpus: \"0.5\", memory: 256M }\n    healthcheck:\n      test: [\"CMD\", \"node\", \"-e\",\n        \"require('http').get('http:\/\/localhost:3000\/health',\n        r=>process.exit(r.statusCode===200?0:1))\"]\n      interval: 30s\n      timeout: 5s\n      retries: 3\n    pids_limit: 100\n\n  postgres:\n    image: postgres:16-alpine\n    networks: [backend]\n    environment:\n      POSTGRES_PASSWORD_FILE: \/run\/secrets\/db_password\n    secrets: [db_password]\n    volumes: [pgdata:\/var\/lib\/postgresql\/data]\n    read_only: true\n    tmpfs: [\/tmp, \/var\/run\/postgresql]\n    deploy:\n      resources:\n        limits: { cpus: \"0.5\", memory: 512M }\n\nnetworks:\n  frontend:\n    driver: bridge\n  backend:\n    driver: bridge\n    internal: true\n\nsecrets:\n  db_password:\n    file: .\/secrets\/db_password.txt\n\nvolumes:\n  pgdata:<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"erweiterte-tipps-fuer-produktionsumgebungen\">Erweiterte Tipps f\u00fcr Produktionsumgebungen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Docker Rootless Mode:<\/strong> Seit Docker 20.10 ist der Rootless-Modus stabil. In Docker 27 ist er vollst\u00e4ndig f\u00fcr Produktionsumgebungen unterst\u00fctzt. Im Rootless-Modus l\u00e4uft der Docker-Daemon selbst als normaler Benutzer ohne Root-Rechte. Das eliminiert eine gesamte Klasse von Host-Escape-Schwachstellen. Einrichten mit: <code>dockerd-rootless-setuptool.sh install<\/code>. Haupteinschr\u00e4nkungen: kein <code>--net=host<\/code>, kein <code>--privileged<\/code>, und overlay2 ben\u00f6tigt Kernel 5.11+.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Docker Bench for Security:<\/strong> Das offizielle CIS Docker Benchmark Audit-Tool pr\u00fcft Ihren Host und Ihre Container auf Hunderte von Sicherheitskonfigurationen. F\u00fchren Sie es nach jeder gr\u00f6\u00dferen Infrastruktur\u00e4nderung aus:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Docker Bench for Security ausfuehren\ndocker run --rm -it \\\n  --net host --pid host --userns host \\\n  --cap-add audit_control \\\n  -v \/etc:\/etc:ro \\\n  -v \/var\/lib:\/var\/lib:ro \\\n  -v \/var\/run\/docker.sock:\/var\/run\/docker.sock:ro \\\n  --label docker_bench_security \\\n  docker\/docker-bench-security<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Runtime-Security mit Falco:<\/strong> Falco ist ein Open-Source-Tool f\u00fcr die Laufzeit-Sicherheits\u00fcberwachung von Containern. Es analysiert Kernel-Syscalls in Echtzeit und l\u00f6st Alarme aus, wenn verd\u00e4chtige Aktivit\u00e4ten erkannt werden: ein Shell-Prozess in einem Container, der normalerweise keine Shell enth\u00e4lt, ungew\u00f6hnliche Netzwerkverbindungen oder verd\u00e4chtige Dateioperationen. Falco ist de facto Standard f\u00fcr Container-Runtime-Security in Kubernetes-Umgebungen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Image-Digest-Pinning automatisieren:<\/strong> Renovate Bot erstellt automatisch Pull Requests, wenn neue gepinnte Image-Versionen verf\u00fcgbar sind. Konfigurieren Sie Renovate, Dockerfile-Image-Digests w\u00f6chentlich zu aktualisieren und dabei Trivy-Scans der neuen Version auszuf\u00fchren, bevor der PR genehmigt wird. Das kombiniert Sicherheit mit Aktualit\u00e4t, ohne manuelle Eingriffe.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Kubernetes-\u00dcbergang:<\/strong> Beim Wechsel von Docker Compose zu Kubernetes \u00fcbernehmen Kubernetes-native Konzepte die Sicherheitsma\u00dfnahmen: <code>SecurityContext<\/code> f\u00fcr Capabilities und Nicht-Root-User, <code>NetworkPolicy<\/code> f\u00fcr Netzwerksegmentierung, <code>PodSecurityStandards<\/code> f\u00fcr Richtlinienerzwingung und Kubernetes Secrets f\u00fcr Geheimnisse. Lesen Sie dazu auch unseren Artikel \u00fcber <a href=\"\/de\/nginx-reverse-proxy-https-einrichten\/\">Nginx Reverse Proxy mit HTTPS<\/a> f\u00fcr die TLS-Terminierung vor Kubernetes-Deployments.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"verwandte-artikel\">Verwandte Artikel<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/de\/openssl-tutorial-zertifikate\/\">OpenSSL-Tutorial: Schl\u00fcssel und Zertifikate in 12 Schritten<\/a><\/li>\n<li><a href=\"\/de\/nginx-reverse-proxy-https-einrichten\/\">Nginx Reverse Proxy: HTTPS in 12 Schritten einrichten<\/a><\/li>\n<li><a href=\"\/de\/hashicorp-vault-tutorial\/\">HashiCorp Vault 2.0: Sichere Secrets in 12 Schritten<\/a><\/li>\n<li><a href=\"\/de\/http-security-headers-nodejs-helmet\/\">HTTP Security Headers in Node.js: 12 Schritte mit Helmet.js<\/a><\/li>\n<li><a href=\"\/de\/nmap-tutorial-netzwerk-scanner\/\">Nmap-Tutorial: Docker-Netzwerke und exponierte Ports pr\u00fcfen<\/a><\/li>\n<li><a href=\"\/de\/security\/\">Security-Hub: Alle Sicherheits-Tutorials im \u00dcberblick<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"faq-docker-container-absichern\">FAQ: Docker Container absichern<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Muss ich alle 12 Schritte f\u00fcr eine lokale Entwicklungsumgebung umsetzen?<\/strong><br>Nein. F\u00fcr lokale Entwicklung sind Schritt 1 (aktuelle Version), Schritt 2 (minimale Basis-Images) und Schritt 8 (keine Secrets als Umgebungsvariablen) am wichtigsten. Die restriktiven Ma\u00dfnahmen wie Read-Only-Dateisystem und Seccomp-Profile k\u00f6nnen in der Entwicklung den Workflow verlangsamen. Trennen Sie klar zwischen Entwicklungs- und Produktionskonfiguration mit separaten Compose-Dateien.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Wie \u00fcberpr\u00fcfe ich, ob mein Container als Root l\u00e4uft?<\/strong><br>F\u00fchren Sie <code>docker exec CONTAINER_ID whoami<\/code> aus oder pr\u00fcfen Sie beim Start mit <code>docker inspect CONTAINER_ID | grep -A2 '\"User\"'<\/code>. Wenn das Ergebnis leer ist, bedeutet das Root. F\u00fcr alle laufenden Container: <code>docker ps -q | xargs docker inspect --format='{{.Name}}: User={{.Config.User}}'<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Was ist CVE-2024-21626 und bin ich betroffen?<\/strong><br>CVE-2024-21626 ist eine runc-Schwachstelle (CVSS 8.6), bei der ein manipuliertes Docker-Image durch einen &#8220;Leaky File Descriptor&#8221; im WORKDIR-Handling aus dem Container ausbrechen und Root-Rechte auf dem Host erlangen kann. Pr\u00fcfen Sie Ihre runc-Version mit <code>runc --version<\/code>. Sie ben\u00f6tigen runc 1.1.12 oder neuer. Der Fix ist in Docker Engine 25.0.4+ und Docker Desktop 4.28.0+ enthalten.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ist Docker Desktop oder Docker Engine empfehlenswerter f\u00fcr Produktion?<\/strong><br>F\u00fcr Produktionsserver auf Linux empfehlen sich Docker Engine (Community Edition) direkt oder containerd ohne Docker-Overhead. Docker Desktop ist f\u00fcr Entwicklungs-Workstations konzipiert und enthielt 2025\/2026 mehrere kritische CVEs. Halten Sie Docker Desktop auf mindestens Version 4.67.0 aktuell. F\u00fcr macOS und Windows-Entwicklung ist Docker Desktop akzeptabel mit aktivierten automatischen Updates.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Wie oft sollte ich Docker-Images neu bauen?<\/strong><br>F\u00fcr Produktionsumgebungen empfiehlt sich ein w\u00f6chentlicher automatischer Rebuild, um Basis-Image-Sicherheitsupdates aufzunehmen. Richten Sie eine CI\/CD-Pipeline ein, die jede Woche automatisch rebuildet, Trivy scannt und bei kritischen Vulnerabilties einen Alert ausl\u00f6st. Tools wie Renovate Bot oder Dependabot helfen dabei, Image-Digests aktuell zu halten.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Was tun bei einem Container-Sicherheitsvorfall?<\/strong><br>Isolieren Sie den Container sofort vom Netzwerk: <code>docker network disconnect NETWORK CONTAINER_ID<\/code>. Erstellen Sie einen forensischen Snapshot: <code>docker export CONTAINER_ID > evidence-$(date +%Y%m%d).tar<\/code>. Stoppen Sie den Container ohne ihn zu l\u00f6schen (<code>docker stop<\/code>, nicht <code>docker rm<\/code>). Benachrichtigen Sie Ihr Security-Team und folgen Sie Ihrem Incident-Response-Plan. Untersuchen Sie Docker-Logs mit <code>docker logs CONTAINER_ID<\/code> und Host-Audit-Logs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Was ist der Unterschied zwischen Docker Secrets und Kubernetes Secrets?<\/strong><br>Docker Secrets (im Swarm-Modus) werden als tempor\u00e4re In-Memory-Dateien unter <code>\/run\/secrets\/<\/code> eingeh\u00e4ngt und sind nicht in Images oder Logs sichtbar. Kubernetes Secrets werden standardm\u00e4\u00dfig base64-kodiert im etcd gespeichert, was keine echte Verschl\u00fcsselung darstellt. F\u00fcr Kubernetes empfehlen sich externe Secrets-Manager wie HashiCorp Vault oder AWS Secrets Manager mit dem Kubernetes External Secrets Operator. Aktivieren Sie in Kubernetes mindestens etcd-Verschl\u00fcsselung at rest.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Kann ich Docker Security Benchmarks automatisch pr\u00fcfen?<\/strong><br>Ja. Docker Bench for Security f\u00fchrt automatisch CIS Docker Benchmark-Checks durch und gibt Bestanden\/Fehlgeschlagen-Berichte aus. Das Tool kann als Container gestartet werden und ben\u00f6tigt ca. 30 Sekunden pro Host. Integrieren Sie es in Ihren CI\/CD-Prozess oder nutzen Sie kommerzielle Tools wie Aqua Security, Prisma Cloud oder Snyk Container f\u00fcr kontinuierliches Compliance-Monitoring.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Quellen:<\/strong> <a href=\"https:\/\/docs.docker.com\/engine\/security\/\" rel=\"nofollow noopener\" target=\"_blank\">Docker Engine Security Documentation<\/a> | <a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Docker_Security_Cheat_Sheet.html\" rel=\"nofollow noopener\" target=\"_blank\">OWASP Docker Security Cheat Sheet<\/a> | <a href=\"https:\/\/www.cve.org\/CVERecord?id=CVE-2024-21626\" rel=\"nofollow noopener\" target=\"_blank\">CVE-2024-21626 (runc Container Escape)<\/a> | <a href=\"https:\/\/github.com\/aquasecurity\/trivy\" rel=\"nofollow noopener\" target=\"_blank\">Trivy Vulnerability Scanner<\/a> | <a href=\"https:\/\/github.com\/sigstore\/cosign\" rel=\"nofollow noopener\" target=\"_blank\">Cosign \/ Sigstore Image Signing<\/a> | <a href=\"https:\/\/github.com\/opencontainers\/runc\/security\/advisories\/GHSA-xr7r-f8xq-vfvv\" rel=\"nofollow noopener\" target=\"_blank\">runc Security Advisory (CVE-2024-21626)<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Im Juni 2026 betreffen 60 % aller Cloud-Umgebungen noch immer die runc-Schwachstelle CVE-2024-21626 (CVSS 8.6), die es Angreifern erlaubt, aus einem Container auszubrechen und Root-Rechte auf dem Host zu erlangen.\u2026<\/p>\n","protected":false},"author":9,"featured_media":256,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-255","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-security"],"_links":{"self":[{"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/posts\/255","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/users\/9"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/comments?post=255"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/posts\/255\/revisions"}],"predecessor-version":[{"id":257,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/posts\/255\/revisions\/257"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/media\/256"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/media?parent=255"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/categories?post=255"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/tags?post=255"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}