SSD Nodes Learn Hosting plans →
Anleitungen Matt ConnorVon Matt Connor

Docker Compose auf dem VPS: Grundlagen und Setup

Installieren Sie Docker Engine und Compose auf Ubuntu 24.04, erstellen Sie einen Miniflux-Stack mit PostgreSQL und umgehen Sie ufw-Fallen beim Port-Publishing.

Was Sie aufbauen

Docker Compose bildet die Grundlage für nahezu alles Weitere auf dieser Website. Nextcloud, Vaultwarden, n8n, Immich, Rocket.Chat: Jeder dieser Leitfäden beginnt mit „Erstellen Sie diese Compose-Datei“. Diese Seite erklärt, was diese Datei tatsächlich bedeutet. Sie installieren Docker Engine und das Compose-Plugin aus dem offiziellen apt-Repository von Docker auf Ubuntu 24.04. Anschließend richten Sie einen echten Stack mit zwei Diensten ein: Miniflux, einen kleinen RSS-Reader, und PostgreSQL. Dieses Paar deckt alle Muster ab, die auch die größeren Anwendungen verwenden: festgelegte Images, eine Datenbank mit Healthcheck, ein benanntes Volume, Secrets in einer .env-Datei und einen Port, der nur an localhost veröffentlicht wird.

Die Installation dauert fünf Minuten. Der restliche Leitfaden behandelt die Probleme, die später Schwierigkeiten verursachen: Die Gruppe docker ist praktisch root unter einem anderen Namen, veröffentlichte Ports umgehen Ihre ufw-Regeln, und ein einzelnes Flag für docker compose down löscht Ihre Datenbank ohne Bestätigungsaufforderung.

Voraussetzungen: ein frisches Ubuntu-24.04-KVM-VPS, ein Benutzer mit sudo sowie mindestens 1 GB Arbeitsspeicher. Eine vorhandene Docker-Installation ist ebenfalls unproblematisch. Im ersten Abschnitt wird beschrieben, was Sie entfernen müssen.

Installation aus dem Docker-Repository, nicht aus dem Ubuntu-Repository

Vor dem ersten Befehl sollten Sie zwei falsche Wege ausschließen. Das Ubuntu-eigene docker.io-Paket funktioniert, bleibt aber hinter den Docker-Releases zurück und verwendet nicht das Plugin-Layout, von dem die übrige Dokumentation ausgeht. Der Bindestrich-Befehl docker-compose führt dagegen in die ältere Installationsmethode. Auf einem älteren Server ist das normalerweise Compose v1, das in Python geschrieben wurde und von Docker als End of Life eingestuft ist. Deshalb funktionieren viele dieser Anleitungen nicht mehr. Docker veröffentlicht zwar weiterhin eine eigenständige docker-compose-Binärdatei auf Basis des aktuellen Go-Codes. Auf der eigenen Installationsseite wird diese Variante jedoch als nicht empfohlen und nur aus Gründen der Abwärtskompatibilität unterstützt. Das aktuelle Compose wird mit docker compose und einem Leerzeichen aufgerufen. Es ist ein CLI-Plugin und wird aus demselben Repository wie die Engine installiert.

Falls bereits etwas davon auf dem System vorhanden ist, entfernen Sie es zuerst. Das gilt auch für docker-compose-v2 und docker-buildx, die Ubuntu-eigene Paketierung der Plugins. So kommt alles aus einem einzigen Repository:

sudo apt remove -y docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runc

Package 'docker.io' is not installed, so not removed ist die normale Ausgabe auf einem frischen VPS. Fügen Sie anschließend das Docker-Repository hinzu und installieren Sie die Pakete:

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

Prüfen Sie alle drei Ebenen:

docker --version
docker compose version
sudo docker run --rm hello-world

Die ersten beiden Befehle geben Versionszeichenfolgen aus. Docker Compose version v5.x.x bestätigt, dass das Plugin und nicht die veraltete v1-Binärdatei installiert ist. Das Plugin wechselte im Dezember 2025 direkt von v2 zu v5. Die Versionen 3 und 4 wurden ausgelassen, um Verwechslungen mit den alten Versionen des Compose-Dateiformats zu vermeiden. Anleitungen, die hier v2.x.x zeigen, beschreiben daher dasselbe Tool. Der Befehl hello-world sollte mit Hello from Docker! enden. Das Paket aktiviert den Dienst beim Booten. systemctl is-enabled docker gibt enabled aus.

Die docker-Gruppe bedeutet root-Zugriff – entscheiden Sie bewusst

Derzeit benötigt jeder docker-Befehl sudo, weil der Socket des Daemons unter /var/run/docker.sock root und der Gruppe docker gehört. Ohne Mitgliedschaft erhalten Sie den am häufigsten gesuchten Docker-Fehler:

permission denied while trying to connect to the Docker daemon socket at
unix:///var/run/docker.sock

Die standardmäßige Lösung:

sudo usermod -aG docker $USER

Die Gruppenmitgliedschaft wird beim Anmelden übernommen. Deshalb bleibt der Fehler in Ihrer aktuellen Shell bestehen. Führen Sie für diese Sitzung newgrp docker aus oder melden Sie sich ab und wieder an. Danach sollte id docker in Ihren Gruppen auflisten.

Nun die klare Aussage: Die Mitgliedschaft in der docker-Gruppe gewährt root-Zugriff auf dem Host. Nicht nur eine ähnliche Berechtigung und kein eingeschränkter Zugriff, sondern root. Jedes Mitglied dieser Gruppe kann docker run --rm -it -v /:/host alpine chroot /host ausführen und das gesamte Dateisystem kontrollieren, ohne dass ein Passwort abgefragt wird. Die Gruppe dient der Benutzerfreundlichkeit, nicht der Zugriffseinschränkung.

Der Rootless-Modus von Docker ist die echte Alternative. Dabei läuft der Daemon selbst unter Ihrem nicht privilegierten Benutzer. Das bringt Einschränkungen mit sich: Für Ports unter 1024 ist zusätzliche Konfiguration erforderlich, das Netzwerk läuft über einen Userspace-Shim mit messbarem Overhead, und einige Images verhalten sich ohne echten root-Zugriff fehlerhaft. Auf einem einzelnen VPS mit nur einem Administrator, dessen Login ohnehin sudo-Berechtigungen besitzt, ändert die Gruppe in der Praxis nichts. Genau das setzen alle Anleitungen hier voraus. Geben Sie die Mitgliedschaft trotzdem niemals so aus, als wäre sie weniger weitreichend als sudo.

Anatomie einer Compose-Datei

Geben Sie jedem Stack ein eigenes Verzeichnis. Der Verzeichnisname wird zum Projektnamen und als Präfix für Container, Netzwerke und Volumes verwendet:

sudo mkdir -p /opt/miniflux && sudo chown $USER /opt/miniflux && cd /opt/miniflux

Erstellen Sie compose.yml (die moderne Bezeichnung; docker-compose.yml funktioniert weiterhin). Lassen Sie den alten Schlüssel version: weg. Er ist veraltet, und Compose gibt eine Warnung aus, wenn es ihn findet.

services:
  miniflux:
    image: miniflux/miniflux:2.2.9
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:8080"
    environment:
      - DATABASE_URL=postgres://miniflux:${POSTGRES_PASSWORD}@db/miniflux?sslmode=disable
      - RUN_MIGRATIONS=1
      - CREATE_ADMIN=1
      - ADMIN_USERNAME=admin
      - ADMIN_PASSWORD=${ADMIN_PASSWORD}
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      - POSTGRES_USER=miniflux
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=miniflux
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "miniflux", "-d", "miniflux"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  db-data:

Jede Zeile oben ist eine bewusste Entscheidung. Gehen Sie sie einzeln durch.

Image-Versionen festschreiben: :latest plus ein Pull ist ein unbeaufsichtigtes Upgrade

postgres:16-alpine, nicht postgres:latest. Ein Tag ist nicht unveränderlich: :latest wird bei jedem Pull erneut auf die Version aufgelöst, die der Maintainer zuletzt veröffentlicht hat. Kombinieren Sie das mit der Routine für Upgrades, die Sie gleich kennenlernen, docker compose pull && docker compose up -d, dann bedeutet :latest, dass Versionssprünge immer dann eingespielt werden, wenn der Upstream sie veröffentlicht, und nicht dann, wenn Sie es festlegen. Bei PostgreSQL ist das kein hypothetisches Problem: Ein unerwarteter Sprung von 16 auf 17 lässt den Container in einer Crash-Schleife laufen, weil das Datenverzeichnis inkompatibel ist. PostgreSQL-Hauptversionen erfordern ein Dump-and-Restore und keinen Neustart.

Schreiben Sie mindestens die Hauptversion fest (postgres:16-alpine folgt den Patch-Releases von 16.x). Anwendungen sollten Sie auf ein exaktes Release wie miniflux/miniflux:2.2.9 festschreiben. Prüfen Sie die Releases-Seite des Projekts und verwenden Sie beim Erstellen der Datei die zu diesem Zeitpunkt aktuelle Version. Ein Upgrade besteht dann aus einer bewussten Änderung in einer Zeile und ist in git diff sichtbar.

Auf 127.0.0.1 veröffentlichen, weil Docker ufw umgeht

"127.0.0.1:8080:8080": Hostadresse, Hostport, Containerport. Die meisten Tutorials verwenden "8080:8080". Das ist eine Kurzform für 0.0.0.0:8080:8080 und bedeutet: Der Dienst lauscht auf jeder Schnittstelle, einschließlich der öffentlichen.

Hier liegt die Falle, die fast jeden einmal trifft. Docker veröffentlicht einen Port, indem es eine DNAT-Regel schreibt. Diese Regel ersetzt das Ziel des Pakets durch die interne IP-Adresse des Containers, bevor die Filterung erfolgt. Das Paket nimmt dadurch den Pfad FORWARD und erreicht INPUT nicht, wo Ihre ufw-Regeln angewendet werden. sudo ufw deny 8080 meldet Erfolg, ufw status zeigt, dass der Port verweigert wird, und der Dienst antwortet trotzdem aus dem gesamten Internet. Ihre Firewall ist nicht defekt. Sie wird absichtlich umgangen. Warum Docker ufw umgeht und wie Sie Containerverkehr tatsächlich filtern erklärt den Mechanismus und die DOCKER-USER-Lösung für Ports, die öffentlich erreichbar bleiben müssen.

Die Gewohnheit, die das gesamte Problem beseitigt: Binden Sie veröffentlichte Ports an 127.0.0.1, sofern kein konkreter Grund dagegenspricht. Setzen Sie für alles, was aus dem Internet erreichbar sein soll, einen Reverse Proxy davor. Genau das richtet der Leitfaden zum Traefik-Reverse-Proxy als nächsten Schritt nach dieser Seite ein: ein Container verwaltet die Ports 80 und 443 und leitet Anfragen anhand des Hostnamens mit TLS an alle anderen Dienste weiter. (Kommen Sie von einer älteren Traefik-v2-Konfiguration? Der Migrationsleitfaden von Traefik v2 zu v3 beschreibt die Umbenennungen und Änderungen an den Regeln.)

Prüfen Sie die Bind-Adresse nach dem Start des Stacks: sudo ss -tlnp | grep 8080 sollte 127.0.0.1:8080 anzeigen, nicht 0.0.0.0:8080 oder *:8080.

Named Volumes und Bind-Mounts

db-data:/var/lib/postgresql/data ist ein Named Volume. Docker erstellt und verwaltet ein Verzeichnis unter /var/lib/docker/volumes/ und bindet es in den Container ein. Die Alternative ist ein Bind-Mount, ./data:/var/lib/postgresql/data. Dabei wird ein von Ihnen gewählter Pfad auf dem Host eingebunden.

In der Praxis bewährt sich folgende Aufteilung: Named Volumes für Daten, die nur Container verwenden, insbesondere für Datenbanken. Docker initialisiert das Volume mit dem vom Image erwarteten Eigentümer, und die Dateiberechtigungen funktionieren direkt. Bind-Mounts für Dateien, die Sie vom Host aus bearbeiten, etwa Konfigurationsdateien, die Sie in einem Texteditor ändern, oder eine Medienbibliothek, in die Sie mit rsync Dateien kopieren. Das gilt für alles, dessen Pfad ausdrücklich erkennbar sein soll. Das typische Problem bei Bind-Mounts sind die Eigentumsverhältnisse: Das Image führt seinen Prozess unter einer festen UID aus (das Miniflux-Image verwendet UID 65534, postgres:16-alpine wechselt zu UID 70), während das Verzeichnis auf dem Host UID 1000 gehört. Die Anwendung beendet sich dann beim Start, und in den Logs erscheint permission denied. Named Volumes vermeiden diese Fehlerklasse weitgehend. Dafür liegen die Daten an einem von Docker verwalteten Pfad, der weiter unten beschrieben wird.

environment und .env: Geheimnisse aus git heraushalten

Compose setzt ${POSTGRES_PASSWORD} aus einer Datei namens .env ein, die neben compose.yml liegt. Das geschieht jedoch nur, wenn Ihre Shell diese Variable nicht bereits definiert: Ein in der Shell exportierter Wert hat Vorrang vor der Datei .env. Ein vergessenes export POSTGRES_PASSWORD=... aus einem früheren Experiment überschreibt die Datei daher unbemerkt. docker compose config weiter unten zeigt, welcher Wert verwendet wurde. Erstellen Sie die Datei:

cat > .env <<'EOF'
POSTGRES_PASSWORD=change-me-to-something-long
ADMIN_PASSWORD=change-me-too
EOF
chmod 600 .env
echo ".env" >> .gitignore

Erzeugen Sie echte Werte mit openssl rand -hex 24. Verwenden Sie absichtlich Hex und nicht base64: Das Passwort landet in der DATABASE_URL-Verbindungszeichenfolge. Die Zeichen /, + und =, die base64 erzeugen kann, stören die URL-Analyse. Der Fehler äußert sich dann als Authentifizierungsfehler und nicht als Syntaxfehler und kostet Sie möglicherweise einen ganzen Abend. Die Zeile .gitignore gehört vor den ersten Commit. Die Compose-Datei kann veröffentlicht und versioniert werden. Die Datei .env darf das niemals werden. Ein Geheimnis, das in der git-Historie auftaucht, muss ersetzt werden. Wenn Sie den Stack mit einer fehlenden Variable starten, gibt Compose eine deutliche Warnung aus und verwendet eine leere Zeichenfolge. Bei einem PostgreSQL-Passwort führt das zu einer fehlerhaften Bereitstellung:

WARN[0000] The "POSTGRES_PASSWORD" variable is not set. Defaulting to a blank string.

docker compose config gibt die vollständig interpolierte Datei aus. Das ist die schnellste Möglichkeit zu prüfen, was die Container tatsächlich erhalten. Beachten Sie, dass die Ausgabe Ihre Geheimnisse enthält.

depends_on wartet auf nichts, sofern Sie keinen Healthcheck hinzufügen

Ein einfaches depends_on: [db] steuert nur die Startreihenfolge: Compose startet PostgreSQL zuerst und die Anwendung kurz danach, während PostgreSQL noch mehrere Sekunden benötigt, bis es Verbindungen akzeptiert. Die Anwendung versucht, die Datenbank zu erreichen, schlägt fehl und beendet sich oder versucht es erneut. Das hängt davon ab, wie robust sie implementiert wurde.

Die zuverlässige Variante verwendet die Datei oben: Der db-Dienst definiert einen healthcheck (PostgreSQL liefert pg_isready genau für diesen Zweck), und die Anwendung deklariert depends_on mit condition: service_healthy. Compose startet die Datenbank, prüft den Zustand alle 10 Sekunden und startet Miniflux erst, wenn die Prüfung erfolgreich ist. Wird die Datenbank nie gesund, etwa wegen eines falschen Passworts oder eines beschädigten Volumes, startet die Anwendung nicht. Compose zeigt an, welche Abhängigkeit fehlgeschlagen ist:

dependency failed to start: container miniflux-db-1 is unhealthy

Diese Meldung verweist auf docker compose logs db. Dort steht der eigentliche Fehler.

restart: unless-stopped

restart: unless-stopped bei beiden Diensten bedeutet, dass die Container nach einem Absturz und nach einem VPS-Neustart wieder gestartet werden. Sie bleiben jedoch beendet, wenn Sie zuvor bewusst docker compose stop ausgeführt haben. Die Alternative always startet Container sogar nach einem manuellen Stop wieder. Das ist nur selten beabsichtigt. Ohne eine Restart-Richtlinie beendet ein Neustart nach einem Kernel-Update um 4 Uhr morgens Ihre Dienste unbemerkt, bis Sie es feststellen.

Die täglichen Befehle

Für den täglichen Betrieb benötigen Sie fünf Befehle. Führen Sie sie aus dem Projektverzeichnis aus.

docker compose up -d        # create and start; idempotent, recreates only what changed
docker compose ps           # status, ports, and health of this project's containers
docker compose logs -f miniflux   # follow one service's logs; --tail 100 for recent history
docker compose pull && docker compose up -d   # upgrade to the pinned tags
docker compose down         # stop and remove containers and the network

up -d kann wiederholt und gefahrlos ausgeführt werden. Der Befehl vergleicht die Datei mit dem aktuellen Zustand und startet nur Dienste neu, deren Konfiguration oder Image geändert wurde. Das Upgrade-Paar ruft die Versionen ab, auf die Ihre festgelegten Tags derzeit zeigen: Patch-Releases unter postgres:16-alpine. Bei einer exakt festgelegten Version wird nichts abgerufen, bis Sie diese ändern. Genau das ist beabsichtigt. Nach Upgrades bleiben alte Images auf dem System. Geben Sie mit docker image prune -f Speicherplatz frei.

Nun zum destruktiven Befehl, ausdrücklich: docker compose down kann gefahrlos verwendet werden, wenn Container und Netzwerk verworfen werden dürfen und Ihre Daten im Volume liegen. docker compose down -v löscht auch die benannten Volumes. Ihre Datenbank ist dann sofort und unwiderruflich gelöscht. Es gibt keine Bestätigungsabfrage und keine Möglichkeit zum Rückgängigmachen. Das Flag -v ist zum Entfernen von Testumgebungen vorgesehen. Bei einem Stack mit produktiven Daten müssen Sie es genauso behandeln wie rm -rf. Unter /var/lib/docker/volumes/ gibt es keinen Papierkorb.

Für eine einmalige Shell in einem laufenden Container bringt docker compose exec db psql -U miniflux Sie in die Datenbank. Mit docker compose exec miniflux sh öffnen Sie eine Shell in der Anwendung.

Wo Ihre Daten tatsächlich liegen

Benannte Volumes erhalten das Projektpräfix. Daher wird db-data in einem Verzeichnis namens miniflux zu miniflux_db-data:

docker volume ls
docker volume inspect miniflux_db-data

Die Ausgabe von inspect enthält die relevante Zeile:

"Mountpoint": "/var/lib/docker/volumes/miniflux_db-data/_data"

Dieses Verzeichnis enthält die Datenbank. Es gehört auf dem Host-Dateisystem root und bleibt bei down, Upgrades und dem Neuerstellen von Containern erhalten. Genau dieses Verzeichnis müssen Ihre Backups sichern.

Benanntes Volume sichern

Das Standardmuster ist ein kurzlebiger Container, der das Volume schreibgeschützt neben ein Hostverzeichnis einbindet und die Daten mit tar archiviert:

docker run --rm \
  -v miniflux_db-data:/data:ro \
  -v "$PWD":/backup \
  alpine:3.22 tar czf /backup/miniflux-db-$(date +%F).tar.gz -C /data .

Es ist keine Installation erforderlich, und es bleibt kein laufender Prozess zurück. Beim Wiederherstellen gehen Sie spiegelbildlich vor: tar xzf in ein neues, leeres Volume, wobei Sie die Mounts umkehren.

Eine Besonderheit gilt für Datenbanken: Wenn Sie das Datenverzeichnis eines laufenden Postgres mit tar archivieren, kann das Archiv einen Zustand mitten in einem Schreibvorgang enthalten. Postgres startet dann möglicherweise nicht ordnungsgemäß. Entweder docker compose stop für die wenigen Sekunden, die tar benötigt, oder Sie erstellen besser einen logischen Dump. Dieser ist konstruktionsbedingt konsistent:

docker compose exec -T db pg_dump -U miniflux miniflux | gzip > miniflux-$(date +%F).sql.gz

-T deaktiviert das Pseudo-Terminal, das Compose standardmäßig zuweist. Wenn die Dump-Ausgabe durch ein TTY geleitet wird, kann sie beschädigt werden. Nehmen Sie einen dieser Befehle in cron auf und kopieren Sie das Ergebnis vom VPS. Ein Backup auf derselben Festplatte wie die geschützten Daten ist nur eine Kopie, kein Backup. Der Nextcloud-Leitfaden baut genau um diese beiden Muster eine vollständige, geplante Routine auf.

Fehlermodi und die zugehörigen Meldungen

permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock, Sie sind noch nicht in der Gruppe docker oder die aktuelle Sitzung wurde vor der Gruppenänderung gestartet. id zeigt Ihre effektiven Gruppen an; newgrp docker korrigiert die aktuelle Shell, ein erneutes Anmelden behebt das Problem für alle Sitzungen.

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?, ein anderes Problem: Der Daemon selbst läuft nicht. sudo systemctl status docker und sudo journalctl -u docker -n 50 geben Aufschluss über die Ursache. Auf einem VPS ist eine volle Festplatte die klassische Ursache, prüfen Sie dies mit df -h /var/lib/docker.

Bind for 127.0.0.1:8080 failed: port is already allocated, ein anderer Container belegt bereits diesen Host-Port. docker ps zeigt, welcher Container dies ist; ein veralteter Container aus einem experimentellen docker run vor Wochen ist meist der Auslöser. Wenn docker ps keinen Container anzeigt, der den Port nutzt, belegt ein Prozess außerhalb von Docker den Port: sudo ss -tlnp | grep 8080 benennt diesen Prozess.

yaml: line 14: did not find expected key, ein Einrückungsfehler in oder kurz vor der genannten Zeile. Compose-Dateien sind YAML: Verwenden Sie zwei Leerzeichen zur Einrückung, nur Leerzeichen, und ein Tabulatorzeichen an beliebiger Stelle führt zum Abbruch. docker compose config validiert die Datei, ohne Dienste zu starten; es ist eine gute Angewohnheit, diesen Befehl nach jeder Bearbeitung auszuführen.

Die ufw-Überraschung gibt keine Fehlermeldung aus, was sie gefährlich macht: Das Deployment ist erfolgreich, ufw status sieht korrekt aus, und ein Port-Scan von außen findet Ihre Datenbank dennoch. Lesen Sie den Abschnitt zu den Ports oben erneut, prüfen Sie jeden ports:-Eintrag auf ein fehlendes 127.0.0.1:-Präfix und bestätigen Sie von einem anderen Rechner aus mit curl http://your-vps-ip:8080, dass die Antwort "connection refused" lautet.

Von hier aus führt Sie der Traefik-Leitfaden dazu, diesen einzelnen Stack in viele Anwendungen hinter einem einzigen HTTPS-Einstiegspunkt zu verwandeln, und was sich 2026 für das Self-Hosting lohnt ist die Einkaufsliste, um diese zu betreiben. Sobald mehrere dieser Stacks laufen und jeder sein eigenes Anmeldeformular mitbringt, fasst ein selbst gehosteter SSO-Server wie Authentik diese hinter demselben Proxy unter einem einzigen Konto zusammen.

Ein Spieleserver wie ein Minecraft-Server auf einem VPS ist ein freundliches erstes Compose-Projekt zum Üben. Wenn Sie lieber an etwas lernen möchten, das Sie täglich öffnen, ist openGym, ein selbst gehosteter Workout-Tracker ein kleiner Stack, der an ein Git-Tag statt an ein Image-Tag gebunden ist und TLS davor erfordert, bevor Sie den ersten Passkey registrieren. Fotos sind meist das Erste, was Nutzer aus der Cloud eines Drittanbieters zurückholen möchten, und der Vergleich von PhotoPrism und Immich klärt den RAM-Bedarf und die Backup-Routine, auf die Sie sich einlassen, bevor Sie ein Volume für eines der beiden festlegen. Wenn sich zwei Dienste nicht mehr nach genug anfühlen, ist das Aufsetzen von AFFiNE als Notion-ähnlicher Arbeitsbereich die Anwendung derselben Muster auf vier Container und ein guter Test dafür, ob die oben genannten festen Tags, Healthchecks und benannten Volumes bereits zur Gewohnheit geworden sind.

FAQ

Warum erhalte ich „permission denied while trying to connect to the Docker daemon socket“?

Ihr Benutzer gehört nicht zur Gruppe docker oder wurde erst nach Beginn der aktuellen Sitzung hinzugefügt. Die Gruppenmitgliedschaft gilt erst bei der Anmeldung. Führen Sie sudo usermod -aG docker $USER aus und anschließend newgrp docker, oder melden Sie sich ab und wieder an. Prüfen Sie die Mitgliedschaft mit id. Die Gruppe gewährt Zugriff mit Root-Rechten auf den Host. Fügen Sie daher nur Benutzer hinzu, denen Sie auch sudo geben würden.

Löscht docker compose down meine Daten?

Ein einfaches docker compose down löscht keine Daten. Der Befehl entfernt die Container und das Projektnetzwerk. Benannte Volumes bleiben erhalten und werden beim nächsten up -d wieder eingebunden. docker compose down -v ist die destruktive Variante. Der Befehl löscht die benannten Volumes, also auch Ihre Datenbank, ohne Rückfrage und ohne Wiederherstellungsmöglichkeit. Führen Sie -v niemals für einen Stack mit echten Daten aus, sofern kein verifiziertes Backup vorhanden ist.

Was ist der Unterschied zwischen docker-compose und docker compose?

docker-compose mit Bindestrich ist die alte Methode, Compose aufzurufen. Auf älteren Servern handelt es sich dabei normalerweise um Compose v1, die ursprüngliche Python-Implementierung, die Docker als End of Life eingestuft hat. Docker veröffentlicht weiterhin eine eigenständige docker-compose-Binärdatei auf Basis des aktuellen Go-Codes. Diese Installationsmethode wird jedoch als nicht empfohlen und nur aus Gründen der Abwärtskompatibilität unterstützt. Verwenden Sie sie daher nicht auf neuen Servern. docker compose mit Leerzeichen ist das aktuelle Compose. Es basiert ab v2 auf einem Go-Plugin für die Docker CLI und trägt seit Dezember 2025 die Versionsnummer v5. Installiert wird es als docker-compose-plugin aus dem Docker-apt-Repository. Befehle und YAML-Dateien sind nahezu vollständig kompatibel. Wenn eine ältere Anleitung docker-compose up verwendet, geben Sie stattdessen docker compose up ein.

Warum kann ich meinen Docker-Container aus dem Internet erreichen, obwohl ufw den Port blockiert?

Docker veröffentlicht Ports mit DNAT-Regeln in der iptables-Kette PREROUTING. Die umgeschriebenen Pakete durchlaufen den Pfad FORWARD über die eigenen Docker-Ketten und erreichen daher nicht die Kette INPUT, in der die ufw-Regeln gelten. ufw deny 8080 hat deshalb keine Wirkung auf einen veröffentlichten Container-Port. Beheben Sie das Problem an der Quelle: Veröffentlichen Sie den Port auf 127.0.0.1: und stellen Sie die Dienste stattdessen über einen Reverse Proxy bereit.

Sollte ich ein benanntes Volume oder einen Bind-Mount verwenden?

Verwenden Sie benannte Volumes für Daten, auf die nur der Container zugreift, insbesondere für Datenbanken. Docker setzt dabei die vom Image erwarteten Eigentümer, sodass die Berechtigungen direkt funktionieren. Verwenden Sie Bind-Mounts für Dateien, die Sie auch auf dem Host bearbeiten, etwa Konfigurationen, die Sie ändern, oder Medien, die Sie hochladen. Das gilt auch für alles, dessen Pfad eindeutig sichtbar sein soll. Wenn ein Container beim Start mit permission denied auf einem Bind-Mount fehlschlägt, prüfen Sie zuerst, ob die UID auf dem Host und im Container nicht übereinstimmt.