⚡ HomelabVergleich

Docker-Compose Best Practices fürs Homelab: Saubere Stacks, sichere Secrets

Ein Homelab wächst schnell: Irgendwann laufen Pihole, Home Assistant, Jellyfin, Nextcloud und ein Dutzend weitere Dienste parallel – und jeder davon braucht eine eigene Konfiguration. Genau hier entscheidet sich, ob dein Setup wartbar bleibt oder zum undurchschaubaren Flickenteppich wird. Der Schlüssel liegt in sauberen docker-compose.yml-Dateien. Dieser Guide zeigt die wichtigsten Best Practices, mit denen deine Stacks übersichtlich, sicher und nachvollziehbar bleiben.

Sprechende Namen: Container und Services klar benennen

Der erste Schritt zu einem wartbaren Stack sind eindeutige Namen. Docker vergibt sonst automatisch Namen wie eager_banach – die helfen niemandem. Setze daher immer einen sprechenden Servicenamen und einen Container-Namen:


services:
  pihole:
    container_name: pihole
    image: pihole/pihole:latest

Faustregel: Der Service-Name ist zugleich die Rolle im Stack, der container_name macht den Container in docker ps und im Portainer sofort identifizierbar. Vermeide Sonderzeichen und Großbuchstaben – Kleinbuchstaben mit Bindestrichen sind der gängige Standard.

Volumes: Daten außerhalb des Containers ablegen

Der wichtigste Grundsatz überhaupt: Ein Container ist vergänglich, seine Daten nicht. Alles, was nach einem Update erhalten bleiben soll, gehört in ein Volume oder ein gebundenes Verzeichnis – niemals in das Container-Dateisystem.


volumes:
  - pihole_data:/etc/pihole
  - ./config/pihole:/etc/dnsmasq.d

Benannte Volumes wie pihole_data verwaltet Docker selbst und eignen sich für Datenbanken und interne Zustände. Gebundene Verzeichnisse (./config/...) nutzt du, wenn du Konfigurationsdateien direkt im Dateisystem bearbeiten willst. Beide Varianten überstehen docker compose down und Image-Updates unbeschadet.

Netzwerke: Dienste gezielt verbinden

Ein eigenes Netzwerk pro Stack isoliert deine Dienste sauber vom Rest und verhindert, dass alles unkontrolliert im Standard-Netzwerk hängt. Compose legt pro Datei automatisch ein Netzwerk an – explizit definiert wird es übersichtlicher:


networks:
  proxy:
    external: true
  internal:

Ein externes Netzwerk proxy teilst du mit deinem Reverse-Proxy (etwa Traefik oder Nginx Proxy Manager), damit er auf deine Dienste zugreifen kann. Das interne Netzwerk internal bleibt für Dienste wie eine Datenbank reserviert, die von außen gar nicht erreichbar sein müssen. So verkleinerst du die Angriffsfläche erheblich.

Umgebungsvariablen und .env: Secrets nie ins Repo

Passwörter, API-Keys und Zugangsdaten gehören nicht in die docker-compose.yml – schon gar nicht, wenn du sie mit Git versionierst. Lagere sie stattdessen in einer .env-Datei im selben Verzeichnis:


# .env
PUID=1000
PGID=1000
TZ=Europe/Berlin
DB_PASSWORD=mein-sicheres-passwort

Compose liest diese Datei automatisch ein. In der Compose-Datei referenzierst du die Werte mit ${VARIABLE}:


services:
  db:
    image: mariadb:11
    environment:
      MARIADB_ROOT_PASSWORD: ${DB_PASSWORD}
    env_file:
      - .env

Die .env selbst nimmst du in die .gitignore auf und legst zusätzlich eine .env.example mit Platzhaltern ins Repo – so wissen auch andere, welche Variablen nötig sind.

Healthchecks: Ausfall automatisch erkennen

Ein Healthcheck prüft in festen Abständen, ob ein Dienst tatsächlich funktioniert – nicht nur, ob der Prozess läuft. Besonders wertvoll ist das bei Diensten, die erst nach einer Weile bereit sind, etwa Datenbanken:


services:
  db:
    image: mariadb:11
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

Der start_period gibt dem Container Zeit zum Hochfahren, bevor der Check greift. Ein fehlgeschlagener Healthcheck markiert den Container als unhealthy – und in Kombination mit einem Monitoring wie Uptime-Kuma bekommst du den Ausfall sofort gemeldet. Vor allem für abhängige Dienste kannst du mit depends_on und condition: service_healthy erzwingen, dass ein Service erst startet, wenn der andere bereit ist.

Restart-Policies: Dienste automatisch wiederbeleben

Die Restart-Policy legt fest, was nach einem Absturz oder Neustart des Hosts passiert. Für fast alle Homelab-Dienste ist unless-stopped die richtige Wahl:


restart: unless-stopped

Damit startet der Container bei jedem Neustart des Servers automatisch – außer du hast ihn bewusst gestoppt. Die Alternative always ignoriert sogar ein manuelles docker stop und startet den Container beim nächsten Daemon-Start erneut. Für temporäre oder einmalige Dienste genügt no, die Vorgabe.

Ordnerstruktur: Ein Stack, ein Verzeichnis

Eine klare Struktur macht dein Homelab durchsuchbar und backuppbar. Bewährt hat sich pro Dienst ein eigenes Verzeichnis mit allem, was dazugehört:


docker/
├── pihole/
│   ├── docker-compose.yml
│   ├── .env
│   └── config/
├── nextcloud/
│   ├── docker-compose.yml
│   ├── .env
│   └── config/
└── traefik/
    ├── docker-compose.yml
    └── config/

Jeder Stack besitzt seine eigene docker-compose.yml, seine .env und einen config/-Ordner für gebundene Dateien. So lässt sich ein Dienst einzeln starten, sichern oder umziehen – und das gesamte Verzeichnis docker/ versionierst du als Ganzes mit Git. Das ist zugleich dein Backup für die Konfiguration.

Vollständiges Beispiel: alles zusammen

Die Best Practices in einem kompakten Stack, der eine Webanwendung mit Datenbank verbindet:


services:
  app:
    image: mein-dienst:latest
    container_name: mein-dienst
    restart: unless-stopped
    environment:
      - DB_HOST=db
      - DB_PASSWORD=${DB_PASSWORD}
    env_file:
      - .env
    volumes:
      - ./config:/config
    networks:
      - internal
    depends_on:
      db:
        condition: service_healthy

  db:
    image: mariadb:11
    container_name: mein-dienst-db
    restart: unless-stopped
    environment:
      MARIADB_ROOT_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - internal
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect"]
      interval: 30s
      timeout: 10s
      retries: 3

volumes:
  db_data:

networks:
  internal:

Start und Verwaltung bleiben dabei denkbar einfach:


docker compose up -d      # Stack starten
docker compose ps         # Status anzeigen
docker compose down       # Stack stoppen
docker compose pull       # Images aktualisieren

Fazit

Saubere Docker-Compose-Dateien sind keine Kosmetik, sondern die Grundlage eines wartbaren Homelabs: Sprechende Namen, persistente Volumes, klar getrennte Netzwerke und ausgelagerte Secrets machen jeden Stack verständlich und sicher. Healthchecks und Restart-Policies sorgen dafür, dass deine Dienste Ausfälle selbst überstehen – und eine konsequente Ordnerstruktur macht das gesamte Setup reproduzierbar und einfach zu sichern. Wer diese Best Practices von Anfang an beherzigt, spart sich später Stunden an Fehlersuche.