Docker itself is not the problem on a 1 GiB box — the daemon idles under 50 MB. The problem is that Compose will happily start five containers with no memory limits at all, and the kernel's OOM killer does not ask which one you meant to keep.Docker selbst ist auf einer 1-GiB-Kiste nicht das Problem — der Daemon dümpelt im Leerlauf unter 50 MB. Das Problem ist, dass Compose bereitwillig fünf Container ganz ohne Memory-Limits startet, und der OOM-Killer des Kernels fragt nicht nach, welchen davon du eigentlich behalten wolltest.
Ubuntu's own repository ships an old, renamed build called docker.io. Skip it and add Docker's official apt repository instead, so you get current releases and the docker compose plugin as a first-class package, not the separate Python tool it used to be. The full steps also live in Docker's own Ubuntu install guide:Ubuntus eigenes Repository liefert einen alten, umbenannten Build namens docker.io. Überspringe ihn und füge stattdessen Dockers offizielles apt-Repository hinzu, damit du aktuelle Releases bekommst und das docker compose-Plugin als vollwertiges Paket, nicht mehr das separate Python-Tool von früher. Die vollständigen Schritte stehen auch in Dockers eigener Ubuntu-Installationsanleitung:
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Add your user to the docker group so you stop typing sudo in front of every command, then start a new shell for it to take effect:Füge deinen Nutzer der Gruppe docker hinzu, damit du nicht mehr vor jedem Befehl sudo tippen musst, und starte danach eine neue Shell, damit das wirksam wird:
sudo usermod -aG docker "$USER"
newgrp docker
From here it is docker compose — no hyphen, a subcommand of the docker CLI — not the old standalone docker-compose binary. The compose file syntax is identical either way.Von hier an heißt es docker compose — ohne Bindestrich, ein Subcommand der docker-CLI — nicht mehr das alte eigenständige docker-compose-Binary. Die Syntax der Compose-Datei ist in beiden Fällen identisch.
A 1 GiB Starter has exactly 1 GiB of RAM and nothing behind it. Without swap, the moment memory pressure crosses that line, the kernel picks a process to kill — sometimes sshd, not the container you meant to lose. A swap file does not make your workload faster; it turns a hard crash into a slow one you can watch coming with free -h:Ein 1-GiB-Starter hat genau 1 GiB RAM und nichts dahinter. Ohne Swap wählt der Kernel in dem Moment, in dem der Speicherdruck diese Grenze überschreitet, einen Prozess zum Beenden aus — manchmal sshd, nicht den Container, den du eigentlich loswerden wolltest. Eine Swap-Datei macht deine Workload nicht schneller; sie macht aus einem harten Absturz einen langsamen, den du mit free -h kommen siehst:
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
Two gigabytes is a reasonable default on a 25 GB disk. Also lower how eagerly the kernel reaches for it — on a server you want swap as an emergency buffer, not an active memory tier:Zwei Gigabyte sind auf einer 25-GB-Platte ein vernünftiger Standardwert. Senk außerdem, wie eifrig der Kernel danach greift — auf einem Server willst du Swap als Notfallpuffer, nicht als aktive Speicherebene:
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swappiness.conf
sudo sysctl --system
This only matters for containers that need to be reached from outside — a web UI, a webhook, anything with a browser pointed at it. A background worker that only makes outbound calls does not care.Das spielt nur für Container eine Rolle, die von außen erreichbar sein müssen — eine Web-UI, ein Webhook, alles, worauf ein Browser zeigt. Ein Hintergrund-Worker, der nur ausgehende Aufrufe macht, ist davon unberührt.
Cheap VPS plans commonly hand you NAT IPv4 — a shared address with a small set of forwarded ports — rather than an address of your own. A reverse proxy that gets a certificate and serves HTTPS needs to own ports 80 and 443 on a public address; a forwarded port like 41022 instead means plain https://app.example.com will not work, because you do not hold 443 on that IP.Günstige VPS-Tarife geben dir häufig NAT-IPv4 — eine geteilte Adresse mit einer kleinen Auswahl weitergeleiteter Ports — statt einer eigenen Adresse. Ein Reverse-Proxy, der ein Zertifikat holt und HTTPS ausliefert, muss die Ports 80 und 443 auf einer öffentlichen Adresse besitzen; ein weitergeleiteter Port wie 41022 bedeutet stattdessen, dass ein einfaches https://app.example.com nicht funktioniert, weil dir Port 443 auf dieser IP nicht gehört.
Two honest ways through it:Zwei ehrliche Auswege:
Read NAT IPv4 vs a dedicated IP and NAT IPv4, ports and forwarding before you order if you are not sure which situation you are in.Lesenswert vorab, wenn du nicht sicher bist, in welcher Situation du steckst: NAT IPv4 vs. dedizierte IP und NAT IPv4, Ports und Weiterleitung.
Compose files often show a deploy.resources.limits.memory block, documented in the Compose Specification's deploy section, and leave it at that. On a plain docker compose up, it is silently ignored — deploy is written for Swarm, and outside Swarm mode Compose only honors it when you add the --compatibility flag, which translates it into the same cgroup limit as the older mem_limit property:Compose-Dateien zeigen oft einen deploy.resources.limits.memory-Block, dokumentiert im deploy-Abschnitt der Compose Specification, und belassen es dabei. Bei einem einfachen docker compose up wird er stillschweigend ignoriert — deploy ist für Swarm geschrieben, und außerhalb des Swarm-Modus beachtet Compose ihn nur, wenn du das Flag --compatibility hinzufügst, das ihn in dasselbe cgroup-Limit übersetzt wie die ältere Eigenschaft mem_limit:
docker compose --compatibility up -d
If you would rather not remember a flag on every invocation, use mem_limit directly. It is a legacy top-level property, still fully supported by the compose plugin, and it applies with a plain docker compose up. The Uptime Kuma tag below is an old 1.x release kept only to show the syntax, not a recommendation — check the project's GitHub releases page for whatever is actually current before you copy this in:Wenn du dir lieber kein Flag bei jedem Aufruf merken willst, nutze direkt mem_limit. Das ist eine Legacy-Eigenschaft auf oberster Ebene, vom Compose-Plugin weiterhin vollständig unterstützt, und sie greift bei einem einfachen docker compose up. Der Uptime-Kuma-Tag unten ist ein altes 1.x-Release, das nur zur Veranschaulichung der Syntax dient, keine Empfehlung ist — prüfe die GitHub-Releases-Seite des Projekts auf das, was tatsächlich aktuell ist, bevor du das hier übernimmst:
services:
uptime-kuma:
image: louislam/uptime-kuma:1.23.16
restart: unless-stopped
mem_limit: 200m
memswap_limit: 400m
volumes:
- kuma_data:/app/data
ports:
- "127.0.0.1:3001:3001"
volumes:
kuma_data:
mem_limit caps physical memory; memswap_limit caps memory plus swap, and doubling it lets the container spill a little into the swap file instead of getting OOM-killed the instant it touches the ceiling. Set both, on every service, before you decide the box "isn't big enough" — an unbounded container competing with three other unbounded containers is a resource-allocation problem, not a RAM problem.mem_limit deckelt den physischen Speicher; memswap_limit deckelt Speicher plus Swap, und es zu verdoppeln erlaubt dem Container, ein wenig in die Swap-Datei überzulaufen, statt sofort OOM-gekillt zu werden, sobald er an die Decke stößt. Setze beides, bei jedem Dienst, bevor du entscheidest, die Kiste sei „nicht groß genug“ — ein unbegrenzter Container, der mit drei anderen unbegrenzten Containern konkurriert, ist ein Problem der Ressourcenverteilung, kein RAM-Problem.
Docker's default logging driver, json-file, keeps every line a container has ever printed to stdout, with no size cap out of the box. A chatty service left running for months can grow its log file into gigabytes on a 25 GB disk, and the symptom shows up as "the disk is full" days after the actual cause.Dockers Standard-Logging-Treiber, json-file, behält jede Zeile, die ein Container je nach stdout geschrieben hat, ohne Größenbegrenzung ab Werk. Ein geschwätziger Dienst, der monatelang läuft, kann seine Logdatei auf einer 25-GB-Platte auf mehrere Gigabyte anwachsen lassen, und das Symptom zeigt sich als „die Platte ist voll“ — Tage nach der eigentlichen Ursache.
Set a default at the daemon level so nothing you forget to configure per-service falls through:Setze einen Standard auf Daemon-Ebene, damit nichts, was du pro Dienst zu konfigurieren vergisst, durchrutscht:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Save that as /etc/docker/daemon.json and restart the daemon:Speichere das als /etc/docker/daemon.json und starte den Daemon neu:
sudo systemctl restart docker
This only applies to containers created after the restart, so recreate anything already running (docker compose up -d --force-recreate) to pick it up. Override it per service in the compose file when a different limit makes sense:Das gilt nur für Container, die nach dem Neustart erzeugt werden. Erzeuge daher alles, was bereits läuft, neu (docker compose up -d --force-recreate), damit es übernommen wird. Überschreibe es pro Dienst in der Compose-Datei, wenn ein anderes Limit sinnvoller ist:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Three files of ten megabytes each caps that service at 30 MB of logs, rotated automatically, forever.Drei Dateien von je zehn Megabyte deckeln diesen Dienst auf 30 MB Logs, automatisch rotiert, für immer.
latest is not a version, it is a promise that the maintainer will not break anything, and that promise gets broken eventually, usually on a night you were not planning to debug anything. Pin an explicit tag, the same way the Uptime Kuma line above does:latest ist keine Version, es ist ein Versprechen des Maintainers, nichts kaputtzumachen, und dieses Versprechen wird irgendwann gebrochen — meist in einer Nacht, in der du nichts debuggen wolltest. Pinne einen expliziten Tag, genau wie die Uptime-Kuma-Zeile oben es tut:
image: louislam/uptime-kuma:1.23.16
Bump it deliberately with docker compose pull && docker compose up -d after reading what changed, not automatically. For the strongest guarantee that a redeploy pulls the exact same bytes, pin the digest instead of the tag (image: name@sha256:...) — the right trade for anything holding data you care about.Erhöhe ihn bewusst mit docker compose pull && docker compose up -d, nachdem du gelesen hast, was sich geändert hat, nicht automatisch. Für die stärkste Garantie, dass ein Redeploy exakt dieselben Bytes zieht, pinne den Digest statt des Tags (image: name@sha256:...) — der richtige Kompromiss für alles, was Daten hält, die dir wichtig sind.
For anything with a web UI you intend to open in a browser, Caddy is the shortest path to real HTTPS, because it requests and renews the certificate on its own — the mechanics are covered in Caddy's automatic HTTPS documentation:Für alles mit einer Web-UI, die du im Browser öffnen willst, ist Caddy der kürzeste Weg zu echtem HTTPS, weil es das Zertifikat von sich aus anfordert und erneuert — die Mechanik ist in Caddys Dokumentation zu Automatic HTTPS beschrieben:
app.example.com {
reverse_proxy 127.0.0.1:3001
}
That is the whole config for one service; add another block for each additional hostname. Bind the container's own port to 127.0.0.1, as in the Uptime Kuma example above, so the only thing reachable from the internet is Caddy itself, not every container's raw port.Das ist die gesamte Konfiguration für einen Dienst; füge für jeden weiteren Hostnamen einen weiteren Block hinzu. Binde den eigenen Port des Containers an 127.0.0.1, wie im Uptime-Kuma-Beispiel oben, sodass aus dem Internet nur Caddy selbst erreichbar ist, nicht der rohe Port jedes einzelnen Containers.
Caddy's own footprint is small — comfortably under 50 MB idle — so it is not what pushes you over 1 GiB; the containers behind it are. Run one Caddy instance and proxy everything through it rather than giving each service its own.Caddys eigener Fußabdruck ist klein — im Leerlauf bequem unter 50 MB —, es ist also nicht das, was dich über 1 GiB treibt; das sind die Container dahinter. Betreibe eine einzige Caddy-Instanz und leite alles darüber, statt jedem Dienst seine eigene zu geben.
Every docker compose pull and image rebuild leaves old layers behind. On a 25 GB disk that adds up fast. The safe, routine command is:Jeder docker compose pull und jeder Image-Rebuild lässt alte Layer zurück. Auf einer 25-GB-Platte summiert sich das schnell. Der sichere Routine-Befehl ist:
docker image prune -f
That removes only dangling images — layers with no tag pointing at them anymore — and never touches anything a running or stopped container still references. docker system prune -af --volumes is the aggressive version, and --volumes is the part to be careful with: it deletes any volume not currently attached to a container, including the data volume of anything you stopped but never removed. Run it by hand, read what it lists before confirming, and never put it in an unattended cron job.Das entfernt nur „dangling“ Images — Layer, auf die kein Tag mehr zeigt — und rührt nie etwas an, worauf ein laufender oder gestoppter Container noch verweist. docker system prune -af --volumes ist die aggressive Variante, und --volumes ist der Teil, bei dem Vorsicht geboten ist: Er löscht jedes Volume, das gerade an keinem Container hängt, einschließlich des Datenvolumes von allem, was du gestoppt, aber nie entfernt hast. Führe ihn von Hand aus, lies, was er auflistet, bevor du bestätigst, und pack ihn niemals in einen unbeaufsichtigten Cron-Job.
With sane limits set, on a 1 GiB Starter:Mit vernünftig gesetzten Limits, auf einem 1-GiB-Starter:
Fits comfortably. Uptime Kuma monitoring a few dozen endpoints, idling under 100 MB. Vaultwarden, a lean Rust rewrite of the Bitwarden server, idling under 50 MB. A small n8n instance running a handful of workflows without heavy concurrent executions — see self-hosting n8n on a VPS for the full setup and its own memory notes. Any one or two of these together, each with a mem_limit set, leaves real headroom for the OS and Docker itself.Passt bequem. Uptime Kuma, das ein paar Dutzend Endpunkte überwacht, im Leerlauf unter 100 MB. Vaultwarden, eine schlanke Rust-Neuimplementierung des Bitwarden-Servers, im Leerlauf unter 50 MB. Eine kleine n8n-Instanz, die eine Handvoll Workflows ohne starke gleichzeitige Ausführungen laufen lässt — siehe n8n selbst hosten auf einem VPS für das vollständige Setup und die eigenen Hinweise zum Speicher. Ein oder zwei davon zusammen, jeweils mit gesetztem mem_limit, lassen echten Spielraum für das Betriebssystem und Docker selbst.
Does not fit, realistically. Nextcloud once you turn on preview generation — the PHP-FPM workers that render thumbnails spike hard under real usage and get OOM-killed on a 1 GiB box regardless of how carefully you tune it; self-hosting Nextcloud properly covers what it actually needs. Gitea plus CI runners — Gitea alone is light, but each CI job the runner picks up spins up its own container with its own memory demand stacked on top of everything already running. Anything built on Elasticsearch — its documented minimum heap alone is larger than this entire machine, before the OS or any other service gets a share.Passt realistisch nicht. Nextcloud, sobald du die Vorschau-Generierung einschaltest — die PHP-FPM-Worker, die Thumbnails rendern, schlagen unter echter Last stark aus und werden auf einer 1-GiB-Kiste OOM-gekillt, egal wie sorgfältig du sie tunst; Nextcloud richtig selbst hosten deckt ab, was es tatsächlich braucht. Gitea plus CI-Runner — Gitea allein ist leicht, aber jeder CI-Job, den der Runner aufnimmt, startet seinen eigenen Container mit eigenem Speicherbedarf, obendrauf auf allem, was schon läuft. Alles, was auf Elasticsearch aufbaut — schon dessen dokumentierter Mindest-Heap ist größer als diese gesamte Maschine, bevor das Betriebssystem oder ein anderer Dienst überhaupt etwas abbekommt.
The pattern across all three: it is not the base application that breaks 1 GiB, it is a specific feature — previews, CI jobs, a search index — that spikes far above idle. Read the feature list with that in mind, not just a "runs on a Raspberry Pi" marketing line.Das Muster bei allen dreien: Es ist nicht die Basisanwendung, die 1 GiB sprengt, es ist ein bestimmtes Feature — Vorschauen, CI-Jobs, ein Suchindex —, das weit über den Leerlauf hinausschießt. Lies die Feature-Liste mit diesem Gedanken im Kopf, nicht nur die Marketingzeile „läuft auf einem Raspberry Pi“.
Full disclosure: this is what we sell. A 1 GiB Starter runs two or three small, memory-limited containers without drama; move up a tier the moment you want Nextcloud with previews, Gitea plus CI runners, or anything built on Elasticsearch.Zur vollen Transparenz: Das ist, was wir verkaufen. Ein 1-GiB-Starter betreibt zwei oder drei kleine, speicherbegrenzte Container ohne Drama; steige eine Stufe höher, sobald du Nextcloud mit Vorschauen, Gitea plus CI-Runner oder irgendetwas auf Elasticsearch-Basis willst.
Linux KVM VPS — EUR 4.99 to EUR 59.99 a month, on our own single-tenant bare metal in Dallas, TX and Charlotte, NC. Full hardware virtualisation (KVM), your own kernel, full root. Six tiers, vps-starter to vps-ultra. Starter is 1 vCPU, 1 GiB RAM, 25 GB disk.Linux-KVM-VPS — 4,99 bis 59,99 EUR im Monat, auf unserer eigenen Single-Tenant-Bare-Metal-Hardware in Dallas, TX und Charlotte, NC. Vollständige Hardware-Virtualisierung (KVM), eigener Kernel, volles Root. Sechs Tarife, vps-starter bis vps-ultra. Starter hat 1 vCPU, 1 GiB RAM, 25 GB Speicher.
You order in the shop, pay by card (Stripe) or SEPA bank transfer, and your login details are e-mailed to you once the service is set up. Support is e-mail, run by one person, with no guaranteed response time. All prices are final totals under the German small-business rule (§19 UStG); no VAT is added or shown.Du bestellst im Shop, zahlst per Karte (Stripe) oder SEPA-Überweisung, und deine Zugangsdaten werden dir per E-Mail zugeschickt, sobald der Dienst eingerichtet ist. Support läuft per E-Mail, von einer einzelnen Person betrieben, ohne garantierte Reaktionszeit. Alle Preise sind Endpreise. Gemäß § 19 UStG wird keine Umsatzsteuer ausgewiesen.
Order vps-starter → · Linux KVM VPS overviewvps-starter bestellen → · Übersicht Linux-KVM-VPS
Written by the person who runs overnight.host: a small, honest hosting company on dedicated bare metal — Linux VPS, game servers, web hosting. Live status at up.overnight.host.Geschrieben von der Person, die overnight.host betreibt: ein kleines, ehrliches Hosting-Unternehmen auf dedizierter Bare-Metal-Hardware — Linux-VPS, Gameserver, Webhosting. Live-Status unter up.overnight.host.
deploy.resources.limits.memory actually do anything with plain docker compose up?Macht deploy.resources.limits.memory bei einem einfachen docker compose up überhaupt etwas?No, not by itself. Compose only applies the deploy section's resource limits when you add the --compatibility flag; without it, the block is parsed and silently ignored. Either add that flag to every invocation or use the simpler mem_limit / memswap_limit properties, which apply on a plain docker compose up with no flag required.Nein, nicht von selbst. Compose wendet die Ressourcenlimits des deploy-Abschnitts nur an, wenn du das Flag --compatibility hinzufügst; ohne das wird der Block geparst und stillschweigend ignoriert. Füge das Flag entweder bei jedem Aufruf hinzu oder nutze die einfacheren Eigenschaften mem_limit / memswap_limit, die bei einem einfachen docker compose up ohne nötiges Flag greifen.
You need it more on a small VPS than on a laptop with 16 GiB to spare. It is not extra performance — it is the difference between a container getting cleanly OOM-killed and logged versus the kernel picking an arbitrary process, possibly sshd, when memory runs out with no buffer at all.Du brauchst es auf einem kleinen VPS mehr als auf einem Laptop mit 16 GiB Reserve. Es ist keine zusätzliche Performance — es ist der Unterschied zwischen einem Container, der sauber OOM-gekillt und geloggt wird, und dem Kernel, der einen beliebigen Prozess auswählt, möglicherweise sshd, wenn der Speicher ohne jeden Puffer ausgeht.
docker stats shows live, per-container cgroup memory and CPU usage, which is what your mem_limit values are being checked against. Cross-check the host total with free -h — if docker stats looks fine but free -h shows swap in heavy use, something outside your containers, or a container with no limit set, is the culprit.docker stats zeigt live den cgroup-Speicher- und CPU-Verbrauch pro Container, wogegen deine mem_limit-Werte geprüft werden. Gleiche die Gesamtsumme des Hosts mit free -h ab — sieht docker stats unauffällig aus, zeigt free -h aber Swap stark ausgelastet, ist etwas außerhalb deiner Container oder ein Container ohne gesetztes Limit der Übeltäter.
You can get it running, but you are fighting the application's own defaults the whole time, and any visit to the Photos or Files app will try to generate a preview unless you disable that server-side too. It is a smaller, more fragile deployment than the guide it deserves; self-hosting Nextcloud properly is written for the tier that actually suits it.Du bekommst es zum Laufen, aber du kämpfst dabei die ganze Zeit gegen die eigenen Standardeinstellungen der Anwendung an, und jeder Besuch der Fotos- oder Dateien-App versucht eine Vorschau zu erzeugen, sofern du das nicht auch serverseitig deaktivierst. Das ist ein kleineres, fragileres Deployment, als es die Anleitung verdient hätte; Nextcloud richtig selbst hosten ist für die Stufe geschrieben, die tatsächlich dazu passt.
No. Docker and Compose do not care about your networking situation at all. A dedicated IPv4 only becomes relevant the moment a service needs inbound HTTPS on a public hostname and your default NAT IPv4 does not forward 80 and 443 to you — and plenty of useful compose stacks never need that.Nein. Docker und Compose kümmern sich überhaupt nicht um deine Netzwerksituation. Eine dedizierte IPv4 wird erst relevant, sobald ein Dienst eingehendes HTTPS auf einem öffentlichen Hostnamen braucht und deine Standard-NAT-IPv4 dir 80 und 443 nicht weiterleitet — und viele nützliche Compose-Stacks brauchen das nie.
Prices are final totals; no VAT is shown (§19 UStG). Need something the shop does not list? Email us for a written offer.Alle Preise sind Endpreise ohne ausgewiesene USt. (§19 UStG). Du brauchst etwas, das nicht im Shop steht? Schreib uns für ein schriftliches Angebot.
Order now →Jetzt bestellen → Request a custom configIndividuelle Konfiguration anfragen