Docker Compose: command a entrypoint – jak nadpisywać
Dowiedz się, jak poprawnie konfigurować parametry command oraz entrypoint w Docker Compose. Sprawdź, dlaczego ustawienie entrypoint usuwa domyślne CMD i poznaj cztery kombinacje.
Docker Compose: command a entrypoint – jedna zasada
W Docker Compose entrypoint: określa uruchamiany program, a command: definiuje argumenty przekazywane do tego programu. Proces kontenera stanowi lista entrypoint z dołączoną na końcu listą command. Każde inne zachowanie opisane na tej stronie wynika z tego jednego zdania.
Te dwa klucze odpowiadają dwóm instrukcjom w Dockerfile. entrypoint: zastępuje ENTRYPOINT zdefiniowane w obrazie. command: zastępuje CMD zdefiniowane w obrazie. Nie są one niezależne i to właśnie tutaj użytkownicy napotykają problemy: ustawienie entrypoint: powoduje również odrzucenie CMD z obrazu. Specyfikacja Compose stwierdza to wprost. Jeśli entrypoint nie jest puste, Compose ignoruje domyślne polecenie (command) zdefiniowane w obrazie.
Odczyt deklaracji obrazu
Zanim nadpiszesz jakiekolwiek ustawienia, sprawdź, co zawiera obraz.
docker image inspect --format '{{json .Config.Entrypoint}}' postgres:16
docker image inspect --format '{{json .Config.Cmd}}' postgres:16Otrzymujesz ["docker-entrypoint.sh"] oraz ["postgres"], dzięki czemu kontener uruchamia docker-entrypoint.sh postgres. Ten skrypt tworzy katalog danych przy pierwszym uruchomieniu, odczytuje zmienne POSTGRES_*, obniża uprawnienia do użytkownika postgres i ostatecznie wykonuje przekazane argumenty. Kluczową decyzją jest określenie, którą część chcesz zmodyfikować. Aby przekazać flagę do bazy danych, zastąp command:. Jeśli zastąpisz entrypoint:, cały proces konfiguracji nie zostanie wykonany.
Cztery kombinacje przedstawione na niewielkim obrazie
Zbuduj obraz, którego jedynym zadaniem jest wypisanie listy argumentów, z którymi został uruchomiony.
FROM alpine:3.20
ENTRYPOINT ["/bin/echo", "ep"]
CMD ["cmd"]docker build -t argdemo .services:
demo:
image: argdemoUruchom docker compose up po każdej edycji i odczytaj pojedynczą linię, którą zapisuje w dzienniku.
- Żaden klucz nie jest ustawiony. Proces to
/bin/echo ep cmd, a dziennik pokazujeep cmd. - Tylko
command: ["cmd2"]. Proces to/bin/echo ep cmd2. Entrypoint pozostaje niezmieniony, a zmianie uległy tylko argumenty. - Tylko
entrypoint: ["/bin/echo", "ep2"]. Proces to/bin/echo ep2, a dziennik pokazujeep2.cmdz obrazu znika, a system nie wyświetla żadnego ostrzeżenia. - Oba klucze ustawione. Proces to
/bin/echo ep2 cmd2. Jest to jedyny przypadek, w którym użytkownik kontroluje całą listę argumentów.
Dlaczego ustawienie entrypoint czyści CMD obrazu
CMD obrazu jest zapisywane jako domyślna lista argumentów dla ENTRYPOINT tego obrazu. Zastąpienie punktu wejścia (entrypoint) sprawia, że te argumenty odnoszą się do programu, który nie jest już uruchomiony, więc Compose je odrzuca zamiast tworzyć linię poleceń, której autor obrazu nie przewidział. docker run --entrypoint zachowuje się w ten sam sposób, jest to zatem zachowanie Docker, a nie specyfika Compose.
Konsekwencja jest konkretna. nginx:1.27 deklaruje ENTRYPOINT ["/docker-entrypoint.sh"] oraz CMD ["nginx", "-g", "daemon off;"]. Ustaw entrypoint: /custom-init.sh, a Twój skrypt uruchomi się z pustą listą argumentów. Skrypt kończący się standardowym exec "$@" nie ma wtedy czego wykonać, więc exec nie robi nic, skrypt dociera do ostatniej linii, a kontener kończy działanie z kodem 0 bez żadnego komunikatu o błędzie. Przywróć argumenty samodzielnie:
services:
web:
image: nginx:1.27
entrypoint: /custom-init.sh
command: ["nginx", "-g", "daemon off;"]Zasada, którą należy zapamiętać: za każdym razem, gdy ustawiasz entrypoint:, zdecyduj w tej samej edycji, jakie powinno być command:.
Forma exec i forma shell oraz różnice w Compose
Dockerfile akceptuje dwie składnie. CMD ["nginx", "-g", "daemon off;"] to forma exec: plik binarny uruchamia się bezpośrednio, bez udziału powłoki. CMD nginx -g "daemon off;" to forma shell: Docker przepisuje ją jako /bin/sh -c 'nginx -g "daemon off;"', więc najpierw uruchamia się powłoka, a program staje się jej procesem potomnym.
Compose nie kopiuje tej zasady, co bywa zaskakujące. Ciąg znaków w command: jest dzielony na argumenty i wykonywany bezpośrednio, bez otoczki /bin/sh -c. Dokumentacja Compose jest w tej kwestii jednoznaczna: pole command nie działa w kontekście SHELL zdefiniowanym w obrazie, więc jeśli wymagane są funkcje powłoki, należy wywołać ją samodzielnie.
Dlatego command: echo "hello $$HOSTNAME" wypisuje dosłowny tekst hello $HOSTNAME. Żadna powłoka nie przetworzyła tego ciągu, więc nie nastąpiła żadna ekspansja. Należy jawnie zażądać powłoki, gdy jest ona potrzebna:
services:
demo:
image: alpine:3.20
command: /bin/sh -c 'echo "hello $$HOSTNAME"'Sygnały, PID 1 oraz poprawne zatrzymywanie docker compose down
docker compose stop oraz docker compose down wysyłają SIGTERM do procesu o PID 1 wewnątrz każdego kontenera, czekają stop_grace_period, a następnie wysyłają SIGKILL. Domyślny okres karencji wynosi 10 sekund.
PID 1 ma w systemie Linux specjalne znaczenie. Jądro nie stosuje domyślnej akcji sygnału dla PID 1, więc proces, który nie posiada zdefiniowanego handlera SIGTERM, po prostu ignoruje SIGTERM, gdy działa jako PID 1. Proces pozostaje aktywny przez cały okres karencji, po czym jest natychmiastowo ubijany, co przerywa wszelkie otwarte połączenia lub niezatwierdzone transakcje.
Powłoka uruchomiona przed programem zwiększa prawdopodobieństwo wystąpienia tego problemu, ponieważ to powłoka jest procesem PID 1 i większość powłok nie przekazuje sygnałów do procesów potomnych. Niektóre powłoki zastępują sam proces końcowym poleceniem w ciągu -c, więc czasami program mimo wszystko otrzymuje PID 1. Zależy to od powłoki oraz dokładnej składni polecenia, dlatego nie należy zgadywać. Należy to sprawdzić:
docker compose exec -T web cat /proc/1/cmdline | tr '\0' ' '; echoJeśli jako PID 1 wyświetla się /bin/sh -c ... zamiast właściwego programu, istnieją dwa rozwiązania. Należy użyć formy exec w obrazie lub zachować powłokę i przekazać proces za pomocą exec:
services:
web:
image: myapp:1.4
command: /bin/sh -c 'exec myapp --config /etc/myapp.toml'exec zastępuje proces powłoki programem zamiast tworzyć proces potomny, dzięki czemu program przejmuje PID 1 i otrzymuje sygnał.
Niektóre programy uruchamiają procesy potomne i nigdy ich nie usuwają, co prowadzi do powstawania procesów zombie, ponieważ PID 1 odpowiada również za ich sprzątanie. Docker Compose posiada przełącznik rozwiązujący ten problem:
services:
web:
image: myapp:1.4
init: true
stop_grace_period: 30sinit: true uruchamia niewielki proces init jako PID 1, który przekazuje sygnały do procesu głównego i sprząta procesy potomne. stop_grace_period zapewnia więcej czasu na powolne zamykanie usług. Jeśli program oczekuje innego sygnału, stop_signal: SIGQUIT zmienia sygnał wysyłany przez Docker Compose. Warto sprawdzić, czego wymaga dany obraz za pomocą docker image inspect --format '{{.Config.StopSignal}}' nginx:1.27.
Stos, w którym docker compose down zawsze zajmuje dziesięć sekund na usługę, oznacza, że żaden proces nie obsługuje SIGTERM. Należy to naprawić przed obwinianiem narzędzi. Szczegółowe informacje o tym, co usuwa każde z podpoleceń, znajdują się w różnica między docker compose down a stop.
Ten sam podział na formę exec i shell pojawia się w jeszcze jednym miejscu. Healthcheck zapisany jako test: ["CMD", "curl", "-f", "http://localhost/"] uruchamia plik binarny bezpośrednio, podczas gdy test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"] uruchamia go przez powłokę, dzięki czemu || ma określone znaczenie. Pisanie healthchecków w Compose, które kończą się wiarygodnym błędem omawia pozostałe aspekty tego zagadnienia.
Dodawanie flagi do oficjalnego obrazu
To jest cel większości czytelników. Wymagane jest dodanie jednej dodatkowej flagi do postgres, przy jednoczesnym zachowaniu nienaruszonego skryptu inicjalizacyjnego.
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:Zmieniono tylko command:, więc docker-entrypoint.sh nadal działa i wykonuje przekazane polecenie. Należy sprawdzić wynik zamiast zakładać jego poprawność:
docker compose up -d db
docker compose exec -T db psql -U postgres -c 'show max_connections;'Dane wyjściowe powinny wskazywać 200. Jeśli nadal widoczne jest 100, należy uruchomić docker compose config i potwierdzić, że oczekiwane command znajduje się w scalonej konfiguracji. Compose scala pliki nadpisujące poprzez całkowite zastąpienie command, a nie przez dopisywanie do niego, więc drugi plik, który również ustawia command:, wygrywa bez ostrzeżenia.
Powyższe ${POSTGRES_PASSWORD} jest rozwijane przez Compose na hoście z pliku .env, zanim kontener zostanie utworzony. Sekcja Pliki środowiskowe i sekrety w Compose opisuje, gdzie taka wartość może być bezpiecznie przechowywana.
Uruchamianie jednorazowej migracji za pomocą docker compose run
docker compose run buduje nowy kontener na podstawie tej samej definicji usługi i zastępuje polecenie tym, które wpiszesz po nazwie usługi. Punkt wejścia (entrypoint) obrazu nadal jest wykonywany, więc kontener jest przygotowany dokładnie tak samo, jak ten działający w trybie ciągłym.
docker compose run --rm app python manage.py migrate--rmusuwa kontener po zakończeniu polecenia. Bez tej flagi każde uruchomienie pozostawia zatrzymany kontener, widoczny wdocker compose ps -a.- Porty nie są publikowane. Kontener
runignoruje sekcjęports:usługi, chyba że dodasz--service-ports, dzięki czemu nie dochodzi do konfliktów z już działającą usługą. - Zależności są uruchamiane w pierwszej kolejności. Wszystko zdefiniowane w
depends_onuruchamia się przed Twoim poleceniem, chyba że użyjesz--no-deps, co pomija ten krok. - Kontener otrzymuje wygenerowaną nazwę, taką jak
myproject-app-run-9f2c1a, więc nigdy nie wchodzi w konflikt z kontenerem usługi.
Aby zastąpić również punkt wejścia, służy do tego odpowiednia flaga:
docker compose run --rm --entrypoint /bin/sh app -c 'python manage.py migrate'Wynikowa lista argumentów to /bin/sh -c 'python manage.py migrate', ponieważ słowa po nazwie usługi są nadal traktowane jako polecenie. docker compose exec to inne narzędzie, które działa inaczej: uruchamia proces wewnątrz kontenera, który już działa, i całkowicie ignoruje zarówno entrypoint:, jak i command:. Używaj run do zadań wymagających świeżego kontenera, a exec do zaglądania do wnętrza działającego kontenera. Ściąga z poleceniami Compose zestawia pozostałe podpolecenia obok siebie.
Dlaczego mój kontener natychmiast kończy działanie?
Należy zacząć od kodu wyjścia, ponieważ pozwala on szybko zawęzić przyczynę problemu.
docker compose ps -a
docker compose logs appKod wyjścia 0 i brak wyjścia. Polecenie zostało wykonane i zakończone. Najczęstszą przyczyną jest nadpisanie entrypoint:, które usunęło CMD obrazu, przez co punkt wejścia (entrypoint) został uruchomiony z pustą listą argumentów i nie miał czego przekazać dalej.
Błąd kończący się permission denied. Skrypt nie posiada bitu wykonywalności wewnątrz obrazu, zazwyczaj dlatego, że nie został on ustawiony dla pliku w repozytorium. Należy ustawić go podczas budowania za pomocą COPY --chmod=0755 entrypoint.sh /entrypoint.sh.
Błąd kończący się no such file or directory dla pliku, który jest widoczny w obrazie. Skrypt posiada znaki końca linii w formacie Windows. Jego pierwsza linia jest odczytywana jako #!/bin/sh wraz z bajtem powrotu karetki, więc jądro szuka interpretera z tym bajtem w nazwie i go nie znajduje. Należy uruchomić dos2unix entrypoint.sh, a następnie dodać * text eol=lf do .gitattributes, aby problem nie powrócił.
executable file not found in $PATH. Plik binarny wskazany w command: nie znajduje się w obrazie lub użyto wbudowanego polecenia powłoki, takiego jak cd, w miejscu, gdzie wymagany jest rzeczywisty program.
Uzyskiwanie dostępu do powłoki w obrazie, którego punkt wejścia kończy się niepowodzeniem
Gdy punkt wejścia (entrypoint) kończy działanie, zanim możliwe będzie przeprowadzenie inspekcji, należy go zastąpić:
docker compose run --rm --entrypoint /bin/sh appJeśli polecenie to zwróci executable file not found in $PATH, obraz nie zawiera żadnej powłoki. Obrazy typu Distroless oraz oparte na scratch często nie zawierają powłoki. System plików można nadal odczytać z zewnątrz bez uruchamiania punktu wejścia:
docker create --name probe myapp:1.4
docker export probe | tar -tv | head -40
docker rm probeGdy wymagane jest, aby kontener pozostał uruchomiony w celu wielokrotnego podłączania się do niego, należy zaparkować go na procesie, który nigdy się nie kończy. Należy umieścić poniższy zapis w pliku nadpisującym (override file), którego nie zatwierdza się w systemie kontroli wersji:
services:
app:
entrypoint: ["tail", "-f", "/dev/null"]
command: []Parametr command: [] nie jest ściśle wymagany, ponieważ ustawienie entrypoint: już wyczyściło CMD obrazu, jednak jego zapisanie dokumentuje intencję dla kolejnych osób czytających plik. Należy uruchomić kontener i wejść do środka:
docker compose -f compose.yaml -f compose.debug.yaml up -d app
docker compose exec app /bin/shTeraz należy uruchomić właściwy punkt wejścia ręcznie i obserwować, w którym miejscu następuje zatrzymanie. Pozwala to na wyświetlenie komunikatu o błędzie bezpośrednio w terminalu, zamiast w kontenerze, który zakończył działanie ułamek sekundy wcześniej. Jeśli dopiero tworzysz swój pierwszy stos, pierwszy stos Compose na VPS opisuje strukturę plików, na której opierają się powyższe kroki.
FAQ
Dlaczego kontener wyłącza się natychmiast po wykonaniu docker compose up?
Sprawdź docker compose ps -a, aby poznać kod wyjścia. Kod 0 bez żadnego wyjścia zazwyczaj oznacza, że ustawiono entrypoint: dla usługi, co wyczyściło również CMD obrazu, więc punkt wejścia (entrypoint) został uruchomiony z pustą listą argumentów i zakończył działanie. Dodaj argumenty ponownie za pomocą command:. Błąd kończący się permission denied oznacza, że skrypt punktu wejścia nie posiada bitu wykonywalności. Błąd kończący się no such file or directory dla istniejącego pliku oznacza, że skrypt posiada znaki końca linii w formacie Windows, przez co linia shebang wskazuje na interpreter, który nie istnieje.
Czy ustawienie entrypoint w Compose usuwa CMD obrazu?
Tak. Jeśli entrypoint nie jest puste, Compose ignoruje domyślne polecenie zadeklarowane w obrazie. Jest to udokumentowane zachowanie, zgodne z docker run --entrypoint. Powodem jest to, że CMD obrazu jest zapisywane jako argumenty dla ENTRYPOINT tego obrazu, więc po zastąpieniu punktu wejścia stare argumenty przestają mieć przypisany proces. Ustaw command: w tej samej usłudze, jeśli nowy punkt wejścia nadal wymaga argumentów.
Czy ciąg znaków w poleceniu Compose jest uruchamiany przez powłokę?
Nie. W przeciwieństwie do CMD w Dockerfile, ciąg znaków w command: w Compose jest dzielony na argumenty i wykonywany bezpośrednio, bez otoczki /bin/sh -c. Dlatego $VARIABLE nigdy nie jest rozwijane przez powłokę wewnątrz kontenera. Wywołaj powłokę samodzielnie, gdy jest potrzebna, tak jak w command: /bin/sh -c 'echo "hello $$HOSTNAME"'. Podwojony znak $$ eskapuje znak dolara, dzięki czemu Compose przekazuje go do kontenera zamiast rozwijać go na hoście.
Dlaczego docker compose down trwa dziesięć sekund dla jednego kontenera?
Compose wysyła SIGTERM do procesu o PID 1, czeka przez stop_grace_period (domyślnie 10 sekund), a następnie wysyła SIGKILL. Jądro systemu nie stosuje domyślnych akcji sygnałów dla PID 1, więc program bez obsługi SIGTERM ignoruje sygnał i zawsze czeka przez pełny okres. Sprawdź, co faktycznie jest procesem PID 1 za pomocą docker compose exec -T app cat /proc/1/cmdline | tr '\0' ' '. Jeśli jest to powłoka, zmień formę uruchamiania obrazu na exec lub dopisz exec wewnątrz ciągu znaków powłoki. Jeśli proces tworzy procesy potomne, których nigdy nie kończy, ustaw init: true dla usługi.