SSD Nodes Learn 🎉 VPS ab $4.99/Monat
Anleitungen Matt ConnorVon Matt Connor · Aktualisiert 2026-08-07

Docker Compose: command vs. entrypoint richtig verstehen

ENTRYPOINT startet das Programm, command liefert seine Argumente. Sehen Sie alle vier Override-Kombinationen und warum entrypoint das CMD des Images löscht.

Docker-Compose-Befehl und Entrypoint in einer Regel

In Docker Compose legt entrypoint: das auszuführende Programm fest, während command: die an dieses Programm übergebenen Argumente festlegt. Der Prozess des Containers besteht aus der Entrypoint-Liste, an die die Command-Liste angehängt wird. Jede andere Verhaltensweise auf dieser Seite ergibt sich aus diesem einen Satz.

Diese beiden Schlüssel entsprechen zwei Dockerfile-Anweisungen. entrypoint: ersetzt den ENTRYPOINT des Images. command: ersetzt den CMD des Images. Sie sind nicht unabhängig voneinander. Genau das führt häufig zu Problemen: Wenn entrypoint: gesetzt wird, wird auch der CMD des Images verworfen. Die Compose-Spezifikation formuliert dies direkt. Wenn entrypoint nicht null ist, ignoriert Compose den Standardbefehl des Images.

Was das Image bereits vorgibt

Bevor Sie etwas überschreiben, prüfen Sie, was das Image mitliefert.

docker image inspect --format '{{json .Config.Entrypoint}}' postgres:16
docker image inspect --format '{{json .Config.Cmd}}' postgres:16

Sie sehen ["docker-entrypoint.sh"] und ["postgres"]. Der Container startet daher docker-entrypoint.sh postgres. Dieses Skript erstellt das Datenverzeichnis beim ersten Start, liest die Variablen POSTGRES_* ein, wechselt zum Benutzer postgres mit eingeschränkten Rechten und führt schließlich die übergebenen Argumente mit exec aus. Die entscheidende Frage ist, welchen Teil Sie ändern möchten. Um ein Flag an die Datenbank zu übergeben, ersetzen Sie command:. Wenn Sie entrypoint: ersetzen, wird diese gesamte Initialisierung nicht ausgeführt.

Die vier Kombinationen, auf einer kleinen Abbildung dargestellt

Erstellen Sie ein Image, dessen einzige Aufgabe darin besteht, die Argumentliste auszugeben, mit der es gestartet wurde.

FROM alpine:3.20
ENTRYPOINT ["/bin/echo", "ep"]
CMD ["cmd"]
docker build -t argdemo .
services:
  demo:
    image: argdemo

Führen Sie nach jeder Änderung docker compose up aus und lesen Sie die einzelne protokollierte Zeile.

  • Keine der beiden Schlüssel gesetzt. Der Prozess ist /bin/echo ep cmd und das Log zeigt ep cmd.
  • Nur command: ["cmd2"]. Der Prozess ist /bin/echo ep cmd2. Der Entrypoint bleibt unverändert, und nur die Argumente ändern sich.
  • Nur entrypoint: ["/bin/echo", "ep2"]. Der Prozess ist /bin/echo ep2, und das Log zeigt ep2. Das cmd aus dem Image ist verschwunden, und es erfolgt keine Warnung.
  • Beide Schlüssel gesetzt. Der Prozess ist /bin/echo ep2 cmd2. Dies ist der einzige Fall, in dem Sie die vollständige Argumentliste kontrollieren.

Warum das Setzen von entrypoint das CMD des Images löscht

Das CMD eines Images wird als Standardargumentliste für das ENTRYPOINT dieses Images verwendet. Wenn Sie den Entrypoint ersetzen, gehören diese Argumente nun zu einem Programm, das nicht mehr ausgeführt wird. Compose verwirft sie daher, statt eine Befehlszeile zu erstellen, die der Autor des Images nie vorgesehen hat. docker run --entrypoint verhält sich genauso. Das ist also ein Verhalten von Docker und keine Besonderheit von Compose.

Die Folge ist konkret. nginx:1.27 definiert ENTRYPOINT ["/docker-entrypoint.sh"] und CMD ["nginx", "-g", "daemon off;"]. Wenn Sie entrypoint: /custom-init.sh setzen, startet Ihr Skript mit einer leeren Argumentliste. Ein Skript, das mit dem üblichen exec "$@" endet, hat dann nichts zu starten. exec bewirkt daher nichts. Das Skript erreicht seine letzte Zeile, und der Container wird mit Exit-Code 0 beendet, ohne dass irgendwo eine Fehlermeldung ausgegeben wird. Fügen Sie die Argumente selbst wieder hinzu:

services:
  web:
    image: nginx:1.27
    entrypoint: /custom-init.sh
    command: ["nginx", "-g", "daemon off;"]

Die wichtige Regel lautet: Entscheiden Sie jedes Mal, wenn Sie entrypoint: setzen, in derselben Änderung, wie command: lauten soll.

Ausführungsform und Shell-Form sowie die Unterschiede bei Compose

Eine Dockerfile akzeptiert zwei Syntaxformen. CMD ["nginx", "-g", "daemon off;"] ist die Ausführungsform: Das Binärprogramm wird direkt und ohne Shell ausgeführt. CMD nginx -g "daemon off;" ist die Shell-Form: Docker schreibt sie als /bin/sh -c 'nginx -g "daemon off;"' um. Dadurch wird zuerst eine Shell gestartet, und Ihr Programm wird zu deren untergeordnetem Prozess.

Compose übernimmt diese Regel nicht. Das überrascht viele Anwender. Eine Zeichenkette in command: wird in Argumente aufgeteilt und ohne Wrapper durch /bin/sh -c direkt ausgeführt. Die Compose-Referenz weist ausdrücklich darauf hin: Das Feld command wird nicht im durch das Image definierten SHELL-Kontext ausgeführt. Wenn Sie Shell-Funktionen benötigen, müssen Sie eine Shell selbst aufrufen.

Deshalb gibt command: echo "hello $$HOSTNAME" den wörtlichen Text hello $HOSTNAME aus. Keine Shell hat die Zeichenkette verarbeitet, daher wurde nichts ersetzt. Fordern Sie eine Shell an, wenn Sie eine benötigen:

services:
  demo:
    image: alpine:3.20
    command: /bin/sh -c 'echo "hello $$HOSTNAME"'

Signale, PID 1 und ein sauberes docker compose down

docker compose stop und docker compose down senden SIGTERM an PID 1 in jedem Container, warten auf stop_grace_period und senden anschließend SIGKILL. Die standardmäßige Kulanzfrist beträgt 10 Sekunden.

PID 1 hat unter Linux eine Sonderrolle. Der Kernel wendet die Standardaktion eines Signals nicht auf PID 1 an. Ein Prozess, der keinen SIGTERM-Handler installiert, ignoriert SIGTERM daher, wenn er als PID 1 ausgeführt wird. Er läuft während der gesamten Kulanzfrist weiter und wird anschließend sofort beendet. Dadurch werden offene Verbindungen oder nicht festgeschriebene Transaktionen abgebrochen.

Eine Shell vor Ihrem Programm macht dieses Verhalten wahrscheinlicher, weil die Shell PID 1 ist und die meisten Shells Signale nicht an einen Kindprozess weiterleiten. Einige Shells ersetzen sich bei einem -c-String durch den abschließenden Befehl. Dadurch erreicht Ihr Programm manchmal trotzdem PID 1. Das hängt von der Shell und vom genauen String ab. Raten Sie daher nicht, sondern prüfen Sie es:

docker compose exec -T web cat /proc/1/cmdline | tr '\0' ' '; echo

Wenn PID 1 als /bin/sh -c ... und nicht als Ihr Programm angezeigt wird, gibt es zwei Lösungen. Verwenden Sie im Image die Exec-Form oder behalten Sie die Shell bei und übergeben Sie den Prozess mit exec:

services:
  web:
    image: myapp:1.4
    command: /bin/sh -c 'exec myapp --config /etc/myapp.toml'

exec ersetzt den Shell-Prozess durch Ihr Programm, statt einen Kindprozess zu forken. Ihr Programm übernimmt dadurch PID 1 und empfängt das Signal.

Einige Programme starten Kindprozesse und sammeln sie nie wieder ein. Dadurch bleiben Zombie-Prozesse zurück, weil PID 1 auch als Reaper fungiert. Compose bietet dafür einen Schalter:

services:
  web:
    image: myapp:1.4
    init: true
    stop_grace_period: 30s

init: true führt als PID 1 einen kleinen Init-Prozess aus, der Signale an Ihren Prozess weiterleitet und Kindprozesse einsammelt. stop_grace_period gibt einem tatsächlich langsamen Herunterfahren mehr Zeit. Wenn Ihr Programm ein anderes Signal erwartet, ändert stop_signal: SIGQUIT das von Compose gesendete Signal. Mit docker image inspect --format '{{.Config.StopSignal}}' nginx:1.27 prüfen Sie, welches Signal ein Image bereits anfordert.

Wenn docker compose down pro Dienst immer zehn Sekunden benötigt, zeigt das, dass nichts SIGTERM verarbeitet. Beheben Sie das, bevor Sie die Werkzeuge dafür verantwortlich machen. Unter dem Unterschied zwischen docker compose down und stop erfahren Sie, welche Ressourcen die jeweiligen Unterbefehle entfernen.

Dieselbe Unterscheidung zwischen Exec- und Shell-Form tritt noch an einer weiteren Stelle auf. Ein als test: ["CMD", "curl", "-f", "http://localhost/"] formulierter Healthcheck führt die Binärdatei direkt aus. test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"] wird dagegen über eine Shell ausgeführt, sodass || eine Bedeutung hat. Compose-Healthchecks schreiben, die Fehler zuverlässig melden behandelt die übrigen Aspekte dieses Feldes.

Ein Flag an ein offizielles Image anhängen

Darum geht es den meisten Lesern. Sie möchten postgres ein zusätzliches Flag übergeben und dürfen das Initialisierungsskript nicht beeinflussen.

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
    command: postgres -c max_connections=200 -c shared_buffers=256MB

volumes:
  pgdata:

Nur command: wurde geändert. docker-entrypoint.sh wird daher weiterhin ausgeführt und startet weiterhin den von Ihnen angegebenen Prozess. Prüfen Sie das Ergebnis, statt es vorauszusetzen:

docker compose up -d db
docker compose exec -T db psql -U postgres -c 'show max_connections;'

Die Ausgabe sollte 200 anzeigen. Wenn weiterhin 100 angezeigt wird, führen Sie docker compose config aus und bestätigen Sie, dass der erwartete command in der zusammengeführten Ausgabe enthalten ist. Compose führt Override-Dateien zusammen, indem es command vollständig ersetzt, statt Inhalte daran anzuhängen. Eine zweite Datei, die ebenfalls command: setzt, hat daher stillschweigend Vorrang.

Das ${POSTGRES_PASSWORD} oben wird von Compose auf dem Host aus Ihrer Datei .env erweitert, bevor der Container existiert. Umgebungsdateien und Secrets in Compose erläutert, wo dieser Wert sicher abgelegt werden kann.

Eine einmalige Migration mit docker compose run ausführen

docker compose run erstellt aus derselben Service-Definition einen neuen Container und ersetzt den Befehl durch den Text, den Sie nach dem Servicenamen eingeben. Der Entrypoint des Images wird weiterhin ausgeführt. Der Container wird daher genau wie der dauerhaft laufende Container vorbereitet.

docker compose run --rm app python manage.py migrate
  • --rm löscht den Container, sobald der Befehl beendet ist. Ohne diese Option hinterlässt jeder Aufruf einen gestoppten Container, der in docker compose ps -a sichtbar ist.
  • Ports werden nicht veröffentlicht. Ein run-Container ignoriert ports: des Service, sofern Sie nicht --service-ports hinzufügen. Daher kann er nicht mit dem bereits laufenden Service kollidieren.
  • Abhängigkeiten werden zuerst gestartet. Alles in depends_on wird vor Ihrem Befehl gestartet. --no-deps überspringt diesen Schritt.
  • Der Container erhält einen generierten Namen wie myproject-app-run-9f2c1a. Dadurch kommt es nie zu einem Namenskonflikt mit dem Service-Container.

Um auch den Entrypoint zu ersetzen, gibt es dafür eine Option:

docker compose run --rm --entrypoint /bin/sh app -c 'python manage.py migrate'

Die resultierende Argumentliste ist /bin/sh -c 'python manage.py migrate', weil die Wörter nach dem Servicenamen weiterhin den Befehl bilden. docker compose exec ist das andere Werkzeug und funktioniert anders: Es führt einen Prozess in einem bereits laufenden Container aus und ignoriert entrypoint: und command: vollständig. Verwenden Sie run für eine Aufgabe, die einen neuen Container benötigt, und exec, um einen laufenden Container zu untersuchen. Das Cheat Sheet für Compose-Befehle stellt die übrigen Unterbefehle direkt gegenüber.

Warum wird mein Container sofort beendet?

Beginnen Sie mit dem Exit-Code. Dadurch lässt sich die Ursache schnell eingrenzen.

docker compose ps -a
docker compose logs app

Exit-Code 0 und keine Ausgabe. Der Befehl wurde ausgeführt und beendet. Die häufigste Ursache ist ein entrypoint:-Override, der den CMD des Images überschrieben hat. Dadurch wurde der Entrypoint mit einer leeren Argumentliste ausgeführt und hatte nichts zu übergeben.

Ein Fehler, der mit permission denied endet. Das Skript hat im Image kein Ausführungsbit. Meist wurde dieses Bit für die Datei im Repository nie gesetzt. Setzen Sie es beim Build mit COPY --chmod=0755 entrypoint.sh /entrypoint.sh.

Ein Fehler, der bei einer Datei, die im Image sichtbar vorhanden ist, mit no such file or directory endet. Das Skript verwendet Windows-Zeilenenden. Die erste Zeile lautet dann #!/bin/sh plus ein Carriage-Return-Byte. Der Kernel sucht dadurch nach einem Interpreter, dessen Name dieses Byte enthält, und findet keinen. Führen Sie dos2unix entrypoint.sh aus. Fügen Sie anschließend * text eol=lf zu .gitattributes hinzu, damit das Problem nicht erneut auftritt.

executable file not found in $PATH. Die in command: angegebene Binärdatei ist nicht im Image enthalten. Möglich ist auch, dass Sie ein Shell-Builtin wie cd angegeben haben, obwohl an dieser Stelle nur ein echtes Programm ausgeführt werden kann.

Eine Shell in ein Image öffnen, dessen Entrypoint fehlschlägt

Wenn der Entrypoint beendet wird, bevor Sie etwas untersuchen können, ersetzen Sie ihn:

docker compose run --rm --entrypoint /bin/sh app

Wenn dieser Befehl executable file not found in $PATH zurückgibt, enthält das Image überhaupt keine Shell. Distroless- und auf scratch basierende Images enthalten häufig keine Shell. Sie können das Dateisystem trotzdem von außen lesen, ohne den Entrypoint zu starten:

docker create --name probe myapp:1.4
docker export probe | tar -tv | head -40
docker rm probe

Wenn der Container laufen soll, damit Sie sich wiederholt mit ihm verbinden können, starten Sie ihn mit einem Prozess, der nie beendet wird. Tragen Sie dies in eine Override-Datei ein, die Sie nicht committen:

services:
  app:
    entrypoint: ["tail", "-f", "/dev/null"]
    command: []

command: [] ist nicht unbedingt erforderlich, da das Setzen von entrypoint: den CMD des Images bereits entfernt hat. Durch den Eintrag wird jedoch die Absicht für die nächste Person dokumentiert, die die Datei liest. Starten Sie den Container und öffnen Sie eine Shell darin:

docker compose -f compose.yaml -f compose.debug.yaml up -d app
docker compose exec app /bin/sh

Führen Sie nun den echten Entrypoint manuell aus und beobachten Sie, an welcher Stelle er stoppt. Dadurch erscheint die Fehlermeldung in Ihrem Terminal statt in einem Container, der vor einer halben Sekunde beendet wurde. Wenn Sie Ihren ersten Stack noch zusammenstellen, beschreibt ein erster Compose-Stack auf einem VPS die Dateistruktur, von der alle obigen Schritte ausgehen.

FAQ

Warum wird mein Container direkt nach docker compose up beendet?

Prüfen Sie docker compose ps -a auf den Exit-Code. Exit 0 ohne Ausgabe bedeutet normalerweise, dass Sie entrypoint: für den Dienst gesetzt haben. Dadurch wurde auch CMD des Images entfernt. Der Entrypoint wurde deshalb mit einer leeren Argumentliste ausgeführt und beendet sich sofort. Fügen Sie die Argumente mit command: wieder hinzu. Ein Fehler mit dem Ende permission denied bedeutet, dass dem Entrypoint-Skript das Ausführungsbit fehlt. Ein Fehler mit dem Ende no such file or directory bei einer vorhandenen Datei bedeutet, dass das Skript Windows-Zeilenenden verwendet. Die Shebang-Zeile verweist dadurch auf einen Interpreter, der nicht vorhanden ist.

Entfernt das Setzen von entrypoint in Compose das CMD des Images?

Ja. Wenn entrypoint nicht null ist, ignoriert Compose den im Image definierten Standardbefehl. Dieses Verhalten ist dokumentiert und entspricht docker run --entrypoint. Der Grund ist, dass CMD eines Images als Argumente für ENTRYPOINT dieses Images geschrieben wird. Sobald Sie den Entrypoint ersetzen, gehören die alten Argumente zu keinem Prozess mehr. Setzen Sie im selben Dienst command:, wenn der neue Entrypoint weiterhin Argumente benötigt.

Wird eine Zeichenkette in Compose command über eine Shell ausgeführt?

Nein. Anders als ein Dockerfile-CMD wird eine Zeichenkette in command: von Compose in Argumente aufgeteilt und direkt ausgeführt, ohne /bin/sh -c-Wrapper. Daher wird $VARIABLE niemals von einer Shell im Container expandiert. Rufen Sie die Shell bei Bedarf selbst auf, wie in command: /bin/sh -c 'echo "hello $$HOSTNAME"'. Das doppelte $$ maskiert das Dollarzeichen, sodass Compose es an den Container übergibt, statt es auf dem Host zu expandieren.

Warum benötigt docker compose down bei einem Container zehn Sekunden?

Compose sendet SIGTERM an PID 1, wartet auf stop_grace_period (standardmäßig 10 Sekunden) und sendet anschließend SIGKILL. Der Kernel wendet auf PID 1 keine Standardaktionen für Signale an. Ein Programm ohne SIGTERM-Handler ignoriert das Signal daher und wartet immer die vollständige Zeitspanne ab. Ermitteln Sie mit docker compose exec -T app cat /proc/1/cmdline | tr '\0' ' ', welcher Prozess tatsächlich PID 1 ist. Wenn es sich um eine Shell handelt, wechseln Sie beim Image zur Exec-Form oder schreiben Sie exec innerhalb der Shell-Zeichenkette. Wenn der Prozess Kindprozesse startet, diese aber nie aufräumt, setzen Sie init: true für den Dienst.

#docker-compose#entrypoint#command#containers#debugging