Docker Compose Best Practices auf Ubuntu

Praxisnahe Anleitung für saubere Docker-Compose-Setups auf Ubuntu mit Ordnerstruktur, Variablen, Persistenz, Healthchecks, Updates und Backups.

Docker Compose ist für viele Self-Hosted-Setups auf Ubuntu die pragmatischste Art, mehrere Container gemeinsam zu betreiben. Genau deshalb lohnt es sich, das nicht einfach nur irgendwie zum Laufen zu bringen, sondern von Anfang an sauber aufzubauen.

In dieser Anleitung schauen wir uns die wichtigsten Best Practices für Docker Compose auf Ubuntu an: sinnvolle Ordnerstrukturen, Umgebungsvariablen, Persistenz, Netzwerke, Updates, Healthchecks, Logs, Backups und sichere Freigaben nach aussen. Ziel ist nicht nur ein funktionierendes Setup, sondern eines, das du auch in drei oder sechs Monaten noch sauber warten kannst.


1. Warum saubere Compose-Strukturen so wichtig sind

Viele Docker-Probleme entstehen nicht durch Docker selbst, sondern durch unklare Ordner, unübersichtliche Compose-Dateien, wilde Port-Freigaben und fehlende Dokumentation. Das funktioniert oft kurzfristig, wird aber später mühsam.

Ein gutes Compose-Setup hilft dir dabei:

  • Dienste schneller zu verstehen
  • Backups gezielt zu machen
  • Updates kontrolliert einzuspielen
  • Fehler schneller zu finden
  • Setups an Kunden oder Mitarbeitende sauber zu übergeben

Compose ist also nicht nur eine Startmethode für Container, sondern oft die eigentliche Betriebsgrundlage deiner Self-Hosted-Dienste.


2. Eine klare Ordnerstruktur verwenden

Lege für jeden Stack ein eigenes Verzeichnis an. Ich würde Dienste nicht alle in ein einziges Grossprojekt kippen, sondern sauber trennen.

Ein gutes Beispiel sieht so aus:

~/stacks/
├── authentik/
│   ├── compose.yaml
│   ├── .env
│   ├── data/
│   └── backups/
├── paperless/
│   ├── compose.yaml
│   ├── .env
│   ├── data/
│   └── backups/
└── uptime-kuma/
    ├── compose.yaml
    ├── .env
    └── data/

So ist auf einen Blick klar, welcher Dienst welche Dateien, Volumes oder Konfigurationen braucht. Gerade bei Backups und Troubleshooting spart das enorm Zeit.


3. Moderne Compose-Datei sinnvoll aufbauen

Eine Compose-Datei sollte lesbar bleiben. Trenne also Services, Volumes und Netzwerke logisch und gib Containern sprechende Namen nur dann, wenn du einen guten Grund dafür hast. Oft ist es besser, den Standard von Compose zu nutzen.

Ein sauberes Grundmuster könnte so aussehen:

services:
  app:
    image: ghcr.io/example/app:latest
    restart: unless-stopped
    env_file:
      - .env
    volumes:
      - ./data/app:/app/data
    networks:
      - internal
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 30s
      timeout: 5s
      retries: 5
    networks:
      - internal

networks:
  internal:

Wichtig dabei:

  • restart: unless-stopped ist für viele produktive Setups sinnvoll
  • env_file oder Variablen aus .env halten Konfigurationswerte aus der Compose-Datei heraus
  • depends_on ersetzt keine echte Bereitschaftsprüfung, kann aber mit service_healthy hilfreich sein
  • eigene Netzwerke schaffen Trennung zwischen internem Verkehr und öffentlich freigegebenen Diensten

4. Geheimnisse und Variablen nie hart codieren

Passwörter, Tokens, API-Keys und Secrets gehören nicht direkt in die Compose-Datei. Nutze mindestens eine .env-Datei, besser noch zusätzlich eine klare Dokumentation, welche Variablen ein Stack braucht.

Ein Beispiel:

APP_URL=https://app.example.com
DB_PASSWORD=ein-langes-zufaelliges-passwort
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USERNAME=mailer@example.com
SMTP_PASSWORD=noch-ein-geheimes-passwort

Wichtig ist dabei:

  • .env nie in Git einchecken
  • Secrets nicht in Screenshots oder Tickets posten
  • bei Kundenprojekten festhalten, wo produktive Zugangsdaten sicher abgelegt sind

Wenn du mehrere Personen im Betrieb hast, lohnt sich zusätzlich ein Passwort-Manager oder eine zentrale Secret-Ablage.


5. Daten bewusst persistieren

Container selbst sind ersetzbar. Wichtig sind die Daten. Genau deshalb solltest du sehr bewusst entscheiden, welche Pfade persistent gespeichert werden.

Die Grundfrage lautet immer: Was muss nach einem Neustart oder Container-Tausch erhalten bleiben?

Typische persistente Daten sind:

  • Datenbanken
  • Uploads und Medien
  • Anwendungskonfigurationen
  • Zertifikate
  • Queues, Indizes oder Arbeitsdaten je nach Anwendung

Ich empfehle, wenn möglich bind mounts mit klaren Projektpfaden zu verwenden, zum Beispiel ./data/postgres statt blind alles in anonyme Volumes zu legen. Das macht Backups, Sichtbarkeit und Wiederherstellung meist einfacher.

Wenn du Volumes bevorzugst, dokumentiere sauber, welche Volumes zu welchem Dienst gehören. Sonst wird ein Restore unnötig mühsam.


6. Nicht jeden Port nach aussen öffnen

Einer der häufigsten Fehler in Compose-Dateien ist, dass interne Dienste unnötig öffentlich freigegeben werden. Nur weil ein Container auf Port 3000, 5432 oder 8080 läuft, heisst das nicht, dass dieser Port auf dem Host veröffentlicht werden muss.

Faustregel:

  • Datenbanken wie PostgreSQL oder Redis nicht öffentlich publishen, wenn es nicht wirklich nötig ist
  • Admin-Oberflächen wenn möglich nur intern, per VPN oder hinter zusätzlichem Schutz bereitstellen
  • öffentliche Webdienste lieber über einen Reverse Proxy veröffentlichen

Statt:

ports:
  - "5432:5432"

oft besser gar keine Port-Freigabe, wenn nur andere Container auf die Datenbank zugreifen sollen.

Für Webdienste ist häufig dieses Muster sauberer:

  • App läuft intern im Compose-Netzwerk
  • Reverse Proxy greift intern auf den Dienst zu
  • nur Proxy-Ports wie 80 und 443 sind am Host offen

7. Healthchecks sinnvoll einsetzen

Ein Container kann laufen und trotzdem nicht wirklich bereit sein. Genau hier helfen Healthchecks. Sie sind besonders nützlich für Datenbanken, Proxys und Anwendungen, die erst nach einer Startphase korrekt reagieren.

Ein gutes Beispiel ist PostgreSQL:

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U app -d app"]
  interval: 30s
  timeout: 5s
  retries: 5

Oder bei einem Webdienst:

healthcheck:
  test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"]
  interval: 30s
  timeout: 5s
  retries: 5

Das verbessert nicht nur die Übersicht, sondern hilft auch bei kontrollierten Abhängigkeiten und beim Monitoring.


8. Logs bewusst behandeln

Docker-Logs sind praktisch, aber nicht unbegrenzt. Ohne Begrenzung können sie unnötig wachsen und Speicher fressen. Gerade auf kleineren Ubuntu-Servern ist das schnell ein Problem.

Eine einfache Rotation direkt in Compose sieht so aus:

logging:
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "3"

Das ist kein vollwertiges Log-Management, aber für viele kleinere Setups ein sehr guter Anfang. Wenn du später zentrale Logs brauchst, kannst du immer noch Graylog, Loki oder ein anderes System ergänzen.


9. Updates kontrolliert einspielen

Ein Compose-Stack sollte nicht nach dem Motto laufen: schnell pullen und hoffen. Besser ist ein klarer Ablauf.

Ein vernünftiger Standardprozess sieht so aus:

cd ~/stacks/mein-dienst
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=50

Vor Updates solltest du bei wichtigen Diensten zusätzlich:

  • ein Backup machen
  • Release Notes prüfen, wenn ein grösserer Versionssprung ansteht
  • im Idealfall erst in einer Testumgebung prüfen

Vor allem Datenbank-lastige Dienste wie Authentik, Paperless-ngx, Immich oder Nextcloud danken dir einen vorsichtigen Update-Prozess.


10. Backups nicht erst später planen

Ein Compose-Setup ohne Backup ist kein produktives Setup. Sichere mindestens:

  • Compose-Datei
  • .env und Konfigurationsdateien
  • persistente Datenverzeichnisse oder Volumes
  • Datenbank-Dumps, wenn die Anwendung das sinnvoll unterstützt

Wichtig ist ausserdem, dass du nicht nur ein Backup machst, sondern auch weisst, wie ein Restore funktioniert. Genau dieser Punkt wird im Alltag am häufigsten unterschätzt.


11. Compose-Dateien kommentieren und dokumentieren

Gerade wenn mehrere Personen mit einem Server arbeiten, ist eine kurze Dokumentation Gold wert. Halte pro Stack mindestens fest:

  • welcher Dienst hier läuft
  • welche Domain dazugehört
  • welche Daten persistent sind
  • wie Updates laufen
  • wie Backups und Restore funktionieren
  • welche Ports absichtlich veröffentlicht sind

Das kann eine kleine README.md im jeweiligen Stack-Ordner sein. Der Aufwand ist klein, der Nutzen im Notfall riesig.


12. Beispiel für einen gut wartbaren Stack

Ein sauberer Dienst-Ordner könnte am Ende so aussehen:

~/stacks/uptime-kuma/
├── compose.yaml
├── .env
├── README.md
├── data/
└── backups/

Und in der README.md steht kurz:

  • Domain: status.example.com
  • Reverse Proxy: Caddy
  • Datenpfad: ./data
  • Update: docker compose pull && docker compose up -d
  • Backup: tägliches Restic-Backup des Ordners

Genau diese Kleinigkeiten machen aus einem Bastel-Setup eine wartbare Lösung.


13. Typische Fehler, die ich vermeiden würde

  • Compose-Dateien mit fest eingebauten Passwörtern
  • alle Dienste in einem einzigen riesigen Compose-Projekt
  • unnötig veröffentlichte Datenbank- oder Admin-Ports
  • keine Trennung zwischen Konfiguration und Daten
  • kein Backup der .env-Datei
  • Updates ohne vorherigen Datenstand
  • keine Dokumentation, warum bestimmte Ports oder Mounts gesetzt wurden

Wenn du diese Fehler vermeidest, bist du schon deutlich weiter als viele spontane Compose-Installationen.


14. Fazit

Docker Compose ist auf Ubuntu ein extrem starkes Werkzeug, wenn du es sauber strukturierst. Gute Best Practices sorgen nicht nur für Ordnung, sondern sparen bei Updates, Fehlern, Backups und Übergaben enorm viel Zeit.

Die wichtigsten Punkte sind eine klare Ordnerstruktur, saubere Variablen, bewusste Persistenz, wenige veröffentlichte Ports, Healthchecks, Log-Rotation und ein nachvollziehbarer Update- und Backup-Prozess.

Tipp:
Wenn du bereits mehrere Compose-Stacks betreibst, nimm dir einen Abend Zeit und räume sie nach diesen Punkten auf. Der Effekt ist oft grösser, als man zuerst denkt.