Docker Compose exec: Interaktive Shell öffnen
Öffnen Sie mit docker compose exec eine Shell in einem laufenden Dienst. Mit run --rm starten Sie einen separaten Container, ohne den Dienst zu stören.
Eine interaktive Shell mit docker compose exec öffnen
docker compose exec web bash öffnet eine interaktive Shell in dem Container, in dem der Dienst web bereits läuft. Der Name nach exec ist der Dienstname aus Ihrer compose.yaml, nicht der Containername. Wenn das Image kein bash enthält, verwenden Sie stattdessen sh.
docker compose ps
docker compose exec web bashFühren Sie zuerst docker compose ps aus. Die Ausgabe sollte web mit dem Status running enthalten. Der zweite Befehl bringt Sie anschließend zu einer Eingabeaufforderung im Container. Mit exit oder Strg-D kehren Sie zum Host zurück. Der Dienst läuft weiter, nachdem Sie die Shell verlassen haben, weil exec einen zweiten Prozess neben dem Hauptprozess gestartet hat. Das Schließen Ihrer Shell wirkt sich nicht auf PID 1 (Prozess-ID 1) aus. Dabei handelt es sich um den Prozess, für dessen Ausführung der Container erstellt wurde.
Das ist eine von zwei Möglichkeiten, in den Container zu gelangen. exec verbindet sich mit einem bereits vorhandenen Container. docker compose run erstellt anhand derselben Dienstdefinition einen neuen Container. Fast alles Weitere in diesem Leitfaden ergibt sich aus diesem einen Unterschied.
Warum -it in Compose optional, bei plain docker jedoch erforderlich ist
Zwei Flags steuern den interaktiven Teil einer Sitzung. -i hält stdin geöffnet, sodass Ihre Eingaben den Prozess erreichen. -t weist ein Pseudoterminal zu, kurz TTY genannt, sodass die Shell eine Eingabeaufforderung ausgibt und Pfeiltasten verarbeitet. Plain docker exec deaktiviert beides standardmäßig. Deshalb schreibt jedes bisher gezeigte Beispiel docker exec -it. docker compose exec aktiviert beides für Sie. Daher bewirken docker compose exec -it web bash und docker compose exec web bash dasselbe. Compose akzeptiert -it weiterhin, damit vertraute Eingabegewohnheiten funktionieren.
Eine fehlende TTY erkennen Sie innerhalb weniger Sekunden. Die Shell läuft, gibt aber keine Eingabeaufforderung aus, und Ctrl-C erreicht den Prozess nicht. Für den umgekehrten Fall, in dem Compose ausdrücklich keine TTY zuweisen soll, gibt es ein eigenes Flag und weiter unten einen eigenen Abschnitt.
Was tun, wenn das Image keine bash enthält
Bei einem Alpine-basierten Image schlägt die Anforderung von bash mit exec wie folgt fehl:
OCI runtime exec failed: exec failed: unable to start container process: exec: "bash": executable file not found in $PATH: unknownDiese Meldung weist nicht auf ein exec-Problem hin. Das angeforderte Binary ist im Image nicht vorhanden. Alpine liefert BusyBox aus. BusyBox stellt ash als /bin/sh bereit und enthält überhaupt keine bash. Fordern Sie daher sh an:
docker compose exec web shDebian- und Ubuntu-basierte Images enthalten bash, einschließlich der -slim-Tags. bash bietet eine Befehlshistorie und eine bessere Vervollständigung. Versuchen Sie daher zuerst bash und verwenden Sie andernfalls sh. sh ist in fast jedem allgemeinen Image vorhanden.
Einige Images enthalten überhaupt keine Shell. Distroless-Images und Images, die FROM scratch erstellt wurden, enthalten absichtlich nur das Anwendungs-Binary und dessen Bibliotheken. Eine nicht vorhandene Shell kann nicht gegen Sie verwendet werden. In diesen Images schlägt sh mit derselben Meldung fehl. Es gibt dann nichts weiter zu versuchen. Zwei Ansätze funktionieren. Die Distroless-Images von Google stellen :debug-Tags bereit, die eine BusyBox-Shell hinzufügen. Durch einen vorübergehenden Wechsel des Tags erhalten Sie Zugriff. Alternativ können Sie einen separaten Container innerhalb der Namespaces des Zielcontainers starten:
CID=$(docker compose ps -q web)
docker run --rm -it --network "container:$CID" --pid "container:$CID" nicolaka/netshootSie verfügen nun über die Tools von netshoot, die auf das Netzwerk der Anwendung zugreifen. Daher verhalten sich curl localhost:8080 und ss -lntp so, als würden Sie sich innerhalb des Anwendungscontainers befinden. Das angezeigte Dateisystem gehört netshoot und nicht der Anwendung. Da der Prozess-Namespace gemeinsam genutzt wird, greift ls /proc/1/root/ als root auf die Dateien des Ziels zu.
Wenn der Dienst nicht läuft, verwenden Sie docker compose run --rm
exec benötigt einen laufenden Container. Wenn Sie den Befehl auf einen gestoppten Dienst anwenden, wird er abgewiesen:
service "web" is not runningDer Befehl startet nichts automatisch. docker compose run übernimmt das:
docker compose run --rm web bashrun erstellt einen neuen Container anhand der Dienstdefinition von web. Dabei verwendet er dasselbe Image, dieselbe Umgebung, dieselben Volumes und dieselben Netzwerke. Den Befehl des Dienstes ersetzt er durch den von Ihnen eingegebenen Befehl. --rm löscht den Container, sobald Sie die Sitzung beenden. Lassen Sie --rm weg, sammeln sich Container unter Namen wie myproject-web-run-4f1c2b an. docker compose ps -a zeigt diese Container an. Eine andere Bereinigung erfolgt nicht.
Zwei Eigenschaften von run führen häufig zu Überraschungen. Der Befehl veröffentlicht die Ports des Dienstes nicht, sofern Sie nicht --service-ports hinzufügen. Das ist beabsichtigt: Ein zweiter Container, der den Host-Port 8080 bindet, würde fehlschlagen, solange der erste Container diesen Port noch verwendet. Die Fehlermeldung lautet bind: address already in use. Außerdem startet run alle Dienste, die der Dienst unter depends_on aufführt, bevor Ihre Shell erscheint. Schon eine kurze Inspektion kann dadurch eine Datenbank und einen Cache starten. --no-deps deaktiviert dieses Verhalten.
run durchläuft den ENTRYPOINT des Images, exec dagegen nicht. exec startet Ihren Befehl direkt im vorhandenen Container. Das EntryPoint-Skript sieht ihn daher nicht. Bei run wird Ihr bash als Argument an dieses Skript übergeben. Viele offizielle Images beenden ihr EntryPoint-Skript mit exec "$@". Dadurch wird der Befehl direkt weitergereicht, und Sie erhalten Ihre Shell. Ein Skript, das seine Argumente selbst interpretiert, verhält sich anders. In diesem Fall ersetzen Sie den EntryPoint für diesen einzelnen Aufruf:
docker compose run --rm --entrypoint sh webDies ist der häufigste Grund dafür, dass sich ein unter exec funktionierender Befehl unter run anders verhält. Die Trennung zwischen Befehl und EntryPoint erklärt, welchen Teil der Image-Konfiguration Sie jeweils ersetzen.
exec oder run: die richtige Auswahl
execbenötigt einen laufenden Container.runbenötigt keinen laufenden Container und kann Abhängigkeiten starten.execsieht die aktuelle Prozessliste und die Dateien in ihrem momentanen Zustand. Dazu gehören auch Dateien, die die Anwendung seit dem Start geschrieben hat.runverwendet eine unveränderte Kopie des Images. Diese Änderungen sind dort nicht vorhanden.execüberspringt den Entrypoint.runführt ihn aus.runhinterlässt einen Container, sofern Sie nicht--rmübergeben.
Verwenden Sie exec, um zu prüfen, was tatsächlich geschieht. Verwenden Sie run --rm für eine temporäre Kopie derselben Umgebung, für einen einmaligen Migrationsbefehl oder wenn der eigentliche Dienst nicht lange genug läuft, um sich mit exec damit zu verbinden.
Nützliche exec-Flags: Benutzer, Arbeitsverzeichnis und Replikate
Die meisten Images wechseln zu einem Nicht-root-Benutzer. Deshalb kommen Sie beim Installieren eines Diagnosewerkzeugs innerhalb Ihrer exec-Shell an dieser Stelle nicht weiter:
E: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied)-u root öffnet eine root-Shell im selben Container:
docker compose exec -u root web sh-w /srv/app legt nur für diesen Befehl das Arbeitsverzeichnis fest. -e KEY=value fügt Ihrer Sitzung eine Umgebungsvariable hinzu, nicht dem Dienst. Wenn ein Dienst mit mehr als einem Replikat ausgeführt wird, bestimmt --index 2, in welchem Container Sie landen. Wenn Sie die Dateibesitzrechte in einem eingebundenen Verzeichnis untersuchen, erklärt PUID und PGID in Container-Images, warum die numerischen IDs und nicht die Benutzernamen bestimmen, wer dort schreiben darf.
Eine psql- oder mysql-Shell im Datenbank-Container öffnen
Der Client ist bereits im Datenbank-Image enthalten. Sie benötigen daher keinen Client auf dem Host und müssen den Port nicht veröffentlichen:
docker compose exec db psql -U postgres -d app
docker compose exec db mariadb -u root -pPostgres-Images enthalten psql, MySQL-Images enthalten mysql und MariaDB-Images enthalten mariadb. Die Verbindung wird innerhalb des Containers hergestellt. Das funktioniert daher auch dann, wenn die Compose-Datei überhaupt keinen Datenbank-Port veröffentlicht. Das ist die sicherere Konfiguration: Nichts im Internet kann einen Port erreichen, den Sie nicht veröffentlicht haben.
Eine Falle kostet viele Anwender einen Nachmittag. Ihre Shell expandiert Variablen auf dem Host, bevor Docker den Befehl erhält. Daher übergibt -U "$POSTGRES_USER" eine leere Zeichenkette, wenn die Variable nur innerhalb des Containers existiert. Einfache Anführungszeichen und eine Shell innerhalb des Containers expandieren die Variable an der richtigen Stelle:
docker compose exec db sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"'Verwenden Sie hier nicht docker compose run --rm db ohne Befehl. Dadurch wird ein zweiter Postgres-Server mit demselben Daten-Volume gestartet, und er verweigert den Start:
FATAL: lock file "postmaster.pid" already existsDie Sperrdatei erfüllt ihre Aufgabe, weil zwei Server, die in dasselbe Datenverzeichnis schreiben, dieses beschädigen würden. Während die Datenbank läuft, führen Sie den Befehl mit exec im laufenden Container aus. Ob die Datenbank überhaupt in Compose betrieben werden sollte, ist eine separate Entscheidung. Die Datenbank in Docker oder auf dem Host betreiben erläutert die jeweiligen Vor- und Nachteile.
Dienste, die beim Start eine Konsole benötigen: stdin_open und tty
exec und run decken Shells ab, die Sie manuell öffnen. Ein Dienst, dessen Hauptprozess von Natur aus interaktiv ist, benötigt zwei Schlüssel in der Compose-Datei:
services:
console:
image: python:3.12-slim
command: python
stdin_open: true
tty: truestdin_open: true ist docker run -i und tty: true ist docker run -t. Ohne diese Einstellungen startet der Container und beendet sich sofort mit Code 0. docker compose ps -a zeigt Exited (0). Es ist kein Fehler aufgetreten. python liest ohne Terminal an stdin sofort das Dateiende und beendet sich normal. Das ist das korrekte Verhalten für ein Programm, an dem niemand Eingaben macht.
Wenn beide Schlüssel gesetzt sind, verbinden Sie sich mit dem laufenden Prozess:
docker attach $(docker compose ps -q console)Trennen Sie die Verbindung mit Ctrl-P und anschließend Ctrl-Q. Der Prozess läuft dabei weiter. Diese Tastenkombination funktioniert nur, wenn der Container sowohl über ein TTY als auch über geöffnetes stdin verfügt. Ctrl-C sendet stattdessen ein Interrupt-Signal an PID 1 und beendet den Dienst.
Lassen Sie beide Schlüssel bei gewöhnlichen Diensten deaktiviert. Ein Webserver liest niemals von stdin. tty: true veranlasst viele Programme, auf farbige Ausgabe und Zeilenpufferung umzuschalten, weil sie davon ausgehen, dass jemand die Ausgabe beobachtet. Dadurch wird docker compose logs mit Escape-Codes gefüllt.
Warum skriptgesteuertes exec in cron und CI mit dem Flag -T fehlschlägt
Ein exec-Befehl, der in Ihrem Terminal funktioniert, schlägt in einem cron-Job oder einem Continuous-Integration-(CI-)Runner fehl:
the input device is not a TTYCompose fordert standardmäßig ein Pseudo-Terminal an. cron stellt dem Job jedoch kein Terminal bereit. Deshalb schlägt die Anfrage fehl, bevor Ihr Befehl überhaupt ausgeführt wird. -T deaktiviert diese Anforderung:
0 3 * * * docker compose -f /srv/app/compose.yaml exec -T db pg_dump -U postgres -Fc app > /srv/backups/app.dump-T ist aus einem zweiten Grund wichtig. Ein TTY verändert den Byte-Stream beim Ausgeben. Ein komprimierter Dump, der ein TTY durchläuft, kommt daher beschädigt an. Für jede umgeleitete oder weitergeleitete Ausgabe benötigen Sie -T.
Beachten Sie bei cron außerdem zwei weitere Punkte. Übergeben Sie -f mit einem absoluten Pfad. cron führt den Job aus dem Home-Verzeichnis aus, in dem keine Compose-Datei liegt. Compose beendet sich dann mit no configuration file provided: not found. Außerdem gibt exec den Exit-Code des ausgeführten Befehls zurück. Ein fehlschlagendes pg_dump lässt Ihr Skript unter set -e daher fehlschlagen, statt ein leeres Backup zu schreiben und Erfolg zu melden. Die übrigen Befehle für den täglichen Betrieb sind in einem Spickzettel für Compose-Befehle zusammengestellt, den Sie neben diesen Skripten aufbewahren sollten.
Warum Änderungen im Container verschwinden
Sie installieren ein Tool mit exec, bearbeiten eine Konfigurationsdatei, beheben das Problem, und eine Woche später ist die Änderung verschwunden. Das entspricht dem vorgesehenen Verhalten der beschreibbaren Container-Schicht. docker compose up -d zerstört nach jeder Änderung am Image-Tag oder an der Servicedefinition den alten Container und erstellt anhand des Images einen neuen. Jede manuelle Änderung geht mit dem alten Container verloren.
docker compose restart arbeitet anders. Es stoppt und startet denselben Container, sodass manuelle Änderungen erhalten bleiben. Deshalb kann eine manuelle Korrektur wochenlang zu funktionieren scheinen und dann bei einem unabhängigen Update verschwinden. Named Volumes und Bind-Mounts überstehen beide Vorgänge, weil ihre Daten außerhalb des Containers gespeichert werden. Bind-Mounts und Named Volumes beschreibt, welche Variante für Daten geeignet ist, die Sie dauerhaft behalten möchten.
Betrachten Sie eine exec-Shell daher als Ort zum Lesen und Testen. Sobald die Korrektur feststeht, tragen Sie sie dort ein, wo sie erhalten bleibt: ein Paket in die Dockerfile, eine Einstellung in die Compose-Datei. Führen Sie anschließend docker compose up -d aus, und prüfen Sie mit einem weiteren exec-Befehl, ob der neue Container die Änderung tatsächlich enthält.
FAQ
Was ist der Unterschied zwischen docker compose exec und docker compose run?
exec führt einen Befehl in einem bereits laufenden Container neben dem Hauptprozess aus und überspringt den Image-Entrypoint. run erstellt aus derselben Servicedefinition einen neuen Container mit demselben Image, derselben Umgebung, denselben Volumes und Netzwerken, reicht Ihren Befehl durch den Entrypoint weiter und startet alle depends_on-Dienste zuerst. Die Ports des Dienstes bleiben bei run außerdem unveröffentlicht, sofern Sie nicht --service-ports hinzufügen. Verwenden Sie exec, um den laufenden Dienst zu untersuchen. Verwenden Sie run --rm, wenn der Dienst gestoppt ist oder Sie den laufenden Dienst nicht beeinträchtigen möchten.
Warum meldet docker compose exec, dass der Dienst nicht läuft?
exec verbindet sich mit einem vorhandenen Container und kann keinen Container erstellen. Ein gestoppter oder abgestürzter Dienst führt daher zu service "web" is not running. Prüfen Sie docker compose ps -a. Dieser Befehl listet beendete Container mit einem Status wie Exited (1) auf. Lesen Sie docker compose logs web, um den Grund für das Beenden zu ermitteln. Um trotzdem eine Shell zu erhalten, führen Sie docker compose run --rm --entrypoint sh web aus. Dadurch wird aus derselben Servicedefinition ein neuer Container erstellt, ohne den fehlerhaften Startbefehl auszuführen.
Wie öffne ich eine Shell, wenn das Image kein bash enthält?
Wenn docker compose exec web bash mit exec: "bash": executable file not found in $PATH fehlschlägt, fehlt bash im Image. Das ist bei Images auf Basis von Alpine normal. Verwenden Sie docker compose exec web sh, da BusyBox /bin/sh bereitstellt. Distroless- und scratch-Images enthalten überhaupt keine Shell. Daher funktioniert dort kein exec-Befehl. Wechseln Sie zum :debug-Tag des Images, wenn der Herausgeber einen solchen anbietet, oder starten Sie mit docker run --rm -it --network "container:$CID" --pid "container:$CID" nicolaka/netshoot einen Debug-Container in den Namespaces des Zielcontainers. Dabei stammt $CID aus docker compose ps -q web.
Warum schlägt mein exec-Befehl in cron mit „the input device is not a TTY“ fehl?
docker compose exec fordert standardmäßig ein Pseudo-Terminal an. cron stellt kein solches bereit. Daher schlägt die Anforderung fehl, bevor Ihr Befehl ausgeführt wird. Fügen Sie -T hinzu, um diese Funktion zu deaktivieren: docker compose exec -T db pg_dump -U postgres app. Verwenden Sie -T auch für umgeleitete oder weitergeleitete Ausgaben, da ein TTY den Byte-Stream verändert und einen Binär-Dump beschädigt. Übergeben Sie in cron außerdem -f mit dem absoluten Pfad zu Ihrer Compose-Datei. Andernfalls beendet sich Compose mit no configuration file provided: not found.
Bleiben Änderungen, die ich mit exec in einem Container vornehme, nach einem Neustart erhalten?
Sie bleiben nach docker compose restart erhalten, weil dabei derselbe Container wiederverwendet wird. Nach docker compose up -d gehen sie nach jeder Änderung am Image oder an der Konfiguration verloren, weil dadurch der Container aus dem Image neu erstellt und seine beschreibbare Schicht verworfen wird. Daten in benannten Volumes oder Bind-Mounts bleiben in beiden Fällen erhalten, da sie außerhalb des Containers gespeichert sind. Nehmen Sie Diagnoseänderungen mit exec vor. Hinterlegen Sie die dauerhafte Konfiguration anschließend im Dockerfile oder in der Compose-Datei.