Wann Kubernetes die falsche Antwort ist
Kubernetes löst Probleme, die entstehen, wenn viele Teams viele Dienste unabhängig voneinander ausrollen. Wenn du zwei Container und eine Datenbank betreibst, hast du keins dieser Probleme, aber alle Kosten: Manifeste, Ingress-Controller, Zertifikatsverwaltung, Cluster-Updates und eine Fehlersuche, die drei Abstraktionsebenen tief geht.
Für ein Team unserer Größe ist das ein schlechter Tausch. Wir betreiben Anwendungen mit Docker Compose, Nginx davor und einem Deploy-Skript, und das trägt bis in Bereiche, die viele überraschen. Die Streaming-Plattform, über die wir beim Skalieren von Socket.IO schreiben, läuft genau so.
Das ist keine Ideologie. Es ist die Beobachtung, dass die Zeit, die in Cluster-Pflege fließt, bei kleinen Teams direkt von der Zeit abgeht, die in das Produkt fließt.
Der Stack in einer Datei
Eine Compose-Datei beschreibt den ganzen Betrieb: Anwendung, Datenhaltung, Proxy. Drei Dinge machen sie produktionstauglich, die in Beispielen aus dem Netz oft fehlen. Ein Healthcheck, damit Abhängigkeiten in der richtigen Reihenfolge starten. Eine Neustart-Regel, damit ein abgestürzter Container von allein wiederkommt. Und eine Begrenzung der Logs, sonst füllt dir irgendwann die Festplatte den Server.
services:
app:
image: registry.example.com/app:$APP_VERSION
restart: unless-stopped
env_file: .env.production
depends_on:
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "node", "healthcheck.js"]
interval: 10s
timeout: 3s
retries: 3
start_period: 20s
logging:
driver: json-file
options: { max-size: "10m", max-file: "3" }
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --appendonly yes
volumes: [redis-data:/data]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
nginx:
image: nginx:alpine
restart: unless-stopped
ports: ["80:80", "443:443"]
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on: [app]
volumes:
redis-data:Das Deployment als Skript
Ein Deployment ist am Ende: neues Image ziehen, Container tauschen, prüfen ob es lebt, und wenn nicht, zurück auf die alte Version. Das sind zwanzig Zeilen Bash, die in der CI laufen und über SSH auf den Server greifen.
Der Teil, der oft fehlt, ist die Prüfung danach. Ohne sie erfährst du von einem kaputten Deployment durch einen Kunden. Mit ihr rollt das Skript selbst zurück, bevor jemand etwas merkt.
#!/usr/bin/env bash
set -euo pipefail
VERSION="$1"
REMOTE="deploy@app.example.com"
DIR="/srv/app"
ssh "$REMOTE" bash -s <<EOF
set -euo pipefail
cd "$DIR"
# aktuelle version merken, damit ein rollback moeglich bleibt
PREVIOUS=\$(grep APP_VERSION .env.production | cut -d= -f2)
echo "APP_VERSION=$VERSION" > .env.production.new
grep -v APP_VERSION .env.production >> .env.production.new
mv .env.production.new .env.production
docker compose pull app
docker compose up -d --no-deps app
# 60 sekunden auf gesundheit warten, sonst zurueck
for i in \$(seq 1 12); do
if curl -fsS localhost:3000/health > /dev/null; then
echo "deployment ok"
exit 0
fi
sleep 5
done
echo "health check fehlgeschlagen, rollback auf \$PREVIOUS"
sed -i "s/APP_VERSION=.*/APP_VERSION=\$PREVIOUS/" .env.production
docker compose up -d --no-deps app
exit 1
EOFOhne Ausfall ausrollen, ohne Orchestrator
Der Trick ist derselbe wie überall: kurz zwei Versionen parallel laufen lassen und den Proxy umschalten. Du startest die neue Version unter einem zweiten Dienstnamen, wartest auf ihren Healthcheck, tauschst im Nginx den Upstream und lädst die Konfiguration mit einem Reload nach. Bestehende Verbindungen laufen dabei aus, statt getrennt zu werden.
Für Anwendungen mit dauerhaften Verbindungen ist genau das wichtig. Ein `nginx -s reload` beendet keine offene WebSocket-Verbindung, ein Neustart des Containers schon. Wer das verwechselt, wirft bei jedem Deployment alle Nutzer aus der Sitzung.
Ehrlicherweise: Wenn dir eine Ausfallzeit von zwei Sekunden nichts ausmacht, lass das weg. Die meisten internen Werkzeuge und viele Websites brauchen es schlicht nicht, und jede Zeile, die du nicht baust, kann auch nicht kaputtgehen.
Wann du doch wechseln solltest
Es gibt klare Punkte, an denen der Aufwand kippt. Wenn mehrere Teams unabhängig voneinander ausrollen wollen, wird die Koordination über eine gemeinsame Compose-Datei zur Bremse. Wenn deine Last so stark schwankt, dass du automatisch skalieren musst, statt Kapazität vorzuhalten. Oder wenn ihr aus Compliance-Gründen Dinge wie feingranulare Rechte und Audit-Trails auf Infrastrukturebene braucht.
Was kein Grund ist: dass es alle machen, oder dass es im Lebenslauf gut aussieht. Beides sind reale Motive, und beide kosten dein Projekt Geld.
Häufige Fragen
Ist Docker Compose überhaupt für Produktion gedacht?
Ja. Der Ruf als reines Entwicklungswerkzeug stammt aus einer Zeit, in der es das war. Mit Healthchecks, Neustart-Regeln und einem sauberen Deploy-Weg betreibt man damit ernsthafte Anwendungen, solange alles auf einer Maschine läuft.
Was ist mit Docker Swarm?
Swarm sitzt genau dazwischen und wird kaum noch weiterentwickelt. Wenn Compose auf einer Maschine nicht mehr reicht, würden wir eher direkt zu einem verwalteten Dienst greifen als zu Swarm.
Wie kommen die Secrets auf den Server?
Nicht ins Repository. Wir legen sie als Datei auf dem Server ab, mit strengen Dateirechten, und ziehen sie über env_file. Was aus der CI kommt, liegt dort in den verschlüsselten Variablen. Ein Passwort-Manager bleibt die Quelle der Wahrheit.
Ab wie vielen Nutzern reicht eine Maschine nicht mehr?
Die Nutzerzahl ist selten die Grenze. Meistens ist es entweder eine einzelne teure Operation oder der Wunsch, keine Ausfallzeit mehr zu haben. Beides merkt man rechtzeitig, wenn man misst, statt zu schätzen.
Dieser Beitrag gibt den Stand von Februar 2026 wieder und ersetzt keine Rechtsberatung. Für verbindliche Auskünfte zu deinem konkreten Fall wende dich an eine Anwältin oder einen Anwalt.