Docker auf einem VPS: Was sich wirklich ändert
Docker auf einem VPS nutzt dieselbe Engine, hat aber weniger Spielraum: RAM endet im OOM-Kill, Ports umgehen UFW, Container starten nach Reboots nicht und Speicher läuft voll.
Was sich ändert, wenn Sie Docker auf einem VPS ausführen
Docker auf einem VPS verwendet dieselbe Engine und dieselben Images wie Docker auf Ihrem Laptop. Daher funktionieren alle Befehle, die Sie bereits kennen, weiterhin. Die Umgebung ist jedoch eine andere. Ein Laptop verfügt über freien Arbeitsspeicher, eine Firewall, die niemand scannt, und ein ausreichend großes Laufwerk, dessen Belegung Sie nie prüfen müssen. Ein gemieteter Server hat eine feste Speichergrenze, eine öffentliche IP-Adresse, die innerhalb weniger Minuten nach dem Booten gescannt wird, und ein Root-Dateisystem, das Docker ohne Rückfrage füllt.
Vier Unterschiede verursachen auf einem kleinen Server die meisten Probleme:
- Der Arbeitsspeicher ist begrenzt. Bei einem Engpass beendet der Kernel einen Prozess.
- Ein veröffentlichter Port umgeht UFW (uncomplicated firewall), weil Docker eigene Firewall-Regeln schreibt.
- Container werden nach einem Reboot nicht automatisch erneut gestartet, sofern Sie dies nicht vorher festgelegt haben.
- Images, Container, Volumes und der Build-Cache wachsen, bis der Datenträger voll ist.
In jedem folgenden Abschnitt werden der Fehler, die tatsächlich angezeigte Meldung und die Anleitung genannt, die das Problem ausführlich behebt. Wenn Sie noch keine Compose-Datei erstellt haben, lesen Sie zuerst Grundlagen zu Docker Compose auf einem VPS und kehren Sie anschließend hierher zurück. Auf dieser Seite wird vorausgesetzt, dass Sie bereits einen Stack starten können.
Wie viel RAM verwendet ein Docker-Container?
Weniger, als die meisten erwarten. Ein Container ist ein Prozess in einer cgroup (Control Group) und keine virtuelle Maschine. Daher gibt es keinen Gastkernel und keine feste Speicherzuweisung. Die Kosten entsprechen dem Speicher, auf den der Prozess im Container tatsächlich zugreift. Deshalb passt ein vollständiger Stack in 2 GB, während derselbe Stack aus virtuellen Maschinen darin nicht Platz hätte.
Die folgenden Werte sind typische Leerlaufwerte für unveränderte Images unter Ubuntu 24.04 mit der Standardkonfiguration. Sie wurden wenige Minuten nach dem Start aus docker stats ausgelesen. Sie dienen als Ausgangspunkt für die Planung und sind kein Benchmark für Ihre Arbeitslast. Führen Sie docker stats --no-stream auf Ihrem eigenen System aus, bevor Sie irgendeinem Wert vertrauen, auch diesen nicht.
The data behind this chart
[
{
"label": "nginx",
"idle_mb": 8,
"budget_mb": 64
},
{
"label": "Redis 7",
"idle_mb": 12,
"budget_mb": 128
},
{
"label": "Traefik v3",
"idle_mb": 40,
"budget_mb": 128
},
{
"label": "PostgreSQL 16",
"idle_mb": 45,
"budget_mb": 512
},
{
"label": "Uptime Kuma",
"idle_mb": 95,
"budget_mb": 256
},
{
"label": "MariaDB 11",
"idle_mb": 190,
"budget_mb": 512
},
{
"label": "Nextcloud (Apache)",
"idle_mb": 210,
"budget_mb": 768
}
]Die beiden Spalten haben unterschiedliche Aufgaben. idle_mb zeigt, wie viel der Container im Leerlauf verwendet. budget_mb ist der Wert, den Sie bei der Planung reservieren sollten, weil der reale Betrieb kein Leerlauf ist. PostgreSQL liegt im Leerlauf bei etwa 45 MB und benötigt 512 MB, sobald Verbindungen, Sortierungen und Cache aktiv sind. Planen Sie mit der Budget-Spalte. Verwenden Sie die Leerlauf-Spalte zur Fehlersuche.
Beachten Sie das Muster dieser 7 Zeilen. nginx verwendet im Leerlauf 8 MB und Nextcloud 210 MB. Der Proxy vor Ihren Anwendungen benötigt fast keinen Speicher. Für die Dimensionierung des Systems sind die Datenbank und die PHP-Anwendung entscheidend.
Ein Hinweis zu docker stats: Der Speicherwert umfasst auch den Page Cache, den die Dateizugriffe des Containers geladen haben. Daher steigt der Wert nach dem Start zunächst an und stabilisiert sich anschließend. Überwachen Sie ihn eine Stunde lang, bevor Sie von einem Speicherleck ausgehen.
Dimensionierung eines VPS: Was in 2 GB, 4 GB und 8 GB passt
Ziehen Sie zuerst den Anteil des Hosts ab. Kernel, systemd, journald, sshd und der Docker-Daemon verwenden denselben Arbeitsspeicher wie Ihre Container, und dockerd zusammen mit containerd belegt davon etwa 100 MB. Außerdem benötigen Sie freien Speicher für den Page Cache und für den Lastanstieg, wenn ein Image-Build oder ein Datenbank-Dump läuft.
The data behind this chart
[
{
"plan": "2 GB VPS",
"total_mb": 2048,
"host_reserve_mb": 768,
"container_mb": 1280
},
{
"plan": "4 GB VPS",
"total_mb": 4096,
"host_reserve_mb": 1024,
"container_mb": 3072
},
{
"plan": "8 GB VPS",
"total_mb": 8192,
"host_reserve_mb": 1536,
"container_mb": 6656
}
]host_reserve_mb deckt das Betriebssystem, den Docker-Daemon und die Reserve ab, die den Server unter Last reaktionsfähig hält. Übrig bleibt container_mb. Nur diesen Betrag können Sie verplanen. Die Reserve wächst mit dem Tarif: auf dem kleinsten Server von 768 MB bis zu 1536 MB auf dem größten. Ein größerer Server führt mehr Container aus, schreibt mehr Logs und benötigt mehr Page Cache.
Ein Tarif mit 2 GB lässt 1280 MB für Container übrig. Wenn Sie davon 512 MB für PostgreSQL und 128 MB für Traefik einplanen, ist bereits die Hälfte verbraucht. Der Rest reicht für zwei kleine Anwendungen mit jeweils etwa 256 MB. Das ist ein realer und sinnvoller Server. Für Nextcloud und zusätzlich einen Such-Cluster reicht der Speicher nicht aus.
Ein Tarif mit 4 GB lässt 3072 MB übrig. Darin passen gleichzeitig eine Datenbank, ein Reverse Proxy, drei Anwendungen und ein Monitoring-Container. Das ist die kleinste sinnvolle Größe für wichtige Dienste, weil die freie Speicherkapazität einen fehlerhaften Deploy abfedert.
Ein Tarif mit 8 GB lässt 6656 MB seiner 8192 MB übrig. Die Begrenzung liegt dann meistens nicht mehr beim Arbeitsspeicher, sondern bei der CPU oder beim Datentransfer. Eine Klasse von Containern dimensioniert sich anhand ihrer Konfiguration und nicht anhand der Last: Ein lokaler Model-Server reserviert den KV-Cache proportional zum Context Window. Ollamas num_ctx erhöhen kann das Budget daher um mehrere Gigabyte vergrößern, bevor die erste Anfrage eingeht. Wenn die Berechnung zeigt, dass Ihr Stack nicht passt, wählen Sie den größeren Tarif, statt das Problem durch Optimierungen zu umgehen: was ein VPS tatsächlich kostet zeigt, welchen monatlichen Gegenwert die zusätzlichen Gigabytes haben.
Zwei Regeln halten die Berechnung realistisch. Setzen Sie für jeden Dienst ein Speicherlimit, damit ein außer Kontrolle geratener Prozess nicht den gesamten Server mitreißt. Lassen Sie außerdem den oberen Bereich des Budgets frei, weil docker compose build und pg_dump im ungünstigsten Moment beide Arbeitsspeicher benötigen. Speicherlimits in Docker Compose beschreibt die Syntax und die typischen Fehler.
Warum wird mein Container mit Exit-Code 137 beendet?
Weil der Kernel ihn beendet hat. 137 entspricht 128 plus 9, und Signal 9 ist SIGKILL. Der Container hat mehr Speicher angefordert, als ihm zugewiesen war, und der Out-of-Memory-Killer (OOM-Killer) hat ihn beendet.
CONTAINER ID IMAGE STATUS
9f2c1a4d7b3e postgres:16 Exited (137) 4 minutes agoBestätigen Sie die Ursache, statt zu raten:
docker inspect my-db | grep -i oomkilled
sudo journalctl -k | grep -i "out of memory""OOMKilled": true bedeutet, dass der Container sein eigenes cgroup-Limit erreicht hat. Das Kernel-Log nennt den Prozess, den der Kernel ausgewählt hat:
Memory cgroup out of memory: Killed process 2417 (postgres) total-vm:1284416kBDas ist der gute Fall, weil der Schaden auf einen Container begrenzt blieb. Der schlechte Fall ist ein Container ganz ohne Limit. Ohne Limit entspricht seine Obergrenze dem gesamten Host. Dadurch kann ein Speicherleck in einem Dienst den Host so stark belasten, dass der Kernel anschließend systemweit anhand der Prozessgröße einen Prozess als Opfer auswählt. In der Logzeile fehlt dann das Präfix Memory cgroup; sie lautet Out of memory: Killed process 2417 (postgres). Häufig trifft die Auswahl Ihre Datenbank, während der Container mit dem Speicherleck weiterläuft. Deshalb ist ein Limit für jeden Dienst wichtiger als der genaue Wert eines einzelnen Limits.
Swap verändert den Zeitpunkt, nicht die Berechnung. Die meisten VPS-Images werden ohne Swap ausgeliefert. Prüfen Sie dies mit swapon --show. Wenn kein Swap vorhanden ist, gibt der Befehl überhaupt nichts aus. Eine Swap-Datei gibt dem Kernel die Möglichkeit, selten verwendete Speicherseiten auszulagern. Dadurch gewinnen Sie einige Minuten, um ein Problem zu erkennen.
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -hfree -h sollte jetzt in der Zeile Swap eine ungleich null große Gesamtsumme anzeigen. Swap erweitert den Arbeitsspeicher nicht. Ein System unter dauerhaftem Speicherdruck wird so langsam, dass Sie sich möglicherweise nicht mehr per SSH anmelden können, um das Problem zu beheben. Betrachten Sie Swap daher als Puffer für Alarme und korrigieren Sie die Dimensionierung.
Warum blockiert UFW meinen veröffentlichten Docker-Port nicht?
Weil der Netzwerkverkehr die von UFW überwachte Chain nicht erreicht. Wenn Sie einen Port mit -p 5432:5432 oder einem Compose-Eintrag ports: veröffentlichen, schreibt der Daemon eine DNAT-Regel (Destination Network Address Translation) in die nat-Tabelle und eine Accept-Regel in seine eigene Chain DOCKER. Ein Paket an einen Container wird an diesen Container weitergeleitet und nicht an den Host zugestellt. Daher wird es im FORWARD-Pfad verarbeitet und durchläuft nicht die INPUT-Regeln, die UFW schreibt.
Sie können dies auf dem Server beobachten:
sudo ufw status | grep 5432
sudo iptables -t nat -L DOCKER -n | grep 5432UFW kann 5432 DENY IN Anywhere ausgeben, während die nat-Tabelle eine DNAT tcp ... to:172.18.0.2:5432-Regel für denselben Port enthält. Von einem anderen Rechner aus stellt nc -vz your.server.ip 5432 weiterhin eine Verbindung her. Die Datenbank ist aus dem öffentlichen Internet erreichbar, obwohl die Firewall etwas anderes meldet.
Die Lösung besteht darin, weniger Ports zu veröffentlichen. Container in einem Compose-Projekt verwenden ein gemeinsames Netzwerk und erreichen einander über den Servicenamen. Eine Datenbank, die nur die daneben laufende Anwendung bedient, benötigt daher überhaupt keinen ports:-Eintrag. Wenn Sie lokalen Zugriff benötigen, binden Sie die Veröffentlichung an das Loopback-Interface:
services:
db:
image: postgres:16
ports:
- "127.0.0.1:5432:5432"Nach docker compose up -d schlägt nc -vz your.server.ip 5432 von außen fehl, während psql -h 127.0.0.1 -p 5432 auf dem Host weiterhin funktioniert. In einem kleinen, korrekt konfigurierten Stack veröffentlicht normalerweise nur der Reverse Proxy Ports, und zwar 80 und 443. Warum veröffentlichte Docker-Ports UFW umgehen behandelt die DOCKER-USER-Chain für Fälle, in denen Sie einen Port veröffentlichen und trotzdem filtern müssen. Grundlagen der UFW-Firewall behandelt die zugrunde liegenden Host-Regeln.
Warum sind meine Container nach einem Reboot verschwunden?
Weil nichts festgelegt hat, dass sie wieder gestartet werden. Ein Container wird standardmäßig mit der Restart-Policy no erstellt, sofern Sie keine andere festlegen. Nach einem Reboot bleibt er daher gestoppt, und der Daemon kümmert sich nicht darum. Reboots sind auf einem VPS nicht selten: Kernel-Updates durch unbeaufsichtigte Upgrades, Wartungsarbeiten des Providers und die oben beschriebene OOM-Sequenz führen alle dazu.
Zwei Voraussetzungen müssen erfüllt sein. Der Daemon muss beim Booten starten:
systemctl is-enabled dockerBei einer Standardinstallation von Ubuntu wird damit enabled ausgegeben. Anschließend benötigt jeder Dienst eine Policy:
services:
app:
image: ghcr.io/example/app:1.4
restart: unless-stoppedunless-stopped startet den Container nach einem Reboot erneut und berücksichtigt dabei einen Container, den Sie absichtlich gestoppt haben. always startet dagegen auch Container erneut, die Sie absichtlich gestoppt haben, sobald der Daemon neu gestartet wird. Das ist beim Debugging überraschend. Das Bearbeiten der Datei allein reicht nicht aus, weil die Restart-Policy beim Erstellen des Containers festgelegt wird. Führen Sie docker compose up -d aus, damit der Container neu erstellt wird, und prüfen Sie anschließend den aktuell verwendeten Wert:
docker inspect my-app | grep -A3 RestartPolicyFühren Sie danach absichtlich einen Reboot des Systems durch und anschließend docker compose ps im Projektverzeichnis. Wenn ein Stack einen geplanten Reboot übersteht, übersteht er auch einen ungeplanten. Wenn Ihr Stack eine garantierte Startreihenfolge oder einen einmalig beim Booten ausgeführten Job benötigt, ist eine systemd-Unit das geeignetere Werkzeug: Docker Compose beim Booten starten enthält die Unit-Datei. Damit Sie feststellen können, ob ein wieder gestarteter Container tatsächlich Dienste bereitstellt, fügen Sie Compose-Healthchecks hinzu.
Warum ist die Festplatte meines VPS voll?
Docker behält alles, bis Sie es ausdrücklich entfernen. Jeder Image-Tag, den Sie jemals abgerufen haben, jeder beendete Container, jedes anonyme Volume, das bei einer Neuerstellung zurückgeblieben ist, und jede Ebene des Build-Cache bleibt auf der Festplatte. Bei einem Root-Dateisystem mit 40 GB oder 80 GB, was bei diesen Tarifgrößen normal ist, führt das innerhalb von Monaten statt Jahren zu einem Ausfall.
Eine volle Festplatte sieht nicht wie ein Absturz aus. Sie erhalten innerhalb derselben Stunde no space left on device von einem Container, apt, von journald und docker pull. PostgreSQL nimmt keine Schreibvorgänge mehr an. Der Server ist weiterhin erreichbar. Dadurch fällt das Problem weniger auf als eine Neustartschleife.
Prüfen Sie zuerst, bevor Sie etwas löschen:
docker system df
df -h /docker system df teilt den Gesamtverbrauch in Images, Container, lokale Volumes und Build-Cache auf. Daneben wird jeweils eine RECLAIMABLE-Spalte angezeigt. Auf einem Server, der seine eigenen Images erstellt, ist der Build-Cache normalerweise der größte Posten.
docker image prune -a
docker builder prune
docker system dfdocker image prune -a entfernt jedes Image, das von keinem Container verwendet wird. docker builder prune leert den Build-Cache. Beides ist während des laufenden Betriebs sicher, weil verwendete Daten übersprungen werden. Nicht sicher ist dagegen docker system prune --volumes. Dieser Befehl löscht jedes Volume, auf das derzeit kein Container verweist. Ein Stack, den Sie für das Wochenende angehalten haben, entspricht genau diesem Fall. Sein Datenbank-Volume wird ebenfalls gelöscht. Lesen Sie Bind-Mounts im Vergleich zu benannten Volumes, bevor Sie dieses Flag verwenden, und erstellen Sie zuerst ein Backup.
Container-Logs wachsen langsamer, aber kontinuierlich. Der standardmäßige json-file-Treiber hat keine Größenbegrenzung. Ein besonders gesprächiger Container kann daher Gigabytes in /var/lib/docker/containers schreiben. Begrenzen Sie die Größe für jeden Container in /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}Wenden Sie die Änderung mit sudo systemctl restart docker an. Dadurch werden Ihre Container neu gestartet. Wählen Sie daher einen passenden Zeitpunkt. Die Begrenzung gilt für Container, die nach der Änderung erstellt werden. Erstellen Sie die laufenden Container deshalb mit docker compose up -d --force-recreate neu und prüfen Sie die Einstellung:
docker inspect my-app | grep -A5 LogConfig
sudo du -sh /var/lib/docker/containersDie Inspect-Ausgabe sollte max-size als gesetzt anzeigen. Wenn der Wert leer ist, wurde dieser Container vor der Änderung erstellt und schreibt weiterhin ohne Begrenzung.
Gewohnheiten für einen kleinen Docker-Server
Dafür benötigen Sie weder ein Dashboard noch ein zusätzliches Tool, in das Sie sich erst einarbeiten müssen.
- Führen Sie am ersten Tag jedes Monats
docker system dfunddf -h /aus. Zwei Befehle und 30 Sekunden genügen, um den Trend zu erkennen, lange bevor es zu einem Ausfall kommt. - Legen Sie für jeden Dienst ein Speicherlimit fest, auch für Dienste, die Sie für unkritisch klein halten. Das Limit macht aus einem Ausfall des gesamten Hosts einen einzelnen Container, der neu gestartet wird.
- Überwachen Sie den Server von einem anderen System aus. So erfahren Sie von Speicher- oder Festplattendruck, bevor der Kernel eingreift. Uptime Kuma läuft in einem Container und benötigt im Leerlauf etwa 95 MB.
- Sichern Sie Volumes, nicht Container. Der Container ist ersetzbar, das Volume nicht. restic-Sicherungen auf einem VPS decken sowohl einen Zeitplan als auch einen Wiederherstellungstest ab.
- Fixieren Sie die Image-Tags in der Compose-Datei und aktualisieren Sie sie an einem von Ihnen festgelegten Tag. Mit
latestentspricht die Version, die Sie beim nächstendocker compose pullerhalten, dem Release-Stand dieses Morgens.
Ein kleiner VPS mit Docker bleibt über Jahre stabil, wenn vier Werte im zulässigen Bereich bleiben: das Speicherbudget, die Liste der veröffentlichten Ports, die Restart-Richtlinie jedes Dienstes und der freie Speicherplatz. Alles andere ist dasselbe Docker, das Sie bereits zu Hause betreiben.
FAQ
Wie viel RAM benötige ich, um Docker auf einem VPS auszuführen?
Docker selbst benötigt wenig Ressourcen. Der Daemon und containerd belegen zusammen etwa 100 MB. Der restliche Bedarf entfällt auf Ihre Container. Reservieren Sie zuerst den Anteil des Hosts: 768 MB auf einem System mit 2048 MB für das Betriebssystem, den Daemon und zusätzliche Reserve. Damit verbleiben 1280 MB für die Container. Eine Datenbank mit 512 MB, ein Reverse Proxy mit 128 MB und zwei kleine Anwendungen passen in dieses Budget. Messen Sie Ihren eigenen Stack mit docker stats --no-stream, statt sich auf veröffentlichte Werte zu verlassen.
Kann ich Docker auf einem VPS mit 1 GB ausführen?
Ja, für ein oder zwei leichtgewichtige Container. Richten Sie vor dem Start zusätzlich eine Swap-Datei ein. Sobald das Betriebssystem und der Docker-Daemon laufen, ist bei einem System mit 1 GB grob die Hälfte des Speichers belegt. Damit bleibt Platz für eine kleine Anwendung und einen Reverse Proxy, aber nicht für eine Datenbank unter realer Last. Das Erstellen von Images auf einem System dieser Größe schlägt fehl oder beendet einen anderen Prozess. Erstellen Sie die Images daher an anderer Stelle und laden Sie das fertige Image herunter.
Schützt UFW einen Docker-Container?
Nicht bei veröffentlichten Ports. Docker erstellt eigene DNAT- und Forward-Regeln. Ein Paket an einen veröffentlichten Container-Port wird deshalb an den Container weitergeleitet, statt an den Host zugestellt zu werden. Die von UFW verwalteten INPUT-Regeln sehen dieses Paket nie. ufw deny 5432 kann aktiv sein, während dieser Port aus dem Internet erreichbar ist. Veröffentlichen Sie den Port mit 127.0.0.1:5432:5432 nur auf der Loopback-Schnittstelle, veröffentlichen Sie interne Dienste nicht oder filtern Sie in der DOCKER-USER-Kette.
Werden meine Container nach einem VPS-Reboot neu gestartet?
Nur wenn sie mit einer Restart Policy erstellt wurden. Setzen Sie restart: unless-stopped für jeden Dienst, führen Sie docker compose up -d aus, damit die Container mit dieser Einstellung neu erstellt werden, und prüfen Sie, ob systemctl is-enabled docker den Wert enabled ausgibt. Führen Sie anschließend bewusst einen Reboot durch und prüfen Sie docker compose ps. Eine Restart Policy, die Sie nie getestet haben, ist keine verlässliche Restart Policy.
Wie oft sollte ich Docker-Images bereinigen?
Bei den meisten kleinen Servern reicht eine monatliche Bereinigung. Sie können auch bereinigen, sobald docker system df Speicher meldet, der zurückgewonnen werden kann und Ihnen fehlen würde. docker image prune -a und docker builder prune sind im laufenden Betrieb sicher, weil verwendete Images und Caches übersprungen werden. Vermeiden Sie docker system prune --volumes, sofern Sie nicht genau wissen, welche Volumes nicht referenziert werden. Der Befehl löscht die Daten jedes Stacks, der zu diesem Zeitpunkt angehalten ist.