Docker Compose Tutorial: Multi-Container-Anwendungen einfach orchestrieren

Docker Compose ist das Werkzeug der Wahl, wenn Sie mehrere Docker-Container als zusammenhängende Anwendung betreiben möchten. In diesem Tutorial lernen Sie, wie Sie mit einer einfachen YAML-Datei komplexe Multi-Container-Setups definieren, starten und verwalten.

Was ist Docker Compose?

Docker Compose ist ein Tool zum Definieren und Ausführen von Multi-Container-Docker-Anwendungen. Statt jeden Container einzeln mit langen docker run-Befehlen zu starten, beschreiben Sie Ihre gesamte Anwendungsinfrastruktur in einer docker-compose.yml-Datei. Ein einziger Befehl startet dann alle Services, konfiguriert Netzwerke und mountet Volumes.

Typische Anwendungsfälle sind Entwicklungsumgebungen mit Datenbank, Cache und Webserver, Microservice-Architekturen sowie lokale Repliken von Production-Setups zum Testen.

Installation von Docker Compose

Seit Docker Desktop 3.4 und Docker Engine 20.10.13 ist Docker Compose V2 als docker compose (ohne Bindestrich) integriert. Die separate Installation ist in den meisten Fällen nicht mehr nötig.

Prüfen der Installation

# Docker Compose Version prüfen (V2 Syntax)
docker compose version

# Ausgabe: Docker Compose version v2.24.0 (oder höher)

# Alternative: Alte V1 Syntax (veraltet)
docker-compose --version

Hinweis: Docker Compose V2 verwendet „docker compose“ (mit Leerzeichen) statt „docker-compose“ (mit Bindestrich). Die V2-Syntax ist schneller und bietet zusätzliche Features. Verwenden Sie in neuen Projekten immer V2.

Die docker-compose.yml verstehen

Die docker-compose.yml ist das Herzstück jeder Compose-Anwendung. Sie definiert alle Services, Netzwerke und Volumes in einer deklarativen YAML-Syntax. Beginnen wir mit einem grundlegenden Beispiel einer Web-Anwendung mit Datenbank:

# docker-compose.yml
version: '3.8'

services:
  # Web-Anwendung (Node.js)
  app:
    build: ./app
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgres://user:password@db:5432/myapp
    volumes:
      - ./app:/usr/src/app
      - /usr/src/app/node_modules
    depends_on:
      - db
      - redis

  # PostgreSQL Datenbank
  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=myapp
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  # Redis Cache
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  postgres_data:

Die wichtigsten Konfigurationsoptionen

OptionBeschreibungBeispiel
imageDocker Image aus Registrypostgres:16-alpine
buildPfad zum Dockerfile./app oder {context: ., dockerfile: Dockerfile.dev}
portsPort-Mapping Host:Container„3000:3000“
volumesDatei-/Verzeichnis-Mounts./data:/app/data
environmentUmgebungsvariablenNODE_ENV=production
depends_onStartreihenfolge definieren[db, redis]
networksNetzwerk-Zugehörigkeit[frontend, backend]
restartNeustart-Policyunless-stopped, always

Wichtige Docker Compose Befehle

# Alle Services starten (im Hintergrund)
docker compose up -d

# Services starten und neu bauen
docker compose up -d --build

# Logs aller Services anzeigen
docker compose logs -f

# Logs eines bestimmten Services
docker compose logs -f app

# Laufende Container anzeigen
docker compose ps

# Services stoppen
docker compose stop

# Services stoppen und Container entfernen
docker compose down

# Alles entfernen (inkl. Volumes und Images)
docker compose down -v --rmi all

# In einen Container einsteigen
docker compose exec app sh

# Einmaligen Befehl ausführen
docker compose run --rm app npm test

# Services skalieren
docker compose up -d --scale app=3

Umgebungsvariablen und .env-Dateien

Sensible Daten wie Passwörter oder API-Keys sollten nicht direkt in der docker-compose.yml stehen. Verwenden Sie stattdessen .env-Dateien oder externe Umgebungsvariablen.

# .env Datei im gleichen Verzeichnis wie docker-compose.yml
POSTGRES_USER=myuser
POSTGRES_PASSWORD=supersecret123
POSTGRES_DB=production_db
NODE_ENV=production
API_KEY=sk-abc123xyz
# docker-compose.yml mit Variablen-Referenzen
version: '3.8'

services:
  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}

  app:
    build: ./app
    environment:
      - NODE_ENV=${NODE_ENV}
      - DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
      - API_KEY=${API_KEY}

Praxis-Tipp: Fügen Sie .env immer zu Ihrer .gitignore hinzu. Erstellen Sie eine .env.example mit Platzhaltern als Vorlage für andere Entwickler.

Netzwerke in Docker Compose

Docker Compose erstellt automatisch ein Netzwerk für alle Services in einer Compose-Datei. Die Services können sich gegenseitig über ihren Service-Namen erreichen. Für komplexere Setups können Sie eigene Netzwerke definieren:

version: '3.8'

services:
  frontend:
    image: nginx:alpine
    networks:
      - frontend_network

  backend:
    build: ./api
    networks:
      - frontend_network
      - backend_network

  db:
    image: postgres:16-alpine
    networks:
      - backend_network

networks:
  frontend_network:
    driver: bridge
  backend_network:
    driver: bridge
    internal: true  # Kein Zugriff von außen

In diesem Setup kann das Frontend nur mit dem Backend kommunizieren. Die Datenbank ist nur vom Backend erreichbar und vom Internet isoliert.

Volumes und Datenpersistenz

Docker Container sind flüchtig. Wenn Sie sie löschen, gehen alle Daten verloren. Volumes sorgen für Datenpersistenz und ermöglichen das Teilen von Daten zwischen Containern.

version: '3.8'

services:
  db:
    image: postgres:16-alpine
    volumes:
      # Named Volume (empfohlen für Datenbanken)
      - postgres_data:/var/lib/postgresql/data
      # Bind Mount für Initialisierungsskripte
      - ./init-scripts:/docker-entrypoint-initdb.d:ro

  app:
    build: ./app
    volumes:
      # Bind Mount für Live-Reload in Entwicklung
      - ./app/src:/usr/src/app/src
      # Anonymous Volume für node_modules
      - /usr/src/app/node_modules

volumes:
  postgres_data:
    driver: local
  # Externes Volume (muss vorher erstellt werden)
  shared_data:
    external: true

Volume-Typen im Überblick

TypSyntaxVerwendung
Named Volumevolume_name:/pathDatenbanken, persistente Daten
Bind Mount./host/path:/container/pathEntwicklung, Konfigurationsdateien
Anonymous Volume/container/pathTemporäre Daten, node_modules

Healthchecks und Dependencies

depends_on startet Services in der richtigen Reihenfolge, wartet aber nicht darauf, dass ein Service wirklich bereit ist. Healthchecks lösen dieses Problem:

version: '3.8'

services:
  app:
    build: ./app
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: secret
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
      start_period: 10s

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 3

Praxisbeispiel: WordPress mit MySQL und Nginx

Ein vollständiges Beispiel für ein WordPress-Setup mit separatem Webserver:

version: '3.8'

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - wordpress_data:/var/www/html:ro
    depends_on:
      - wordpress
    restart: unless-stopped

  wordpress:
    image: wordpress:6-php8.2-fpm
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: ${WP_DB_USER}
      WORDPRESS_DB_PASSWORD: ${WP_DB_PASSWORD}
      WORDPRESS_DB_NAME: ${WP_DB_NAME}
    volumes:
      - wordpress_data:/var/www/html
      - ./wp-content/uploads:/var/www/html/wp-content/uploads
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${WP_DB_NAME}
      MYSQL_USER: ${WP_DB_USER}
      MYSQL_PASSWORD: ${WP_DB_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  wordpress_data:
  mysql_data:

Multi-Stage Compose: Development und Production

Mit Compose Profiles und Override-Dateien können Sie verschiedene Umgebungen definieren:

# docker-compose.yml (Basis-Konfiguration)
version: '3.8'

services:
  app:
    build: ./app
    environment:
      - NODE_ENV=production

  db:
    image: postgres:16-alpine
# docker-compose.override.yml (automatisch für Development geladen)
version: '3.8'

services:
  app:
    build:
      context: ./app
      dockerfile: Dockerfile.dev
    volumes:
      - ./app:/usr/src/app
    environment:
      - NODE_ENV=development
      - DEBUG=true
    ports:
      - "3000:3000"
      - "9229:9229"  # Debug-Port

  db:
    ports:
      - "5432:5432"
# docker-compose.prod.yml (explizit für Production)
version: '3.8'

services:
  app:
    restart: always
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 2G

  db:
    restart: always
    volumes:
      - /mnt/backup:/backup
# Starten mit verschiedenen Konfigurationen
# Development (lädt automatisch override)
docker compose up

# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Best Practices für Docker Compose

  • Immer konkrete Image-Tags verwenden: postgres:16-alpine statt postgres:latest
  • Sensible Daten in .env-Dateien: Niemals Passwörter in docker-compose.yml committen
  • Named Volumes für persistente Daten: Datenbanken immer mit Named Volumes
  • Healthchecks definieren: Für zuverlässige Startup-Reihenfolge
  • restart: unless-stopped: Für Production-Services aktivieren
  • Netzwerke für Isolation: Datenbanken nicht von außen erreichbar machen
  • Resource-Limits setzen: In Production CPU und Memory begrenzen

Fazit

Docker Compose vereinfacht die Verwaltung komplexer Multi-Container-Anwendungen erheblich. Mit einer einzigen YAML-Datei definieren Sie Ihre gesamte Infrastruktur, von der Datenbank über den Cache bis zum Webserver. Die deklarative Konfiguration macht Setups reproduzierbar und versionierbar. Beginnen Sie mit einfachen Setups und erweitern Sie diese schrittweise um Healthchecks, Netzwerke und Umgebungs-spezifische Konfigurationen.

Docker-Setup für Ihr Projekt?