Jak uruchomić interaktywną powłokę w Docker Compose
Dowiedz się, jak użyć docker compose exec do otwarcia powłoki w działającym kontenerze. Poznaj różnicę między exec a run --rm dla usług, które są zatrzymane lub wymagają izolacji.
Uzyskiwanie interaktywnej powłoki za pomocą docker compose exec
docker compose exec web bash otwiera interaktywną powłokę wewnątrz kontenera, który jest już uruchomiony jako usługa web. Nazwa po exec to nazwa usługi z pliku compose.yaml, a nie nazwa kontenera. Jeśli obraz nie zawiera bash, należy wywołać sh.
docker compose ps
docker compose exec web bashNajpierw należy uruchomić docker compose ps. Powinno to wyświetlić web ze stanem running. Następnie drugie polecenie przenosi użytkownika do wiersza poleceń wewnątrz kontenera, a exit lub Ctrl-D przywraca sesję na hoście. Usługa działa nadal po wyjściu, ponieważ exec uruchomił drugi proces obok głównego. Zamknięcie powłoki nie wpływa na PID 1 (process id 1), czyli proces, dla którego kontener został utworzony.
Jest to jeden z dwóch sposobów uzyskania dostępu. Polecenie exec dołącza się do już istniejącego kontenera. docker compose run tworzy nowy kontener na podstawie tej samej definicji usługi. Prawie wszystkie pozostałe informacje w tym przewodniku wynikają z tej jednej różnicy.
Dlaczego flaga -it jest opcjonalna w Compose, a wymagana w zwykłym docker
Dwie flagi sterują interaktywnością sesji. -i utrzymuje otwarte wejście stdin, dzięki czemu wpisywane znaki docierają do procesu. -t przydziela pseudo-terminal, zwany TTY, co pozwala powłoce wyświetlać znak zachęty i obsługiwać klawisze strzałek. Zwykły docker exec domyślnie wyłącza obie te opcje, dlatego w każdym przykładzie widać zapis docker exec -it. docker compose exec włącza obie opcje automatycznie, więc docker compose exec -it web bash oraz docker compose exec web bash wykonują to samo zadanie. Compose nadal akceptuje -it, aby zachować kompatybilność z przyzwyczajeniami użytkowników.
Brak TTY staje się zauważalny w ciągu kilku sekund. Powłoka uruchamia się, ale nie wyświetla znaku zachęty, a sygnał Ctrl-C nie dociera do procesu. Przypadek odwrotny, w którym należy polecić Compose, aby nie przydzielał TTY, posiada własną flagę i został opisany w dalszej części dokumentacji.
Co zrobić, gdy obraz nie zawiera bash
Wywołanie bash w obrazie opartym na Alpine kończy się niepowodzeniem w następujący sposób:
OCI runtime exec failed: exec failed: unable to start container process: exec: "bash": executable file not found in $PATH: unknownTen komunikat nie oznacza problemu z poleceniem exec. Informuje on, że plik binarny, o który poproszono, nie znajduje się w obrazie. Alpine dostarcza BusyBox, który udostępnia ash jako /bin/sh i w ogóle nie zawiera bash, dlatego należy użyć sh:
docker compose exec web shObrazy oparte na Debian i Ubuntu, w tym tagi -slim, zawierają bash, który zapewnia historię poleceń i lepsze uzupełnianie. Należy zatem najpierw spróbować użyć bash, a w razie braku przejść na sh. sh występuje w niemal każdym obrazie ogólnego przeznaczenia.
Niektóre obrazy nie posiadają żadnej powłoki. Obrazy typu Distroless oraz obrazy budowane FROM scratch zawierają wyłącznie plik binarny aplikacji wraz z bibliotekami. Jest to celowe działanie, ponieważ powłoka, której nie ma, nie może zostać wykorzystana przeciwko systemowi. W takich przypadkach sh kończy się tym samym komunikatem i nie ma innych opcji do wypróbowania. Skuteczne są dwa podejścia. Obrazy Distroless firmy Google udostępniają tagi :debug, które dodają powłokę BusyBox, więc tymczasowa zmiana tagu pozwala na uzyskanie dostępu. Alternatywnie można uruchomić oddzielny kontener wewnątrz przestrzeni nazw (namespaces) docelowego kontenera:
CID=$(docker compose ps -q web)
docker run --rm -it --network "container:$CID" --pid "container:$CID" nicolaka/netshootNarzędzia netshoot są teraz skierowane na sieć aplikacji, więc curl localhost:8080 oraz ss -lntp działają tak, jakby użytkownik znajdował się wewnątrz niej. Widoczny system plików należy do netshoot, a nie do aplikacji. Ponieważ przestrzeń nazw procesów jest współdzielona, ls /proc/1/root/ pozwala uzyskać dostęp do plików docelowych, gdy użytkownik posiada uprawnienia root.
Gdy usługa nie działa, użyj docker compose run --rm
Polecenie exec wymaga uruchomionego kontenera. Wskazanie zatrzymanej usługi spowoduje błąd:
service "web" is not runningPolecenie to nie uruchomi żadnego procesu. Zrobi to docker compose run:
docker compose run --rm web bashPolecenie run tworzy nowy kontener na podstawie definicji usługi web, korzystając z tego samego obrazu, zmiennych środowiskowych, wolumenów i sieci, a następnie zastępuje polecenie usługi tym, które wpisano. Flaga --rm usuwa kontener po wyjściu. Pominięcie --rm spowoduje gromadzenie się pozostałości pod nazwami takimi jak myproject-web-run-4f1c2b, które wyświetli docker compose ps -a i których nic innego nie usunie.
Dwa zachowania polecenia run często zaskakują użytkowników. Nie publikuje ono portów usługi, chyba że dodasz --service-ports, co jest celowe: próba powiązania drugiego kontenera z portem hosta 8080, gdy pierwszy nadal go zajmuje, zakończyłaby się błędem bind: address already in use. Polecenie to uruchamia również wszystko, co usługa wymienia w sekcji depends_on, zanim pojawi się powłoka, więc szybki podgląd wnętrza może uruchomić bazę danych i pamięć podręczną. Flaga --no-deps pozwala tego uniknąć.
Polecenie run przechodzi przez ENTRYPOINT obrazu, natomiast exec tego nie robi. Polecenie exec uruchamia komendę bezpośrednio w istniejącym kontenerze, więc skrypt punktu wejścia jej nie przetwarza. W przypadku run, Twoje bash trafia jako argumenty do tego skryptu. Wiele oficjalnych obrazów kończy swój punkt wejścia poleceniem exec "$@", dzięki czemu argumenty są przekazywane dalej i otrzymujesz powłokę. Skrypt, który samodzielnie interpretuje swoje argumenty, postąpi z nimi inaczej, dlatego w takim przypadku należy zastąpić punkt wejścia dla pojedynczego uruchomienia:
docker compose run --rm --entrypoint sh webJest to najczęstszy powód, dla którego polecenie działające w exec zachowuje się inaczej w run, a podział między command a entrypoint wyjaśnia, którą część konfiguracji obrazu zastępujesz w każdym przypadku.
exec czy run: jak dokonać wyboru
- Polecenie exec wymaga działającego kontenera. Polecenie run nie wymaga działającego kontenera i może uruchomić zależności.
- Polecenie exec widzi bieżącą listę procesów oraz pliki w ich aktualnym stanie, w tym dane zapisane przez aplikację od momentu uruchomienia. Polecenie run pobiera czystą kopię obrazu, więc te dane nie są w niej dostępne.
- Polecenie exec pomija entrypoint. Polecenie run wykonuje go.
- Polecenie run pozostawia kontener w systemie, chyba że użyta zostanie flaga
--rm.
Użyj exec, aby sprawdzić, co faktycznie dzieje się wewnątrz kontenera. Użyj run --rm dla jednorazowej kopii tego samego środowiska, do wykonania polecenia migracji lub w sytuacji, gdy właściwa usługa nie działa wystarczająco długo, aby można było wykonać w niej exec.
Przydatne flagi polecenia exec: użytkownik, katalog roboczy i repliki
Większość obrazów przełącza się na użytkownika innego niż root, dlatego instalacja narzędzi diagnostycznych wewnątrz powłoki exec kończy się niepowodzeniem:
E: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied)-u root pozwala uzyskać powłokę z uprawnieniami root w tym samym kontenerze:
docker compose exec -u root web sh-w /srv/app ustawia katalog roboczy tylko dla tego konkretnego polecenia. -e KEY=value dodaje zmienną środowiskową do bieżącej sesji, a nie do samej usługi. Gdy usługa działa w więcej niż jednej replice, --index 2 decyduje, do którego kontenera zostanie uzyskany dostęp. Jeśli problemem jest uprawnienie do plików w zamontowanym katalogu, sekcja PUID i PGID w obrazach kontenerów wyjaśnia, dlaczego to numeryczne identyfikatory, a nie nazwy użytkowników, decydują o możliwości zapisu.
Uzyskanie dostępu do powłoki psql lub mysql wewnątrz kontenera bazy danych
Klient znajduje się już wewnątrz obrazu bazy danych, więc nie jest wymagana jego instalacja na hoście ani publikowanie portu:
docker compose exec db psql -U postgres -d app
docker compose exec db mariadb -u root -pObrazy Postgres zawierają psql, obrazy MySQL zawierają mysql, a obrazy MariaDB zawierają mariadb. Połączenie jest nawiązywane z wnętrza kontenera, więc działa to nawet wtedy, gdy plik compose nie publikuje żadnego portu bazy danych. Jest to bezpieczniejsze rozwiązanie: żaden element w Internecie nie uzyska dostępu do portu, który nie został opublikowany.
Jedna pułapka często kosztuje użytkowników wiele czasu. Powłoka rozwija zmienne na hoście, zanim Docker otrzyma polecenie, więc -U "$POSTGRES_USER" przesyła pusty ciąg znaków, jeśli zmienna istnieje tylko wewnątrz kontenera. Pojedyncze cudzysłowy oraz powłoka wewnątrz kontenera zapewniają rozwinięcie zmiennej we właściwym miejscu:
docker compose exec db sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"'Nie należy używać docker compose run --rm db bez podania polecenia. Powoduje to uruchomienie drugiego serwera Postgres na tym samym wolumenie danych, co kończy się odmową startu:
FATAL: lock file "postmaster.pid" already existsPlik blokady spełnia swoje zadanie, ponieważ zapisywanie do jednego katalogu danych przez dwa serwery doprowadziłoby do jego uszkodzenia. Gdy baza danych działa, należy użyć exec wewnątrz uruchomionego kontenera. Kwestia tego, czy baza danych powinna w ogóle znajdować się w Compose, jest odrębną decyzją, a uruchamianie bazy danych w Dockerze lub na hoście przedstawia wady i zalety tego rozwiązania.
Usługi wymagające konsoli podczas startu: stdin_open oraz tty
Polecenia exec oraz run obsługują powłoki otwierane ręcznie. Usługa, której główny proces jest z natury interaktywny, wymaga dwóch kluczy w pliku compose:
services:
console:
image: python:3.12-slim
command: python
stdin_open: true
tty: truestdin_open: true to docker run -i, a tty: true to docker run -t. Bez nich kontener uruchamia się i natychmiast kończy działanie z kodem 0, a docker compose ps -a pokazuje Exited (0). Nie doszło do awarii. python bez terminala na stdin natychmiast odczytuje koniec pliku i kończy pracę w sposób prawidłowy, co jest właściwym zachowaniem dla programu, do którego nikt nie wprowadza danych.
Po ustawieniu obu kluczy można podłączyć się do uruchomionego procesu:
docker attach $(docker compose ps -q console)Odłączenie następuje po naciśnięciu Ctrl-P, a następnie Ctrl-Q, co pozostawia proces w stanie uruchomionym. Ta sekwencja działa tylko wtedy, gdy kontener ma otwarte zarówno TTY, jak i stdin. Z kolei Ctrl-C wysyła sygnał przerwania do PID 1 i zatrzymuje usługę.
W przypadku zwykłych usług należy pozostawić oba klucze wyłączone. Serwer WWW nigdy nie odczytuje stdin, a tty: true powoduje, że wiele programów przełącza się na kolorowe wyjście i buforowanie liniowe, ponieważ zakładają, że są obserwowane przez użytkownika, co zapełnia docker compose logs kodami sterującymi.
Dlaczego polecenie exec w skryptach zawodzi w cron i CI: flaga -T
Polecenie exec, które działa w terminalu, kończy się niepowodzeniem w zadaniu cron lub w środowisku ciągłej integracji (CI):
the input device is not a TTYCompose domyślnie żąda pseudo-terminala, a cron nie przydziela zadaniom terminala, więc żądanie kończy się błędem, zanim polecenie zostanie uruchomione. -T wyłącza to żądanie:
0 3 * * * docker compose -f /srv/app/compose.yaml exec -T db pg_dump -U postgres -Fc app > /srv/backups/app.dump-T jest istotne z drugiego powodu. TTY modyfikuje strumień bajtów podczas przesyłania, więc skompresowany zrzut danych przechodzący przez terminal dociera uszkodzony. Każde przekierowanie lub potokowanie wyjścia wymaga użycia -T.
Dwie dodatkowe uwagi dotyczące cron. Należy przekazać -f wraz ze ścieżką bezwzględną, ponieważ cron uruchamia zadanie z katalogu domowego, w którym nie ma pliku compose, co powoduje zatrzymanie Compose z błędem no configuration file provided: not found. Ponadto exec zwraca kod wyjścia uruchomionego polecenia, więc niepowodzenie pg_dump spowoduje przerwanie skryptu przy użyciu set -e, zamiast utworzenia pustej kopii zapasowej i zgłoszenia sukcesu. Pozostałe codzienne polecenia zebrano w ściądze poleceń Compose, którą warto mieć pod ręką podczas pracy ze skryptami.
Dlaczego zmiany wprowadzone wewnątrz kontenera znikają
Instalacja narzędzia za pomocą exec, edycja pliku konfiguracyjnego i rozwiązanie problemu kończą się utratą zmian po tygodniu. Jest to zgodne z założeniami działania warstwy zapisu kontenera. docker compose up -d po każdej zmianie tagu obrazu lub definicji usługi niszczy stary kontener i tworzy nowy na podstawie obrazu, przez co każda ręczna edycja zostaje usunięta wraz ze starym kontenerem.
docker compose restart działa inaczej. Zatrzymuje i uruchamia ten sam kontener, więc ręczne edycje przetrwają tę operację. Dlatego ręczna poprawka może wydawać się trwała przez tygodnie, a następnie zniknąć podczas niezwiązanej z nią aktualizacji. Wolumeny nazwane oraz bind mounts przetrwają obie operacje, ponieważ ich dane znajdują się poza kontenerem, a bind mounts i wolumeny nazwane wyjaśnia, co wybrać dla danych, które mają zostać zachowane.
Należy traktować powłokę exec jako miejsce do odczytu i testowania. Po ustaleniu poprawki należy ją wprowadzić w sposób trwały: pakiet w Dockerfile, ustawienie w pliku compose. Następnie należy wykonać docker compose up -d, aby zastosować zmiany, i potwierdzić za pomocą kolejnego exec, że nowy kontener faktycznie je zawiera.
FAQ
Jaka jest różnica między docker compose exec a docker compose run?
Polecenie exec uruchamia komendę wewnątrz działającego kontenera, obok głównego procesu, i pomija entrypoint obrazu. Polecenie run tworzy nowy kontener na podstawie tej samej definicji usługi, używając tego samego obrazu, zmiennych środowiskowych, wolumenów i sieci, przekazuje komendę przez entrypoint oraz najpierw uruchamia usługi depends_on. Polecenie run nie publikuje portów usługi, chyba że dodasz flagę --service-ports. Używaj exec do inspekcji działającej usługi. Używaj run --rm, gdy usługa jest zatrzymana lub gdy nie chcesz zakłócać jej pracy.
Dlaczego docker compose exec zgłasza, że usługa nie działa?
Polecenie exec łączy się z istniejącym kontenerem i nie może go utworzyć, więc zatrzymana lub awaryjna usługa zwraca service "web" is not running. Sprawdź docker compose ps -a, które wyświetla listę kontenerów z zakończonym statusem, takim jak Exited (1), i odczytaj docker compose logs web, aby poznać przyczynę zatrzymania. Aby mimo to uzyskać dostęp do powłoki, uruchom docker compose run --rm --entrypoint sh web. Tworzy to nowy kontener z tej samej definicji usługi, nie uruchamiając błędnej komendy startowej.
Jak otworzyć powłokę, gdy obraz nie zawiera bash?
Błąd docker compose exec web bash z komunikatem exec: "bash": executable file not found in $PATH oznacza, że w obrazie brakuje bash, co jest typowe dla systemów opartych na Alpine. Użyj docker compose exec web sh, ponieważ BusyBox dostarcza /bin/sh. Obrazy typu Distroless oraz scratch w ogóle nie zawierają powłoki, więc żadna komenda exec nie zadziała. Przełącz się na tag :debug obrazu, jeśli wydawca go udostępnia, lub uruchom kontener diagnostyczny w przestrzeniach nazw celu za pomocą docker run --rm -it --network "container:$CID" --pid "container:$CID" nicolaka/netshoot, gdzie $CID pochodzi z docker compose ps -q web.
Dlaczego komenda exec kończy się błędem "the input device is not a TTY" w cron?
Polecenie docker compose exec domyślnie żąda pseudo-terminala, a cron go nie zapewnia, więc żądanie kończy się niepowodzeniem przed uruchomieniem komendy. Dodaj -T, aby wyłączyć tę funkcję: docker compose exec -T db pg_dump -U postgres app. Używaj -T również w przypadku przekierowań lub potoków, ponieważ TTY modyfikuje strumień bajtów i uszkadza zrzuty binarne. W cron należy również przekazać -f z pełną ścieżką do pliku compose, w przeciwnym razie Compose zakończy działanie z błędem no configuration file provided: not found.
Czy zmiany wprowadzone w kontenerze za pomocą exec przetrwają restart?
Zmiany przetrwają docker compose restart, ponieważ ponownie wykorzystuje on ten sam kontener. Zostaną utracone po docker compose up -d w przypadku zmiany obrazu lub konfiguracji, ponieważ proces ten odtwarza kontener z obrazu i odrzuca jego warstwę zapisu. Dane zapisane w nazwanych wolumenach lub bind mounts przetrwają oba przypadki, ponieważ znajdują się poza kontenerem. Wprowadzaj zmiany diagnostyczne za pomocą exec, a następnie umieść trwałą wersję w pliku Dockerfile lub pliku compose.