November 2024, ein Berliner Fintech-Startup mit acht Entwicklern. Ihre SaaS-Plattform läuft auf einem einzelnen Hetzner-VPS mit 32 GB RAM. Dann passiert, was sich die Gründer seit Monaten wünschen: Ein Artikel auf Hacker News bringt 40.000 Besucher in vier Stunden. Der Server geht in die Knie. Kein Problem, denkt der CTO, wir stellen einen zweiten Server daneben und verteilen die Last.
Nur: Benutzer-Sessions liegen als Dateien im lokalen Dateisystem. Hochgeladene Dokumente sind unter /var/uploads gespeichert. Cronjobs laufen auf genau diesem einen Server. Ein zweiter Server würde nichts bringen, weil die Anwendung nicht dafür gebaut ist, auf mehreren Maschinen zu laufen. Das Problem ist nicht die Hardware. Das Problem ist die Architektur.
Dieses Szenario wiederholt sich in Startups und mittelständischen Unternehmen im gesamten DACH-Raum. Ihre Anwendung funktioniert hervorragend auf einem einzelnen Server, aber sobald ein zweiter dazukommen soll, bricht alles zusammen. Container lösen dieses Problem, allerdings nicht automatisch. Sie erzwingen eine Architektur, die Skalierung überhaupt erst möglich macht. Dieser erste Teil der Serie erklärt, warum Container heute der Standard für Webapplikationen sind, was zustandsloses Design bedeutet und wie Docker Compose die lokale Entwicklung produktionsnah abbildet.
Container vs. virtuelle Maschinen: Mehr als nur leichtgewichtig
Bevor Container zum Mainstream wurden, liefen die meisten Webanwendungen auf virtuellen Maschinen. Eine VM simuliert einen kompletten Computer: eigenes Betriebssystem, eigener Kernel, eigene Treiber. Das bedeutet Isolation, kostet aber Ressourcen. Jede VM startet ein vollständiges Linux oder Windows, auch wenn die eigentliche Anwendung nur 200 MB RAM braucht. Der Overhead liegt typischerweise bei 500 MB bis 2 GB pro VM, allein für das Gastbetriebssystem.
Container funktionieren anders, weil sie sich den Kernel des Host-Betriebssystems teilen und nur die Anwendung selbst isolieren: Dateisystem, Netzwerk, Prozesse. Ein Container, der eine Python-Webanwendung enthält, braucht vielleicht 80 MB statt 1,5 GB bei einer VM mit dem gleichen Inhalt, während Startzeiten von 30 bis 120 Sekunden auf wenige Sekunden sinken. Das klingt nach einem reinen Performance-Vorteil, aber der eigentliche Gewinn liegt woanders.
Container machen Deployments reproduzierbar. Ein Docker-Image enthält alles, was eine Anwendung braucht: Laufzeitumgebung, Bibliotheken, Konfiguration. Was auf dem Laptop läuft, läuft auch auf dem Server. Zeiten, in denen Entwickler „Bei mir geht’s“ sagten und Admins verzweifelt Abhängigkeiten nachinstallierten, sind damit vorbei. Laut der CNCF-Umfrage von 2025 nutzen 82 Prozent der Container-Anwender Kubernetes in der Produktion, während das Cloud-Native-Umfeld auf 15,6 Millionen Entwickler weltweit angewachsen ist.
Wie viel Ressourcen spart ein Container tatsächlich?
Ein konkretes Beispiel: Eine Webanwendung besteht aus vier Komponenten. API-Server, Datenbank, Cache und ein Hintergrund-Worker für asynchrone Aufgaben. Auf virtuellen Maschinen bräuchte jede Komponente eine eigene VM. Vier VMs mit jeweils 2 GB Basis-Overhead bedeuten 8 GB RAM allein für Betriebssysteme, bevor die erste Zeile Anwendungscode läuft.
Als Container verpackt, teilen sich alle vier Komponenten einen Kernel. Der Overhead pro Container liegt bei 50 bis 200 MB. Statt 8 GB Systembasis braucht das Setup unter einem Gigabyte. Auf einem Hetzner CPX22 mit zwei vCPUs und 4 GB RAM laufen vier Container problemlos. Mit VMs bräuchte die gleiche Konfiguration mindestens 16 GB RAM. Noch gravierender ist der Unterschied bei der Startzeit. Ein neuer Container steht in ein bis drei Sekunden bereit, eine VM braucht eine halbe Minute oder länger.
Was steckt hinter den Image-Schichten?
Docker-Images bestehen aus Schichten, sogenannten Layers. Jede Anweisung in einem Dockerfile erzeugt eine neue Schicht: das Basis-Image, installierte Pakete, kopierte Quelldateien, die Startkonfiguration. Diese Schichten werden einzeln gespeichert und können zwischen Images geteilt werden. Zehn Anwendungen, die alle auf python:3.13-slim basieren, speichern die Basis-Schicht nur einmal.
Beim Build nutzt Docker einen Cache. Solange sich eine Schicht nicht ändert, wird sie nicht neu gebaut. Praktisch bedeutet das: Die Abhängigkeiten (requirements.txt, package.json) werden in einem frühen Layer installiert. Quellcode kommt in einem späten Layer. Ändert sich nur der Quellcode, müssen die Abhängigkeiten nicht erneut heruntergeladen werden. Ein gut strukturiertes Dockerfile reduziert Build-Zeiten von Minuten auf Sekunden. Schlecht strukturierte Dockerfiles invalidieren bei jeder Änderung den gesamten Cache und installieren alle Pakete neu. Entscheidend ist dabei die Reihenfolge der Anweisungen im Dockerfile.
Multi-Stage Builds gehen noch einen Schritt weiter. Sie trennen die Build-Umgebung vom finalen Image. Im ersten Stage werden Abhängigkeiten kompiliert, Tests ausgeführt, Assets gebaut. Im zweiten Stage landet nur das fertige Artefakt in einem minimalen Basis-Image. Eine Go-Anwendung, deren Build-Image 800 MB groß ist, schrumpft im Produktions-Image auf 15 MB. Weniger Dateien im Image bedeuten weniger Angriffsfläche, weshalb im Produktionscontainer weder Compiler noch Paketmanager oder Build-Werkzeuge landen sollten. Sicherheitsscanner wie Trivy oder Grype finden in Multi-Stage-Images deutlich weniger Schwachstellen, weil schlicht weniger Pakete vorhanden sind.
Ein Service, ein Container: Das Prinzip der Trennung
Entscheidend beim Arbeiten mit Containern ist eine Regel: Jeder Dienst bekommt seinen eigenen Container. Nicht alles in einen großen Container packen und hoffen, dass es funktioniert. Hinter dieser Trennung steckt ein praktischer Grund. Wenn der API-Server abstürzt, soll die Datenbank weiterlaufen. Wenn der Cache neu gestartet werden muss, soll das den Webserver nicht betreffen.
Eine typische Webanwendung besteht aus diesen Containern: Ein Reverse Proxy (Traefik oder Nginx) nimmt alle eingehenden HTTP-Anfragen entgegen und leitet sie an den richtigen Dienst weiter. Dahinter läuft der eigentliche Anwendungsserver, etwa eine Django-Anwendung in Python oder ein Express-Server in Node.js. PostgreSQL, MySQL oder MongoDB als Datenbank bekommt einen eigenen Container. Ein Cache wie Redis beschleunigt häufige Abfragen und speichert Sessions. Und ein separater Dateispeicher nimmt hochgeladene Dateien und Medien auf.
Diese Trennung ermöglicht später die Skalierung einzelner Komponenten. Wenn der API-Server an seine Grenzen stößt, starte drei weitere Instanzen. PostgreSQL läuft weiterhin auf einem einzigen Container. Ohne diese Trennung müsste bei jedem Skalierungsschritt die gesamte Anwendung samt Datenbank dupliziert werden.
Für die meisten Projekte bis 20 Entwickler reicht dabei ein Monolith in separaten Containern völlig aus. Wer mit Microservices anfängt, bevor das Team und die Codebasis dafür bereit sind, schafft mehr Probleme als er löst. Container-Trennung bedeutet nicht automatisch Microservices. Sie bedeutet: Dienste voneinander isolieren, damit sie unabhängig skaliert, aktualisiert und überwacht werden können.
Was passiert zwischen den Containern?
Container kommunizieren über virtuelle Netzwerke. Docker erstellt beim Start automatisch ein Bridge-Netzwerk, über das sich Container gegenseitig per Servicename erreichen. Statt einer IP-Adresse verbindet sich der Anwendungscontainer zur Datenbank per Name: postgres://db:5432. Wird der Datenbankcontainer neu gestartet und bekommt eine andere IP, funktioniert die Namensauflösung trotzdem. Dieses interne DNS ist einer der Gründe, warum Container-Setups zuverlässiger sind als manuell verwaltete Server.
Standardmäßig sind Container von außen nicht erreichbar. Nur Ports, die explizit veröffentlicht werden, sind zugänglich. PostgreSQL braucht keinen offenen Port nach außen, nur einen im internen Netzwerk, was die Angriffsfläche erheblich reduziert. Für komplexere Setups lassen sich mehrere Netzwerke definieren. Ein Frontend-Netzwerk verbindet Reverse Proxy und Anwendung, ein Backend-Netzwerk verbindet Anwendung und Datenbank. Der Reverse Proxy hat keinen direkten Zugriff auf die Datenbank. Dieses Prinzip der minimalen Rechtevergabe gilt auch auf Netzwerkebene.
Wohin mit den Dateien?
Zurück zum Berliner Startup aus der Einleitung. Eines der größten Probleme war der lokale Dateispeicher. Benutzer luden Dokumente hoch, die direkt auf der Festplatte des Servers landeten. Sobald ein zweiter Server dazukommt, hat dieser keinen Zugriff auf die Dateien des ersten. Nutzer bekommen je nach Server, auf den der Load Balancer sie leitet, mal ihre Dateien angezeigt und mal nicht.
Dateien gehören auf einen separaten Speicherdienst, nicht auf das lokale Dateisystem eines Containers. Der Industriestandard ist S3-kompatibler Object Storage. Amazon hat mit S3 den De-facto-Standard geschaffen, und zahlreiche Dienste sprechen das gleiche Protokoll. Für die Anwendung ändert sich wenig: Statt auf /var/uploads zu schreiben, geht der Upload per API an den Object Storage. Bibliotheken wie boto3 (Python), aws-sdk (Node.js) oder minio-go (Go) abstrahieren die Details.
Lange Zeit war MinIO die beliebteste selbst gehostete S3-Alternative. Dann kam der Rückschlag: Im Dezember 2025 stellte MinIO den Open-Source-Betrieb auf Wartungsmodus um, im Februar 2026 wurde das Repository komplett archiviert. Neue Projekte auf MinIO aufzubauen, ist damit keine Option mehr. Enterprise-Funktionen wanderten in das kostenpflichtige AIStor-Produkt mit Preisen im fünfstelligen Bereich pro Jahr. Für Startups und KMUs unbezahlbar.
Am pragmatischsten für Teams im DACH-Raum: Hetzner Object Storage für 4,99 Euro pro Terabyte im Monat, S3-kompatibel, deutsches Rechenzentrum. Cloudflare R2 bietet eine interessante Ergänzung: keine Kosten für ausgehenden Traffic, was bei medienintensiven Anwendungen den Unterschied macht. Für Teams, die volle Kontrolle brauchen, bleibt Ceph mit dem Rados Gateway. Die Einrichtung dauert Tage statt Stunden, dafür läuft alles auf den eigenen Servern. SeaweedFS und GarageHQ sind leichtgewichtige Alternativen für kleinere Setups. MinIOs Archivierung ist ein Lehrstück für jeden, der Open-Source-Software ohne Migrationsstrategie in Produktionssysteme einbaut.
Stateless by Design: Warum zustandslose Container den Unterschied machen
Zustandslos bedeutet: Ein Container speichert nichts, was bei einem Neustart verloren gehen dürfte. Keine Sessions im Dateisystem, keine hochgeladenen Dateien auf der lokalen Festplatte, keine Konfiguration in einer lokal gespeicherten Datei. Alles, was über den Lebenszyklus eines einzelnen Containers hinaus bestehen muss, wird ausgelagert.
Sessions wandern in einen Redis-Cache, der als eigener Container läuft und von allen API-Servern erreichbar ist. Egal, welcher Server eine Anfrage bearbeitet, die Session-Daten sind verfügbar. Hochgeladene Dateien gehen auf den Object Storage. Konfigurationen kommen aus Umgebungsvariablen, nicht aus lokalen Dateien. Datenbankverbindungen, API-Keys, Feature-Flags: alles wird beim Start des Containers als Umgebungsvariable übergeben.
Auch Datenbankverbindungen profitieren vom zustandslosen Ansatz, weil ein Connection Pooler wie PgBouncer zwischen Anwendung und PostgreSQL sitzt und Verbindungen wiederverwendet, statt für jede Anfrage eine neue zu öffnen. Gerade beim Skalieren auf mehrere API-Container würde ohne Pooling jeder Container eigene Verbindungen aufbauen, weshalb PostgreSQL schnell an sein Verbindungslimit stößt.
Adam Wiggins formulierte 2011 die Twelve-Factor App, zwölf Prinzipien für Webanwendungen, die zuverlässig in der Cloud laufen sollen. Fünfzehn Jahre später sind Kernprinzipien wie Konfiguration in Umgebungsvariablen, zustandslose Prozesse, Port Binding und Gleichwertigkeit von Entwicklungs- und Produktionsumgebung nach wie vor zentral, obwohl manche Details ihr Alter zeigen, weil Observability, Supply-Chain-Security und Zero-Downtime-Deployments komplett fehlen. Trotzdem trägt die Grundidee.
In der DevOps-Welt hat sich dafür der Ausdruck „Cattle, not Pets“ etabliert, wobei Server nicht mehr wie Haustiere mit individuellen Namen und Konfigurationen gepflegt werden, sondern austauschbar sind wie Rinder in einer Herde. Einer fällt aus, ein anderer nimmt seinen Platz ein. Funktionieren kann das allerdings nur, wenn kein einzelner Server unverzichtbaren Zustand trägt.
Wann wird Stateless zum Problem?
Nicht alles lässt sich zustandslos gestalten. Datenbanken sind inhärent zustandsbehaftet. PostgreSQL braucht persistenten Speicher für seine Daten, und dieser Speicher darf bei einem Neustart nicht verschwinden. Kubernetes löst das mit StatefulSets und Persistent Volume Claims, aber die Komplexität steigt erheblich. WebSocket-Verbindungen sind ein weiterer Sonderfall. Wenn ein Benutzer eine dauerhafte Verbindung zu einem bestimmten Server hält und dieser Server ausfällt, muss die Verbindung neu aufgebaut werden. Sticky Sessions am Load Balancer mildern das Problem, widersprechen aber dem Stateless-Prinzip.
Aufwendige Dateiverarbeitungen stoßen ebenfalls an Grenzen, weil ein Container, der gerade eine 500 MB große Videodatei konvertiert und dafür 15 Minuten braucht, nicht einfach abgeschossen und neu gestartet werden kann. Warteschlangen wie RabbitMQ oder Redis Streams verteilen solche Jobs an Worker-Container, sodass ein Worker bei einem Absturz den Job erneut aus der Warteschlange ziehen kann.
Als Faustregel gilt: Anwendungslogik zustandslos halten, aber den Zustand an spezialisierten Diensten zentralisieren. Datenbanken, Caches und Object Storage sind die richtigen Orte für persistente Daten. Alles andere gehört nicht in den Container.
Docker Compose: Die Entwicklungsumgebung, die Produktion spiegelt
Für die Entwicklung mit mehreren Containern braucht es ein Werkzeug, um alle Dienste gleichzeitig zu starten und zu verwalten. Docker Compose ist dieses Werkzeug. Eine einzige YAML-Konfigurationsdatei beschreibt alle Dienste: welches Image, welche Ports, welche Umgebungsvariablen, welche Volumes. Ein einziger Befehl startet den gesamten Stack. Auf ihrem MacBook bekommt die Entwicklerin die gleiche Umgebung wie der Tester auf seinem Linux-Rechner.
Seit Docker Compose v2 (v1 wurde im Juni 2023 eingestellt) gibt es Profiles, mit denen sich Dienste gruppieren lassen, sodass eine einzige Compose-Datei mehrere Umgebungen abdeckt. Ein „dev“-Profil startet zusätzlich einen Debug-Container und ein Mailcatcher-Tool, während ein „test“-Profil die Testdatenbank hochfährt und ein Selenium-Container für End-to-End-Tests bereitsteht. In Produktion laufen nur die Kerndienste. Praktisch sieht das so aus: docker compose --profile dev up startet alles für die Entwicklung, docker compose up nur die Basisdienste. Ohne Profiles müssten Teams mehrere YAML-Dateien pflegen, wobei Abweichungen zwischen den Dateien fast unvermeidlich zu Fehlern in der Produktion führen.
Wichtigster Grundsatz dabei: Lokale Entwicklungsumgebung soll der Produktion so ähnlich wie möglich sein. Wenn in Produktion PostgreSQL 18 läuft, dann auch lokal. Wenn in Produktion Redis 8 den Cache übernimmt, dann auch lokal. Gleiche Umgebungsvariablen, die gleichen Port-Zuordnungen, die gleichen Netzwerke zwischen den Containern. Entwicklungsteams, die lokal mit SQLite arbeiten und in Produktion PostgreSQL einsetzen, erleben spätestens bei der ersten komplexen Datenbankabfrage eine böse Überraschung.
Wie stellt man sicher, dass Container in der richtigen Reihenfolge starten?
Container haben Abhängigkeiten untereinander. Jede Anwendung braucht die Datenbank, die Datenbank braucht vielleicht ein Volume. Docker Compose kennt depends_on, aber in der Grundform wartet es nur, bis der Container gestartet ist. Nur heißt das nicht, dass der Dienst darin auch bereit ist. PostgreSQL braucht nach dem Start noch ein bis zwei Sekunden, bis es Verbindungen annimmt.
Health Checks lösen dieses Problem. In der Compose-Datei definiert jeder Dienst einen Test, der prüft, ob er tatsächlich funktioniert. Für PostgreSQL ein pg_isready-Kommando, für Redis ein redis-cli ping, für den Webserver ein HTTP-Request gegen den Health-Endpoint. Mit depends_on und der Bedingung service_healthy startet die Anwendung erst, wenn die Datenbank tatsächlich bereit ist. Ohne Health Checks produziert jeder zweite Erststart Verbindungsfehler.
Ein häufiger Irrtum: Health Checks in Docker Compose sind nicht identisch mit Kubernetes Liveness und Readiness Probes. Compose-Health-Checks beeinflussen die Startreihenfolge, Kubernetes-Probes steuern den laufenden Betrieb.
Für die Entwicklung kommen Volumes hinzu, die den Quellcode vom Host-Rechner in den Container einbinden. Änderungen am Code werden sofort wirksam, ohne den Container neu zu bauen. Hot Reload in Frameworks wie Next.js, Django oder Spring Boot funktioniert innerhalb von Containern genauso wie bei einer direkten Installation.
Welche Fallstricke lauern beim lokalen Setup?
Am meisten frustriert beim Arbeiten mit Docker auf macOS und Windows: die Volume-Performance. Docker läuft auf diesen Systemen nicht nativ, sondern in einer leichtgewichtigen virtuellen Maschine. Jeder Dateizugriff zwischen Host und Container überquert die VM-Grenze. Bei einem Node.js-Projekt mit 200.000 Dateien im node_modules-Verzeichnis macht sich das bemerkbar. Build-Zeiten können sich verdreifachen.
Lösungen existieren: Auf macOS beschleunigt VirtioFS die Dateisynchronisation erheblich, seit Docker Desktop 4.15 ist es verfügbar und inzwischen der Standard. Named Volumes für Abhängigkeitsverzeichnisse (node_modules, vendor, .venv) umgehen das Problem komplett, weil diese Daten direkt in der VM bleiben. Auf Linux gibt es diese Probleme nicht, weil Docker dort nativ läuft.
Ein weiterer Stolperstein: die DNS-Auflösung zwischen Containern. In Docker Compose ist jeder Dienst über seinen Servicenamen erreichbar: Der API-Server verbindet sich zur Datenbank über den Hostnamen „db“, nicht über „localhost“. Diesen Unterschied zu vergessen ist einer der häufigsten Fehler bei Einsteigern.
Und dann ist da die Lizenzfrage. Docker Desktop verlangt seit 2022 eine kostenpflichtige Lizenz für Unternehmen mit mehr als 250 Mitarbeitern oder mehr als zehn Millionen Dollar Jahresumsatz. Für Startups und Freelancer bleibt es kostenlos. Podman Desktop läuft ohne Daemon und ist vollständig Open Source. OrbStack auf macOS bietet eine besonders schnelle VM-Schicht. Rancher Desktop kombiniert Container-Management mit eingebautem Kubernetes. Docker Desktop hat mit seiner Lizenzpolitik eine ganze Generation von Entwicklern zu diesen Alternativen getrieben. Ob das strategisch klug war, darf bezweifelt werden.
Fazit und Ausblick
Container haben die Art verändert, wie Webanwendungen gebaut und ausgeliefert werden. Nicht weil sie leichtgewichtiger sind als virtuelle Maschinen (das auch), sondern weil sie eine Architektur erzwingen, die Skalierung erst möglich macht. Ein Service pro Container, zustandslose Prozesse, externalisierter Speicher. Teams, die diese Prinzipien von Anfang an umsetzen, können später ohne Umbau vom Einzelserver auf ein Cluster wachsen.
Nach meiner Erfahrung liegt die eigentliche Herausforderung beim Container-Deployment nicht in Docker oder Kubernetes. Sie liegt in der Architektur der Anwendung selbst. Wer Sessions im Dateisystem speichert, Uploads auf der lokalen Festplatte ablegt und Konfigurationen in Dateien versteckt, wird auch mit Containern an der gleichen Wand stehen wie das Berliner Startup aus der Einleitung.
Docker Compose liefert die ideale Entwicklungsumgebung, die genau diese Architektur von Tag eins an trainiert. Lokale Compose-Entwicklung baut automatisch Anwendungen, die sich später verteilen lassen. Im nächsten Teil dieser Serie geht es darum, was passiert, wenn Compose nicht mehr reicht: Wie Container auf echte Server kommen, warum Traefik den Reverse Proxy automatisiert und welche Rolle CI/CD-Pipelines beim Weg in die Produktion spielen. Mein Tipp für den Einstieg: Eine bestehende Anwendung mit Docker Compose verpacken, Sessions in Redis auslagern, Datei-Uploads auf Object Storage umstellen. Wer diese drei Schritte geschafft hat, ist für alles Weitere gerüstet.
Quellenverzeichnis
- CNCF Annual Survey 2025: „Kubernetes Established as the De Facto Operating System for AI“, Cloud Native Computing Foundation, Januar 2026
- CNCF/SlashData Report: „Cloud Native Ecosystem Surges to 15.6M Developers“, November 2025
- MinIO GitHub Repository: Archiviert am 13. Februar 2026, github.com/minio/minio
- MinIO Maintenance Mode Announcement: ByteIota, Dezember 2025
- Wiggins, Adam: „The Twelve-Factor App“, 12factor.net, 2011 (letzte Prüfung: Februar 2026)
- Docker Inc.: Docker Compose Release Notes, Version 5.1, docs.docker.com/compose/release-notes
- Docker Inc.: Docker Desktop License Agreement, docs.docker.com/subscription/desktop-license
- Hetzner Online GmbH: Cloud Server Preisliste und Object Storage, hetzner.com/cloud (Stand: Februar 2026)
- Portainer: „4 Best Docker Desktop Alternatives for 2026“, portainer.io/blog, Dezember 2025
- Niessen, Lukas: „The 12-Factor App: 15 Years Later“, Medium, Februar 2026
