Docker loest "laeuft auf meinem Rechner", aber in Produktion brauchen Sie einen kontrollierten Eingangspunkt: Nginx vor den Containern, mit an einer Stelle terminiertem TLS, isolierten Netzwerken und geschlossenen Ports. Ein praktischer, schrittweiser Leitfaden mit docker-compose.
Warum Docker + Nginx
- Isolation und Reproduzierbarkeit — jede App mit eigenen Abhaengigkeiten, identisch auf Dev und Prod.
- Ein einziger Eingangspunkt — Nginx terminiert TLS einmal und routet nach Domain/Pfad zu den Containern.
- Kleine Angriffsflaeche — nur 80/443 exponiert; Datenbanken bleiben im internen Docker-Netz, ohne oeffentlichen Port.
- Einfacher Betrieb — Restart-Policy, Healthcheck, Updates per Image-Re-Pull.
Architektur
Das Internet erreicht nur Nginx (80/443). Nginx leitet ueber ein internes Docker-Bridge-Netz an die App-Container weiter. Die Datenbank liegt im selben Netz, veroeffentlicht aber KEINEN Port nach aussen.
Schritt 1 — Docker installieren
- Docker Engine + Compose-Plugin installieren (Debian/Ubuntu): Pakete
docker-ce docker-ce-cli containerd.io docker-compose-pluginaus dem offiziellen Docker-Repo. - Pruefen:
docker run --rm hello-worldunddocker compose version. - Dediziertes Netz anlegen:
docker network create web(user-defined network = DNS-Aufloesung zwischen Containern per Name).
Schritt 2 — docker-compose fuer die App
Beispiel docker-compose.yml mit App + Postgres, Healthcheck und Restart-Policy:
- services.app:
image,restart: unless-stopped,expose: ["8080"](NICHTports— nicht veroeffentlichen),networks: [web],healthcheckmittest: curl -f http://localhost:8080/health. - services.db:
image: postgres:16,volumes: [dbdata:/var/lib/postgresql/data],networks: [web], KEINEports— nur intern erreichbar. - Secrets (Passwoerter) per
environmentaus einer.env-Datei (nicht ins Image gebacken) odersecrets. - Starten:
docker compose up -d; pruefen:docker compose ps,docker compose logs -f app.
Schritt 3 — Nginx Reverse Proxy
Nginx (auf dem Host oder als Container im selben web-Netz) proxyt per Servicename zum Container:
upstreamoderproxy_pass http://app:8080;(der Name "app" wird ueber die Docker-Netz-DNS aufgeloest).- Korrekte Header:
proxy_set_header Host $host;X-Real-IP $remote_addr;X-Forwarded-For $proxy_add_x_forwarded_for;X-Forwarded-Proto $scheme; - WebSocket (falls noetig):
proxy_http_version 1.1;+Upgrade/Connection. - Multi-App-Routing: per
server_name(verschiedene Domains) oderlocation /app2/zuproxy_pass http://app2:3000;.
Schritt 4 — TLS mit Let's Encrypt
- Redirect
80 -> 443fuer allen menschlichen Verkehr; auf 80 nur/.well-known/acme-challenge/offen lassen. - Zertifikat:
certbot --webroot(oder eincertbot-Container in compose) mit automatischer Erneuerung. - Im 443-Block:
ssl_certificate/ssl_certificate_key,ssl_protocols TLSv1.2 TLSv1.3.
Schritt 5 — Hardening
- Keine unnoetigen Ports: nur Nginx veroeffentlicht 80/443. DB und App nutzen
expose, nichtports. - Non-root im Container:
user:in compose oderUSERim Dockerfile; nicht als root laufen. - Read-only + no-new-privileges:
read_only: true,security_opt: [no-new-privileges:true],cap_drop: [ALL]. - Ressourcenlimits:
deploy.resources.limits(CPU/RAM), damit ein Container den Host nicht lahmlegt. - Security-Header an Nginx: HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, CSP.
- Kleine Images + Updates:
-slim/alpine-Basen, mitdocker scout/ Trivy scannen, regelmaessig re-pullen.
Automatisches Let's Encrypt
Das Zertifikat sollte nicht von Hand erneuert werden. Certbot installiert einen systemd-Timer (certbot.timer), der certbot renew zweimal taeglich ausfuehrt und nur erneuert, was in weniger als 30 Tagen ablaeuft.
- Nginx nach Erneuerung neu laden:
certbot renew --deploy-hook "nginx -s reload". - Test ohne echte Ausstellung:
certbot renew --dry-run. - In Docker: entweder ein
certbot-Container mit einem gemeinsamen Volume mit nginx (Erneuerungsschleife) oder das Musternginx-proxy+acme-companion, das Zertifikate pro Container automatisch ausstellt und erneuert, basierend auf den VariablenVIRTUAL_HOST/LETSENCRYPT_HOST.
GeoIP + nftables-Firewall
Laendersperren koennen auf zwei Ebenen erfolgen. Auf der nginx-Ebene (GeoIP2-Modul + map $geoip2_country_code) antworten Sie mit 403. Effizienter ist es, Pakete in nftables zu verwerfen, bevor sie nginx erreichen — das blockiert auch Scans, nicht nur HTTP-Anfragen.
- Laender-Praefixe in ein nftables-Set (Intervall) legen:
set blocked_cc { type ipv4_addr; flags interval; }. - Regel in der Input-Chain:
ip saddr @blocked_cc drop(oder umgekehrt Whitelist: nur erlaubte Laender akzeptieren). - Praefix-Quelle: Listen pro Land (z. B. ipdeny.com) oder eine GeoIP-Datenbank; ein Cron-Skript laedt das Set taeglich neu.
- Persistenz in
/etc/nftables.conf; Pruefung mitnft list ruleset. - Blacklist vs Whitelist: Blacklist (wenige Hochrisiko-Laender sperren) ist sicher; Whitelist (nur Ihr Land + wenige) ist aggressiv — sie kann legitime Bots/CDNs blockieren, daher zuerst testen.
CrowdSec auf Nginx-Logs
CrowdSec liest die nginx-Logs (access.log / error.log), wendet Szenarien an und blockiert feindliche IPs — mit kollektiver Intelligenz (IPs, die im gesamten CrowdSec-Netz gemeldet werden).
- Acquisition: in
/etc/crowdsec/acquis.yamldie nginx-Log-Pfade hinzufuegen (Typnginx). - Collections:
cscli collections install crowdsecurity/nginx crowdsecurity/base-http-scenarios crowdsecurity/http-cve— erkennt Brute-Force, Pfad-Scans, Flood, boesartige User-Agents, Exploit-Versuche. - Bouncer (setzt die Sperre durch):
crowdsec-firewall-bouncerlegt IPs in ein nftables-Set (crowdsec-blacklists) → drop; odercrowdsec-nginx-bouncer, der auf nginx-Ebene mit 403 antwortet. - Betrieb:
cscli decisions list,cscli metrics,cscli alerts list; Registrierung in der CrowdSec-Konsole fuer Dashboard + Community-Blocklists (CTI). - vs fail2ban: CrowdSec hat reichere Szenarien und kollaborative Reputation; fail2ban bleibt gut fuer einfache, lokale Regeln.
Logging und Betrieb
- Logs:
docker compose logs; fuer Produktion derjson-file-Treiber mitmax-size/max-fileoder Versand an einen Log-Stack (Loki). - App-Update:
docker compose pull && docker compose up -d(erstellt nur Geaendertes neu). - Backup: die Volumes (
dbdata) —docker run --rm -v dbdata:/data ...oder ein geplanterpg_dump. - Gesundheit:
docker compose pszeigt den Healthcheck-Status; die Restart-Policy holt einen abgestuerzten Container zurueck.
Fazit
Das Nginx-+-Docker-Muster bietet reproduzierbare Deployments, einen einzigen TLS-Eingangspunkt und eine minimale Angriffsflaeche — vom einzelnen VPS bis zu mehreren Knoten. Wir entwerfen, sichern und ueberwachen es fuer Sie.