Docker Compose: verschil tussen command en entrypoint
Begrijp hoe Docker Compose de instructies command en entrypoint combineert. Leer waarom het instellen van entrypoint de CMD van uw image wist en hoe u dit correct overschrijft.
Docker Compose command versus entrypoint, in één regel
In Docker Compose stelt entrypoint: het programma in dat wordt uitgevoerd en stelt command: de argumenten in die aan dat programma worden meegegeven. Het proces van de container is de entrypoint-lijst met de command-lijst erachter geplaatst. Elk ander gedrag op deze pagina vloeit voort uit die ene zin.
Deze twee sleutels corresponderen met twee Dockerfile-instructies. entrypoint: vervangt de ENTRYPOINT van de image. command: vervangt de CMD van de image. Ze zijn niet onafhankelijk, en dat is waar gebruikers vastlopen: het instellen van entrypoint: gooit ook de CMD van de image weg. De Compose-specificatie stelt dit direct. Als entrypoint niet leeg is, negeert Compose elk standaardcommando van de image.
Lees wat de image al declareert
Voordat u wijzigingen overschrijft, moet u kijken wat de image meelevert.
docker image inspect --format '{{json .Config.Entrypoint}}' postgres:16
docker image inspect --format '{{json .Config.Cmd}}' postgres:16U krijgt ["docker-entrypoint.sh"] en ["postgres"], waardoor de container docker-entrypoint.sh postgres uitvoert. Dat script maakt bij de eerste opstart de datamap aan, leest de POSTGRES_* variabelen, verlaagt de rechten naar de postgres gebruiker en voert uiteindelijk de argumenten uit die zijn meegegeven. Weten welk deel u wilt wijzigen is de kern van de beslissing. Om een flag aan de database door te geven, vervangt u command:. Als u entrypoint: vervangt, wordt die volledige configuratie helemaal niet uitgevoerd.
De vier combinaties, getoond op een kleine afbeelding
Bouw een image waarvan de enige taak het printen van de argumentenlijst is waarmee het is gestart.
FROM alpine:3.20
ENTRYPOINT ["/bin/echo", "ep"]
CMD ["cmd"]docker build -t argdemo .services:
demo:
image: argdemoVoer docker compose up uit na elke wijziging en lees de enkele regel die het logt.
- Geen van beide keys ingesteld. Het proces is
/bin/echo ep cmden het logbestand toontep cmd. - Alleen
command: ["cmd2"]. Het proces is/bin/echo ep cmd2. De entrypoint blijft ongewijzigd en alleen de argumenten zijn aangepast. - Alleen
entrypoint: ["/bin/echo", "ep2"]. Het proces is/bin/echo ep2en het logbestand toontep2. Decmduit de image is verdwenen en er wordt geen waarschuwing gegeven. - Beide keys ingesteld. Het proces is
/bin/echo ep2 cmd2. Dit is het enige scenario waarin u de volledige argumentenlijst beheert.
Waarom het instellen van een entrypoint de CMD van de image wist
De CMD van een image is geschreven als de standaard argumentenlijst voor de ENTRYPOINT van die image. Vervang het entrypoint en die argumenten horen nu bij een programma dat niet langer draait; daarom laat Compose ze weg in plaats van een opdrachtregel te bouwen die de auteur van de image nooit zo bedoeld heeft. docker run --entrypoint gedraagt zich op dezelfde manier, dus dit is Docker-gedrag en geen eigenaardigheid van Compose.
Het gevolg is concreet. nginx:1.27 declareert ENTRYPOINT ["/docker-entrypoint.sh"] en CMD ["nginx", "-g", "daemon off;"]. Stel entrypoint: /custom-init.sh in en uw script start met een lege argumentenlijst. Een script dat eindigt met het gebruikelijke exec "$@" heeft dan niets om uit te voeren, dus exec doet niets, het script bereikt de laatste regel en de container stopt met code 0 zonder enige foutmelding. Voeg de argumenten zelf weer toe:
services:
web:
image: nginx:1.27
entrypoint: /custom-init.sh
command: ["nginx", "-g", "daemon off;"]De regel om te onthouden: wanneer u entrypoint: instelt, bepaal dan in dezelfde bewerking wat command: moet zijn.
Exec-vorm en shell-vorm, en de verschillen in Compose
Een Dockerfile ondersteunt twee syntactische vormen. CMD ["nginx", "-g", "daemon off;"] is de exec-vorm: het binaire bestand wordt direct uitgevoerd, zonder tussenkomst van een shell. CMD nginx -g "daemon off;" is de shell-vorm: Docker herschrijft dit als /bin/sh -c 'nginx -g "daemon off;"', waardoor er eerst een shell wordt gestart en uw programma als kindproces fungeert.
Compose neemt deze regel niet over, wat vaak tot verrassingen leidt. Een string in command: wordt gesplitst in argumenten en direct uitgevoerd, zonder /bin/sh -c-wrapper. De documentatie van Compose is hier expliciet over: het command-veld wordt niet uitgevoerd binnen de SHELL-context die in de image is gedefinieerd. Als u shell-functionaliteiten nodig heeft, moet u zelf een shell aanroepen.
Dit is de reden waarom command: echo "hello $$HOSTNAME" de letterlijke tekst hello $HOSTNAME afdrukt. Geen enkele shell heeft de string verwerkt, dus er heeft geen expansie plaatsgevonden. Roep expliciet een shell aan wanneer u deze nodig heeft:
services:
demo:
image: alpine:3.20
command: /bin/sh -c 'echo "hello $$HOSTNAME"'Signalen, PID 1 en een correcte docker compose down
docker compose stop en docker compose down sturen SIGTERM naar PID 1 in elke container, wachten op stop_grace_period en sturen daarna SIGKILL. De standaard respijtperiode is 10 seconden.
PID 1 is in Linux een speciaal proces. De kernel past de standaardactie van een signaal niet toe op PID 1. Een proces zonder SIGTERM-handler negeert daarom simpelweg SIGTERM wanneer het als PID 1 draait. Het blijft de volledige respijtperiode actief en wordt daarna geforceerd beëindigd, wat openstaande verbindingen of niet-voltooide transacties direct afbreekt.
Een shell voor uw programma maakt dit waarschijnlijker, omdat de shell PID 1 is en de meeste shells signalen niet doorsturen naar een kindproces. Sommige shells vervangen zichzelf door het laatste commando in een -c-string, waardoor uw programma soms toch PID 1 bereikt. Dit hangt af van de shell en de exacte string; ga er niet vanuit. Controleer het:
docker compose exec -T web cat /proc/1/cmdline | tr '\0' ' '; echoAls PID 1 wordt weergegeven als /bin/sh -c ... in plaats van uw programma, zijn er twee oplossingen. Gebruik de exec-vorm in de image, of behoud de shell en draag het proces over met exec:
services:
web:
image: myapp:1.4
command: /bin/sh -c 'exec myapp --config /etc/myapp.toml'exec vervangt het shell-proces door uw programma in plaats van een kindproces te forken. Uw programma erft hierdoor PID 1 en ontvangt het signaal.
Sommige programma's starten kindprocessen en ruimen deze nooit op, wat leidt tot zombieprocessen, omdat PID 1 ook verantwoordelijk is voor het opruimen (reaping). Compose heeft hiervoor een schakelaar:
services:
web:
image: myapp:1.4
init: true
stop_grace_period: 30sinit: true voert een klein init-proces uit als PID 1 dat signalen doorstuurt naar uw proces en kindprocessen opruimt. stop_grace_period geeft een daadwerkelijk trage afsluiting meer tijd. Als uw programma een ander signaal verwacht, wijzigt stop_signal: SIGQUIT wat Compose verstuurt. Lees wat een image al vereist met docker image inspect --format '{{.Config.StopSignal}}' nginx:1.27.
Een stack waarbij docker compose down altijd tien seconden per service duurt, geeft aan dat niets SIGTERM afhandelt. Los dit op voordat u de tooling de schuld geeft, en raadpleeg het verschil tussen docker compose down en stop voor wat elk subcommando precies verwijdert.
Dezelfde exec-versus-shell splitsing komt op nog een plek voor. Een healthcheck geschreven als test: ["CMD", "curl", "-f", "http://localhost/"] voert het binaire bestand direct uit, terwijl test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"] via een shell draait zodat || betekenis heeft. Compose healthchecks schrijven die correct falen behandelt de rest van dat onderwerp.
Een vlag toevoegen aan een officiële image
Dit is waar de meeste lezers voor komen. U wilt één extra vlag toevoegen aan postgres en u mag het initialisatiescript niet verstoren.
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:Alleen command: is gewijzigd, dus docker-entrypoint.sh wordt nog steeds uitgevoerd en voert nog steeds uit wat u heeft opgegeven. Controleer het resultaat in plaats van er zomaar vanuit te gaan:
docker compose up -d db
docker compose exec -T db psql -U postgres -c 'show max_connections;'De uitvoer hoort 200 te tonen. Als er nog steeds 100 staat, voer dan docker compose config uit en bevestig dat de command die u verwacht in de samengevoegde uitvoer staat. Compose-merges overschrijven bestanden door command volledig te vervangen, niet door eraan toe te voegen. Een tweede bestand dat ook command: instelt, wint daarom stilletjes.
De ${POSTGRES_PASSWORD} hierboven wordt door Compose op de host uitgebreid vanuit uw .env-bestand, nog voordat de container bestaat. Omgevingsbestanden en secrets in Compose behandelt waar die waarde veilig kan worden opgeslagen.
Een eenmalige migratie uitvoeren met docker compose run
docker compose run bouwt een nieuwe container op basis van dezelfde servicedefinitie en vervangt het commando door wat u na de servicenaam typt. Het entrypoint van de image wordt nog steeds uitgevoerd, waardoor de container exact zo wordt voorbereid als de langlopende service.
docker compose run --rm app python manage.py migrate--rmverwijdert de container zodra het commando is afgerond. Zonder deze vlag laat elke uitvoering een gestopte container achter, die zichtbaar is indocker compose ps -a.- Poorten worden niet gepubliceerd. Een
run-container negeert deports:van de service, tenzij u--service-portstoevoegt; hierdoor kan er geen conflict ontstaan met de service die al actief is. - Afhankelijkheden starten eerst. Alles in
depends_onwordt opgestart vóór uw commando, tenzij u--no-depsgebruikt om dit over te slaan. - De container krijgt een gegenereerde naam zoals
myproject-app-run-9f2c1a, waardoor deze nooit botst met de servicecontainer.
Om ook het entrypoint te vervangen, is er een specifieke vlag beschikbaar:
docker compose run --rm --entrypoint /bin/sh app -c 'python manage.py migrate'De resulterende argumentenlijst is /bin/sh -c 'python manage.py migrate', omdat de woorden na de servicenaam nog steeds als commando worden beschouwd. docker compose exec is de andere tool en werkt anders: deze voert een proces uit binnen een container die al draait en negeert zowel entrypoint: als command: volledig. Gebruik run voor een taak die een nieuwe container vereist, en exec om in een actieve container te kijken. De Compose command cheat sheet zet de overige subcommando's naast elkaar.
Waarom stopt mijn container direct?
Begin bij de exitcode, omdat dit de oorzaak snel vernauwt.
docker compose ps -a
docker compose logs appExitcode 0 en geen output. Het commando is uitgevoerd en voltooid. De meest voorkomende oorzaak is een entrypoint:-override die de CMD van de image heeft overschreven, waardoor de entrypoint met een lege argumentenlijst draaide en geen instructies had om uit te voeren.
Een foutmelding die eindigt op permission denied. Het script heeft geen executable-bit binnen de image, meestal omdat dit bit nooit is ingesteld voor het bestand in de repository. Stel dit in tijdens het build-proces met COPY --chmod=0755 entrypoint.sh /entrypoint.sh.
Een foutmelding die eindigt op no such file or directory voor een bestand dat u duidelijk in de image ziet staan. Het script bevat Windows-regeleinden. De eerste regel wordt dan gelezen als #!/bin/sh inclusief een carriage return-byte, waardoor de kernel zoekt naar een interpreter met die byte in de naam en niets vindt. Voer dos2unix entrypoint.sh uit en voeg daarna * text eol=lf toe aan .gitattributes zodat dit niet opnieuw kan gebeuren.
executable file not found in $PATH. Het binaire bestand dat in command: is opgegeven, staat niet in de image, of u heeft een shell built-in zoals cd gebruikt op een plek waar alleen een echt programma kan staan.
Een shell verkrijgen in een image waarvan het entrypoint faalt
Wanneer het entrypoint stopt voordat u iets kunt inspecteren, vervangt u het:
docker compose run --rm --entrypoint /bin/sh appAls dit executable file not found in $PATH retourneert, bevat de image helemaal geen shell. Distroless- en scratch-gebaseerde images bevatten deze vaak niet. U kunt het bestandssysteem nog steeds van buitenaf lezen zonder het entrypoint te starten:
docker create --name probe myapp:1.4
docker export probe | tar -tv | head -40
docker rm probeWanneer u wilt dat de container actief blijft zodat u er herhaaldelijk aan kunt koppelen, parkeert u deze op een proces dat nooit eindigt. Plaats dit in een override-bestand dat u niet commit:
services:
app:
entrypoint: ["tail", "-f", "/dev/null"]
command: []command: [] is niet strikt noodzakelijk, aangezien het instellen van entrypoint: het CMD van de image al heeft gewist, maar het opschrijven ervan legt de intentie vast voor de volgende persoon die het bestand leest. Start de container en stap naar binnen:
docker compose -f compose.yaml -f compose.debug.yaml up -d app
docker compose exec app /bin/shVoer nu het echte entrypoint handmatig uit en kijk waar het stopt. Dit geeft u de foutmelding in uw terminal in plaats van in een container die een halve seconde geleden is gestopt. Als u nog bezig bent met het samenstellen van uw eerste stack, behandelt een eerste Compose-stack op een VPS de bestandsindeling waar alles hierboven van uitgaat.
FAQ
Waarom stopt mijn container direct na docker compose up?
Controleer docker compose ps -a voor de exitcode. Exit 0 zonder output betekent meestal dat u entrypoint: heeft ingesteld op de service, waardoor ook de CMD van de image is gewist. Hierdoor werd de entrypoint uitgevoerd met een lege argumentenlijst en direct beëindigd. Voeg de argumenten opnieuw toe met command:. Een foutmelding die eindigt op permission denied betekent dat het entrypoint-script geen executable bit heeft. Een foutmelding die eindigt op no such file or directory voor een bestand dat wel bestaat, betekent dat het script Windows-regeleinden bevat, waardoor de shebang-regel verwijst naar een interpreter die niet aanwezig is.
Verwijdert het instellen van entrypoint in Compose de CMD van de image?
Ja. Als entrypoint niet leeg is, negeert Compose elk standaardcommando dat in de image is gedeclareerd. Dit is gedocumenteerd gedrag en komt overeen met docker run --entrypoint. De reden hiervoor is dat de CMD van een image is geschreven als argumenten voor de ENTRYPOINT van die image; zodra u het entrypoint vervangt, horen de oude argumenten nergens meer bij. Stel command: in bij dezelfde service als het nieuwe entrypoint nog argumenten nodig heeft.
Wordt een string in een Compose-commando uitgevoerd via een shell?
Nee. In tegenstelling tot een Dockerfile CMD, wordt een string in command: van Compose gesplitst in argumenten en direct uitgevoerd, zonder /bin/sh -c-wrapper. Daarom wordt $VARIABLE nooit geëxpandeerd door een shell in de container. Roep zelf de shell aan wanneer u er een nodig heeft, zoals in command: /bin/sh -c 'echo "hello $$HOSTNAME"'. De dubbele $$ ontsnapt het dollarteken, zodat Compose dit doorgeeft aan de container in plaats van het op de host te expanderen.
Waarom duurt docker compose down tien seconden voor één container?
Compose stuurt SIGTERM naar PID 1, wacht stop_grace_period (standaard 10 seconden) en stuurt daarna SIGKILL. De kernel past geen standaard signaalacties toe op PID 1, dus een programma zonder SIGTERM-handler negeert het signaal en wacht altijd de volledige periode af. Achterhaal wat PID 1 werkelijk is met docker compose exec -T app cat /proc/1/cmdline | tr '\0' ' '. Als dit een shell is, schakel de image dan over naar de exec-vorm of schrijf exec in de shell-string. Als het proces sub-processen start die het nooit opruimt, stel dan init: true in op de service.