Docker Compose Praxis-Guide für Webentwickler

Praktischer Guide zu Docker Compose für Webentwickler mit Best Practices, Netzwerkkonfiguration und Fallstricken

Docker Compose Praxis-Guide für Webentwickler

Mein Portfolio läuft nicht in Docker. Drei Node-Services und ein nginx davor sind kein Use-Case für Container-Orchestrierung — Systemd erledigt das mit weniger Komplexität und besserer Logaggregation. Aber sobald ein Projekt aus mehreren Services besteht, vor allem in der Entwicklung, ist Docker Compose eines der wenigen Tools, das ich praktisch nie infrage stelle.

Was Compose so brauchbar macht, ist nicht Magie, sondern Erwartbarkeit: eine YAML, ein docker compose up, identisches Setup auf jedem Rechner. Was früher ein README mit "Du brauchst Postgres 14, Redis 7 und Node 20" war, ist jetzt versionierter Code.

Was Compose nicht ist

Compose ist kein Production-Orchestrator. Wer ein Cluster will, nimmt Kubernetes oder Nomad. Compose läuft auf einer Maschine, kennt keinen Failover, kein Service-Discovery zwischen Hosts, kein Rolling Update mit Health-Checks. Ich kenne genug Projekte, die Compose im Production-Setup haben — und es funktioniert, solange ein einzelner Server reicht. Sobald nicht, wird's schmerzhaft.

In der Entwicklung dagegen: kaum etwas, das ich nicht damit löse.

Minimal-Setup mit Postgres und einer App

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: dev
      POSTGRES_DB: app
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  app:
    build: .
    depends_on:
      - db
    environment:
      DATABASE_URL: postgres://postgres:dev@db:5432/app
    ports:
      - "3000:3000"
    volumes:
      - ./src:/app/src

volumes:
  pgdata:

Drei Sachen, die hier wichtig sind und in vielen Tutorials ungesagt bleiben:

Named Volumes statt bind mounts für Daten. pgdata als named volume bedeutet, dass Docker die Pfade verwaltet und ich beim Backup docker run --rm -v pgdata:/data nutzen kann. Bind mounts auf ./pgdata führen regelmäßig zu Permission-Issues zwischen Host und Container.

Bind Mount für Code, named Volume für Daten. Source-Code will ich live editieren (./src:/app/src), Datenbankdateien will ich nicht im Repo haben.

depends_on ist ein Startup-Hint, kein Health-Check. Postgres ist beim Start der App nicht garantiert ready. Wer das ernst meint, nutzt condition: service_healthy oder die App muss Connection-Retries haben.

Netzwerke verstehen

Compose erstellt automatisch ein Bridge-Netzwerk, in dem alle Services per Servicenamen erreichbar sind. db:5432 funktioniert nur, weil db der Servicename ist und Docker einen internen DNS-Eintrag dafür anlegt.

Wer mehrere Compose-Projekte auf demselben Host fährt, kann sie über externe Netzwerke verbinden:

networks:
  shared:
    external: true

Das nutze ich, wenn nginx-proxy in einem eigenen Compose-File läuft und mehrere App-Stacks sich daran anhängen.

Häufige Fallstricke

docker compose down ohne -v löscht keine Volumes. Gut so. Aber wenn man Daten resetten will: docker compose down -v. Andersherum bei Production-Daten: zweimal lesen.

Environment-Variablen aus .env werden automatisch geladen, wenn die Datei im Compose-Directory liegt. Praktisch, aber führt dazu, dass man bei mehreren Projekten versehentlich Variablen vermischt. Ich nutze deshalb meist explizit env_file: ./.env.dev.

Beim Build-Cache gilt: alles was sich häufig ändert, kommt nach allem was selten ändert. Erst COPY package.json, dann RUN npm install, dann COPY . — sonst rebuildet Docker bei jedem Source-Change die Dependencies.

Wo ich Compose trotzdem für Production nutze

Ein einzelnes Side-Project, das auf einem 5-Euro-VPS lebt und keine Hochverfügbarkeit braucht. Compose-File checked in, docker compose up -d --build per CI auf Push. Wenn der Server abraucht, ist es ein Side-Project — Recovery ist git clone und einmal docker compose up. Genau dafür ist es brauchbar.

Sobald das Setup ernsthafter wird, wandert es zu Systemd-Units oder echtem Orchestration. Die Grenze hat sich für mich bei "ein Service auf einem Server" gefestigt.