Die Grundstruktur einer docker-compose.yml - was wirklich reingehört

Die Grundstruktur einer docker-compose.yml - was wirklich reingehört

Eine docker-compose.yml zu schreiben ist schnell getan — aber viele Beispiele im Netz enthalten veraltete Felder, fehlende Konfiguration und häufige Fallen. Hier ist was tatsächlich in eine moderne Compose-Datei gehört.

Minimales, funktionsfähiges Beispiel

services:
  web:
    image: nginx:1.25.3
    container_name: mein-web
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro

Das reicht für die meisten einfachen Anwendungen.

Das version-Feld ist obsolet

In älteren Beispielen steht ganz oben:

version: "3.9"   # veraltet — nicht mehr notwendig

Das version-Feld ist seit Docker Compose v2 deprecated und wird ignoriert. Einfach weglassen.

image vs. build

services:
  app:
    # Fertiges Image aus Registry
    image: myregistry/myapp:2.1.0

  app-dev:
    # Eigenes Dockerfile bauen
    build:
      context: .
      dockerfile: Dockerfile.dev

restart-Policy

restart: no              # nie neu starten (Standard)
restart: always          # immer — auch nach manuell docker stop
restart: unless-stopped  # immer, außer wenn manuell gestoppt
restart: on-failure      # nur bei Exit Code != 0

unless-stopped ist für die meisten Produktions-Dienste die richtige Wahl.

Ports: die YAML-Falle

# Gefährlich: YAML interpretiert 8080:80 als Oktalzahl!
ports:
  - 8080:80     # Fehler bei bestimmten Ports (z.B. 0600:80)

# Immer Strings verwenden
ports:
  - "8080:80"
  - "127.0.0.1:8080:80"   # nur auf localhost erreichbar

volumes: named vs. bind

services:
  db:
    image: postgres:16
    volumes:
      # Named Volume (von Docker verwaltet)
      - db-data:/var/lib/postgresql/data
      # Bind Mount (Host-Pfad direkt)
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro

volumes:
  db-data:   # Named Volume muss hier deklariert werden

environment vs. env_file

services:
  app:
    # Direkt in der Compose-Datei (für nicht-sensitive Werte)
    environment:
      - APP_ENV=production
      - PORT=8080

    # Aus Datei laden (für Secrets, .env nicht ins Git!)
    env_file:
      - .env

Netzwerke

Ohne Konfiguration erstellt Compose ein Standard-Bridge-Netzwerk. Alle Services in der Compose-Datei können sich über ihren Service-Namen erreichen.

services:
  web:
    networks:
      - frontend
      - backend
  db:
    networks:
      - backend

networks:
  frontend:
  backend:

Vollständiges Beispiel

services:
  web:
    image: nginx:1.25.3
    container_name: mein-web
    restart: unless-stopped
    ports:
      - "127.0.0.1:80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app
    networks:
      - frontend

  app:
    build: .
    container_name: mein-app
    restart: unless-stopped
    env_file: .env
    environment:
      - NODE_ENV=production
    volumes:
      - app-logs:/app/logs
    networks:
      - frontend
      - backend

  db:
    image: postgres:16-alpine
    container_name: mein-db
    restart: unless-stopped
    env_file: .env.db
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - backend

volumes:
  db-data:
  app-logs:

networks:
  frontend:
  backend: