Docker · Compose · Sidecars · Init-Container
Sidecars, Utility-Container
und Einmal-Jobs im Alltag

Docker Compose kann mehr als Hauptservices starten. Sidecar-Container liefern Logs weiter und exportieren Metriken. Utility-Container stellen CLI-Tools bereit ohne lokale Installation. Init-Container laufen vor dem Hauptservice und führen Datenbank-Migrationen aus. Einmal-Jobs erledigen Wartungsaufgaben auf Anforderung. Dieser Beitrag erklärt alle vier Muster mit dem richtigen Werkzeug für den richtigen Zweck.

13 Min. Lesezeit Sidecars · Init-Container · Utility-Container · Einmal-Jobs Docker Compose v2 · depends_on · restart-Policies

1. Die vier Container-Muster und wann jedes passt

Docker Compose wird häufig auf eine simple Weise verwendet: Hauptservice definieren, Abhängigkeiten wie Datenbank und Cache dazu, fertig. Das deckt aber nur einen Teil der Realität ab. In produktiven Stacks gibt es immer Querschnittsaufgaben – Logging, Monitoring, Datenbank-Migrationen, Wartungsscripts – die keinem einzelnen Service zuzuordnen sind. Hierfür gibt es vier klar unterschiedliche Muster: Sidecar-Container, Utility-Container, Init-Container und Einmal-Jobs. Jedes hat seinen spezifischen Zweck und seine spezifische Konfiguration in Docker Compose.

Die Abgrenzung ist wichtig, weil die falsche Wahl zu Problemen führt. Ein Wartungs-Script als Teil des Hauptservices zu betreiben bedeutet, dass der Hauptservice bei jedem Script-Aufruf nicht vollständig sauber gestartet ist. Eine Datenbank-Migration als manuellen Schritt außerhalb von Docker zu betreiben bricht die Idee des reproduzierbaren Stack-Starts. Ein Sidecar-Container für eine Aufgabe, die nur einmal ausgeführt werden soll, läuft sinnlos unbegrenzt weiter. Das Verständnis der vier Muster ist Voraussetzung für saubere, beherrschbare Compose-Stacks.

Der Kontext bestimmt die Wahl: Läuft die Aufgabe kontinuierlich parallel zum Hauptservice? Dann ist es ein Sidecar-Container. Wird sie einmalig vor dem Start ausgeführt? Init-Container. Wird sie auf Anforderung von außen ausgeführt? Utility-Container. Wird sie einmalig im Kontext des laufenden Stacks ausgeführt und soll dann enden? Einmal-Job. Diese vier Fragen klären die Wahl in fast allen Fällen.

2. Sidecar-Container: Log-Shipping und Metrics-Export

Ein Sidecar-Container läuft als ständiger Begleiter eines Hauptservices. Er teilt Netzwerk oder Volumes mit dem Hauptservice und ergänzt dessen Funktionalität, ohne dessen Code zu verändern. Das klassische Beispiel ist ein Log-Shipper: Der Hauptservice schreibt Logs in ein geteiltes Volume, der Sidecar-Container liest diese Logs und leitet sie an ein zentrales Logging-System weiter. Der Hauptservice muss nichts von Elasticsearch oder Loki wissen – er schreibt einfach Dateien.

Ein weiteres häufiges Sidecar-Container-Muster ist der Metrics-Exporter. Eine PHP-FPM-Instanz oder ein Nginx-Server hat möglicherweise keinen nativen Prometheus-Exporter. Ein Sidecar-Container wie nginx-prometheus-exporter oder php-fpm-exporter verbindet sich mit dem Status-Endpoint des Hauptservices und stellt diese Metriken in einem Format bereit, das Prometheus scrapen kann. Der Hauptservice bleibt unverändert, das Monitoring ist trotzdem vollständig.


# docker-compose.yml — Sidecar pattern for log shipping and metrics
services:
  app:
    image: ghcr.io/myorg/myapp:latest
    volumes:
      # Shared log volume between main service and sidecar
      - app-logs:/var/log/app
    networks:
      - internal

  # Sidecar: forwards app logs to Loki
  log-shipper:
    image: grafana/promtail:latest
    restart: unless-stopped
    volumes:
      - app-logs:/var/log/app:ro          # read-only access to app logs
      - ./promtail.yml:/etc/promtail/config.yml:ro
    depends_on:
      app:
        condition: service_started
    networks:
      - internal

  # Sidecar: exports PHP-FPM metrics for Prometheus
  php-fpm-exporter:
    image: hipages/php-fpm_exporter:latest
    restart: unless-stopped
    environment:
      PHP_FPM_SCRAPE_URI: "tcp://app:9000/status"
    ports:
      - "9253:9253"
    depends_on:
      app:
        condition: service_healthy
    networks:
      - internal

volumes:
  app-logs:

networks:
  internal:

3. Utility-Container: CLI-Tools ohne lokale Installation

Utility-Container lösen ein konkretes Problem in Entwicklungsteams: Nicht jeder Entwickler hat dieselben Versionen von PHP, Node.js, Composer oder anderen Tools lokal installiert. Utility-Container kapseln diese Tools in einem Container und stellen sie über docker run oder über Wrapper-Scripts zur Verfügung. Das Team verwendet immer dieselbe Tool-Version, unabhängig vom lokalen System, und es wird nichts global installiert.

Utility-Container laufen nicht dauerhaft – sie werden mit einem Befehl gestartet, führen diesen aus und beenden sich dann. Das Muster in Docker Compose: ein Service ohne restart-Policy und mit profiles, sodass er beim normalen docker compose up nicht gestartet wird, aber über docker compose run composer install gezielt aufgerufen werden kann. Alternativ werden sie direkt mit docker run --rm aufgerufen, ohne in der Compose-Datei definiert zu sein.

Das Mark-Shust-Magento-Docker-Setup verwendet dieses Muster konsequent: Die Wrapper-Scripts in bin/ rufen Tools im Container auf, ohne dass sie lokal installiert sein müssen. bin/composer install startet einen Utility-Container mit der richtigen Composer-Version, führt den Befehl im Projektverzeichnis aus und beendet sich. Der Entwickler muss keine PHP-Versionen oder Composer-Versionen lokal verwalten.

4. Init-Container: Vorbereitungen vor dem Hauptservice

Init-Container lösen das Problem der Startreihenfolge: Bevor der Hauptservice startet, muss die Datenbank nicht nur erreichbar sein, sondern auch das richtige Schema haben. Docker Compose bietet mit depends_on und Healthchecks die Möglichkeit, Startreihenfolgen zu definieren – aber nur bis zu einem bestimmten Grad. Ein Init-Container als separater Service, der einen Datenbankmigrationsbefehl ausführt und sich dann mit Exit-Code 0 beendet, erlaubt es, komplexe Vorbereitungsschritte sauber zu modellieren.

Der Trick liegt in der restart: "no"-Policy kombiniert mit der Bedingung condition: service_completed_successfully in depends_on. Der Hauptservice startet nur dann, wenn der Init-Container erfolgreich abgeschlossen hat – nicht nur gestartet wurde. Das ist der entscheidende Unterschied zu condition: service_started: Letzteres wartet nur darauf, dass der Container läuft, nicht darauf, dass er seine Aufgabe erfolgreich erledigt hat und beendet wurde.


# docker-compose.yml — Init container for database migration before app start
services:
  db:
    image: mysql:8.4
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: appdb
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-prootpass"]
      interval: 5s
      timeout: 3s
      retries: 10
    volumes:
      - db-data:/var/lib/mysql

  # Init container: runs migration, exits 0 on success
  db-migrate:
    image: ghcr.io/myorg/myapp:latest
    command: ["php", "artisan", "migrate", "--force"]
    restart: "no"                          # run once, do not restart on exit
    depends_on:
      db:
        condition: service_healthy         # wait for DB to be ready, not just started
    environment:
      DB_HOST: db
      DB_DATABASE: appdb
      DB_PASSWORD: rootpass

  app:
    image: ghcr.io/myorg/myapp:latest
    restart: unless-stopped
    depends_on:
      db-migrate:
        condition: service_completed_successfully  # wait for migration to finish
      db:
        condition: service_healthy
    ports:
      - "8080:8080"

volumes:
  db-data:

5. Einmal-Jobs: Wartungsaufgaben auf Anforderung

Einmal-Jobs unterscheiden sich von Init-Containern dadurch, dass sie nicht automatisch beim Stack-Start ausgeführt werden, sondern auf Anforderung. Ein typisches Beispiel ist ein Datenbankdump, ein Cache-Warm-Up, eine Datenimport-Routine oder die Ausführung von Fixtures für Testdaten. Diese Aufgaben sollen im Kontext des laufenden Stacks ausgeführt werden – mit Zugang zu denselben Netzwerken und Umgebungsvariablen – aber nur wenn explizit angefordert.

Das Werkzeug in Docker Compose für Einmal-Jobs ist docker compose run --rm. Dieser Befehl startet einen neuen Container für den angegebenen Service, verbindet ihn mit dem Stack-Netzwerk, führt den Befehl aus und entfernt den Container danach automatisch (--rm). Im Gegensatz zu docker compose exec, das einen Befehl in einem bereits laufenden Container ausführt, startet docker compose run einen völlig neuen Container. Das ist wichtig für Jobs, die einen definierten Startzustand benötigen.

Mit profiles in Docker Compose können Einmal-Job-Services definiert werden, die beim normalen docker compose up nicht gestartet werden, aber über ihr spezifisches Profil aufgerufen werden können. Das hält die Standard-Stack-Konfiguration sauber und vermeidet, dass Job-Container versehentlich dauerhaft gestartet werden.

6. depends_on und Healthchecks als Startreihenfolge

Die Startreihenfolge in Docker Compose ist ohne explizite Konfiguration nicht deterministisch – alle Services starten gleichzeitig. depends_on ist die korrekte Lösung, aber in seiner einfachsten Form wartet es nur darauf, dass der abhängige Container gestartet ist, nicht dass er bereit ist. Ein Datenbankcontainer, der gestartet aber noch nicht initialisiert ist, wird von einer Anwendung, die sofort eine Datenbankverbindung aufbaut, als nicht verfügbar wahrgenommen.

Healthchecks schließen diese Lücke. Mit depends_on: condition: service_healthy wartet Docker Compose, bis der Healthcheck des abhängigen Services erfolgreich durchläuft. Der Healthcheck muss im Service-Definition des abhängigen Containers konfiguriert sein. Für MySQL ist der Healthcheck mysqladmin ping, für PostgreSQL pg_isready, für Redis redis-cli ping. Diese Befehle signalisieren erst Erfolg, wenn der Dienst tatsächlich bereit für Verbindungen ist.

Für Sidecar-Container und Init-Container sind die Bedingungen service_started, service_healthy und service_completed_successfully in Kombination das mächtige Werkzeug. Ein Sidecar-Container kann mit service_started starten, weil er tolerant gegenüber kurzen Unterbrechungen des Hauptservices ist. Ein Init-Container muss mit service_healthy auf die Datenbank warten. Der Hauptservice muss mit service_completed_successfully auf den Init-Container warten.

7. restart-Policies für langlebige vs. kurzlebige Container

Die restart-Policy ist ein häufig unterschätzter Konfigurationsparameter in Docker Compose. Für Sidecar-Container, die dauerhaft laufen sollen, ist restart: unless-stopped die richtige Wahl – sie werden nach Fehlern und System-Neustarts neu gestartet, aber nicht wenn sie manuell gestoppt wurden. Für Init-Container ist restart: "no" zwingend – ein Init-Container, der nach dem erfolgreichen Abschluss neu gestartet wird, führt die Migration erneut aus.

Für Einmal-Jobs ist die restart-Policy irrelevant, weil sie via docker compose run gestartet werden und das --rm-Flag sie nach Abschluss automatisch entfernt. Sidecar-Container mit restart: on-failure werden bei Fehlern neu gestartet, aber nicht bei normalem Beenden. Das ist sinnvoll für Sidecars, die sich beenden, wenn der Hauptservice nicht mehr erreichbar ist – sie sollen dann warten und erneut versuchen, nicht endlos weiterlaufen.

Die kombinierte Konfiguration aus restart-Policy und depends_on-Bedingung definiert das vollständige Lebenszyklusmodell eines Containers im Stack. Ein Init-Container mit restart: "no" und ein Hauptservice mit depends_on: condition: service_completed_successfully bilden zusammen ein zuverlässiges, deterministisches Start-Protokoll, das auch nach Stack-Neustarts korrekt funktioniert.

8. Mustervergleich: Sidecar, Utility, Init und Job

Die vier Muster unterscheiden sich in Lebensdauer, Auslöser, Zweck und Konfiguration. Die folgende Tabelle fasst die wesentlichen Unterschiede zusammen.

Muster Lebensdauer Auslöser restart-Policy
Sidecar Dauerhaft (wie Hauptservice) Stack-Start unless-stopped
Utility-Container Kurzlebig (Befehlsdauer) Manueller Aufruf no (oder --rm)
Init-Container Einmalig (vor Hauptservice) Stack-Start no
Einmal-Job Kurzlebig (Job-Dauer) docker compose run no + --rm

Die häufigste Verwechslung ist zwischen Init-Container und Einmal-Job. Ein Init-Container ist Teil des automatischen Stack-Starts und blockiert den Hauptservice, bis er erfolgreich abgeschlossen ist. Ein Einmal-Job ist ein manueller Eingriff, der im Kontext des laufenden Stacks ausgeführt wird. Die Grenze verschwimmt in der Praxis: Eine erste Datenbank-Initialisierung ist ein Init-Container, ein nachträglicher Datenimport ist ein Einmal-Job.

9. Praxisbeispiel: Magento mit Init-Container und Sidecars

Ein Magento-2-Stack mit Hyvä-Theme bietet ein reales Beispiel für alle vier Muster. Der Init-Container führt beim Stack-Start bin/magento setup:upgrade und setup:di:compile aus, wenn sich die Magento-Version geändert hat. Der Sidecar-Container ist ein Varnish-Cache-Warmer, der kontinuierlich URLs aufruft und den Varnish-Cache warm hält. Ein zweiter Sidecar-Container exportiert Redis-Metriken für das Monitoring. Einmal-Jobs erledigen Cache-Flush, Reindex und Datenbankdumps auf Anforderung.

Die depends_on-Kette im Magento-Stack ist länger als in einfacheren Projekten: MySQL muss gesund sein, bevor Redis gestartet wird. Redis muss gesund sein, bevor Elasticsearch gestartet wird. Elasticsearch muss gesund sein, bevor der Init-Container die Magento-Einrichtung ausführt. Der PHP-FPM-Service wartet auf den erfolgreich abgeschlossenen Init-Container. Nginx wartet auf PHP-FPM. Der Sidecar-Container für Cache-Warming wartet auf Nginx. Diese Kette ist mit depends_on und Healthchecks vollständig deklarativ konfigurierbar.


# Simplified Magento stack with init container and sidecar
services:
  mysql:
    image: mysql:8.4
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-prootpass"]
      interval: 5s
      retries: 12

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 3s
      retries: 10
    depends_on:
      mysql:
        condition: service_healthy

  # Init container: runs setup:upgrade if needed
  magento-setup:
    image: ghcr.io/myorg/magento:2.4.8
    command: ["bin/magento", "setup:upgrade", "--keep-generated"]
    restart: "no"
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy

  phpfpm:
    image: ghcr.io/myorg/magento:2.4.8
    restart: unless-stopped
    depends_on:
      magento-setup:
        condition: service_completed_successfully

  # Sidecar: Redis metrics for Prometheus
  redis-exporter:
    image: oliver006/redis_exporter:latest
    restart: unless-stopped
    environment:
      REDIS_ADDR: redis://redis:6379
    depends_on:
      redis:
        condition: service_healthy

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    depends_on:
      phpfpm:
        condition: service_started
    ports:
      - "8080:80"

Mironsoft

Docker Compose-Stacks, Sidecar-Muster und Container-Orchestrierung

Docker Compose-Stacks die korrekt starten?

Wir strukturieren Compose-Stacks mit korrekten Startreihenfolgen, Init-Containern, Sidecar-Patterns und Einmal-Jobs – damit jeder Stack-Start deterministisch und zuverlässig ist.

Compose-Review

Startreihenfolge, Healthchecks, restart-Policies und Service-Abhängigkeiten analysieren

Sidecar-Design

Log-Shipping, Metrics-Export und Cache-Warming als Sidecar-Container konfigurieren

Init-Container

Datenbank-Migrationen und Setup-Schritte als zuverlässige Init-Container modellieren

10. Zusammenfassung

Die vier Container-Muster – Sidecar-Container, Utility-Container, Init-Container und Einmal-Jobs – decken gemeinsam alle Querschnittsaufgaben in einem Docker Compose-Stack ab. Sidecar-Container laufen dauerhaft als Begleiter des Hauptservices und ergänzen dessen Funktionalität ohne Code-Änderungen. Utility-Container kapseln CLI-Tools und laufen auf Anforderung. Init-Container bereiten den Stack vor dem Hauptservice-Start vor. Einmal-Jobs erledigen Wartungsaufgaben im Kontext des laufenden Stacks.

Die korrekte Konfiguration von restart-Policies, depends_on-Bedingungen und Healthchecks ist entscheidend dafür, dass diese Muster zuverlässig funktionieren. Ein Init-Container mit falscher restart-Policy oder ohne service_completed_successfully-Bedingung am Hauptservice führt zu Race Conditions beim Stack-Start. Die Investition in korrekt konfigurierte Startreihenfolgen zahlt sich bei jedem Stack-Neustart aus.

Sidecars, Utility-Container und Jobs — Das Wichtigste auf einen Blick

Sidecar-Container

Dauerhafter Begleiter. restart: unless-stopped. Typisch: Log-Shipper, Metrics-Exporter, Cache-Warmer. Teilt Netzwerk oder Volume mit Hauptservice.

Init-Container

Einmalig beim Stack-Start. restart: "no". Hauptservice mit depends_on condition: service_completed_successfully. Für DB-Migrationen und Setup-Schritte.

Utility-Container

Auf Anforderung via docker compose run --rm. CLI-Tools in definierter Version. Kein lokaler Installationsaufwand. Profile für Compose-Integration.

depends_on + Healthchecks

service_started, service_healthy, service_completed_successfully – die drei Bedingungen für präzise Startreihenfolgen. Healthchecks im abhängigen Service definieren.

11. FAQ: Sidecars, Utility-Container und Jobs

1Was ist ein Sidecar-Container?
Dauerhafter Begleiter des Hauptservices. Teilt Netzwerk/Volume. Log-Shipper, Metrics-Exporter. Kein Eingriff in Hauptservice-Code.
2Init-Container vs. Einmal-Job?
Init-Container: automatisch beim Stack-Start, blockiert Hauptservice. Einmal-Job: manuell via docker compose run, im laufenden Stack.
3Init-Container kein Neustart?
restart: "no" setzen. Ohne diese Konfiguration startet Docker Compose den Init-Container nach Abschluss automatisch neu.
4depends_on auf Init-Container-Abschluss?
condition: service_completed_successfully. Wartet auf Exit-Code 0, nicht nur auf Container-Start.
5compose run vs. compose exec?
run: neuer Container, frischer Zustand. exec: in laufendem Container. run für Jobs, exec für Debugging.
6Sidecar-Absturz beeinflusst Hauptservice?
Nein. Sidecar hat keine Kontrolle über Hauptservice-Prozess. Hauptservice läuft weiter. Sidecar mit restart: unless-stopped startet neu.
7depends_on ohne Healthcheck nicht ausreichend?
depends_on service_started wartet nur auf Container-Start. Healthcheck signalisiert erst wenn Dienst wirklich bereit ist – z.B. mysql mysqladmin ping.
8Utility-Container ohne Compose?
docker run --rm --network projektname_default -v $(pwd):/app composer:2.8 install. Verbindet sich mit Stack-Netzwerk, wird nach Abschluss entfernt.
9Compose-Datei mit vielen Services übersichtlich?
Profiles nutzen: monitoring, tools, debug. docker compose --profile monitoring up startet Core plus Monitoring-Sidecars.
10Mehrere Init-Container?
Ja. Jeder als separater Service mit restart: no. Können voneinander abhängen. Hauptservice wartet auf letzten in der Kette.