Docker Compose: command vs entrypoint, wetin run?
ENTRYPOINT na the program wey go run, while command supplies arguments. See all 4 Compose override combinations and why setting entrypoint clears CMD.
Docker Compose command versus entrypoint, for one rule
For Docker Compose, entrypoint: dey set the program wey go run, while command: dey set the arguments wey dem go pass give that program. The container process na the entrypoint list plus the command list wey dem append for the end. Every other behaviour for this page come from that one sentence.
Those two keys map to two Dockerfile instructions. entrypoint: dey replace the image ENTRYPOINT. command: dey replace the image CMD. Dem no independent, and na here people dey get stuck: when you set entrypoint:, e also throw away the image CMD. The Compose specification talk am directly. If entrypoint no be null, Compose go ignore any default command wey dey come from the image.
Image already declare wetin
Before you override anything, first check wetin the image ships.
docker image inspect --format '{{json .Config.Entrypoint}}' postgres:16
docker image inspect --format '{{json .Config.Cmd}}' postgres:16You get ["docker-entrypoint.sh"] and ["postgres"], so the container dey run docker-entrypoint.sh postgres. That script dey create the data directory for first boot, read the POSTGRES_* variables, drop privileges to the postgres user, then exec the arguments wey dem give am. To know which part you wan change na the main decision. To pass flag go the database, replace command:. If you replace entrypoint:, none of that setup go run at all.
The four combinations, wey show for small image
Build image wey only job na to print argument list wey dem start am with.
FROM alpine:3.20
ENTRYPOINT ["/bin/echo", "ep"]
CMD ["cmd"]docker build -t argdemo .services:
demo:
image: argdemoRun docker compose up after every edit, then read the one line wey e log.
- No key set. Process na
/bin/echo ep cmdand log showep cmd. command: ["cmd2"]only. Process na/bin/echo ep cmd2. Entrypoint no change, and na only arguments change.entrypoint: ["/bin/echo", "ep2"]only. Process na/bin/echo ep2and log showep2.cmdfrom the image don comot, and nothing warn you.- Both keys set. Process na
/bin/echo ep2 cmd2. Na only this case you control the complete argument list.
Why setting entrypoint dey clear image CMD
An image get CMD wey e write as the default argument list for that image ENTRYPOINT. If you replace the entrypoint, those arguments don belong to program wey no dey run again. So Compose drop dem instead of building command line wey image author no intend make e exist. docker run --entrypoint dey behave the same way, so na Docker behaviour be this, no be Compose quirk.
The result clear. nginx:1.27 declare ENTRYPOINT ["/docker-entrypoint.sh"] and CMD ["nginx", "-g", "daemon off;"]. Set entrypoint: /custom-init.sh, and your script go start with empty argument list. Script wey end with the usual exec "$@" go then get nothing to exec. So exec no do anything, script reach the last line, and container exit with code 0 without any error message anywhere. Put the arguments back by yourself:
services:
web:
image: nginx:1.27
entrypoint: /custom-init.sh
command: ["nginx", "-g", "daemon off;"]The rule wey you need remember be this: anytime you set entrypoint:, decide wetin command: suppose be for the same edit.
Exec form and shell form, àti bí Compose dey differ
Dockerfile dey accept syntax meji. CMD ["nginx", "-g", "daemon off;"] na exec form: binary go run direct, no shell dey involved. CMD nginx -g "daemon off;" na shell form: Docker go rewrite am as /bin/sh -c 'nginx -g "daemon off;"', so shell go run first and your program go become im child.
Compose no follow this same rule, and na wetin dey surprise people. String wey dey inside command: go split into arguments and execute direct, with no /bin/sh -c wrapper. Compose reference talk am clearly: command field no dey run inside SHELL context wey image define, so if you need shell features, you must invoke shell by yourself.
Na why command: echo "hello $$HOSTNAME" dey print literal text hello $HOSTNAME. No shell ever see the string, so nothing expand am. Ask for shell when you want one:
services:
demo:
image: alpine:3.20
command: /bin/sh -c 'echo "hello $$HOSTNAME"'Signals, PID 1, and docker compose down wey clean
docker compose stop and docker compose down dey send SIGTERM go PID 1 inside each container, wait for stop_grace_period, then send SIGKILL. Default grace period na 10 seconds.
PID 1 special for Linux. Kernel no dey apply default action of signal to PID 1, so process wey no install SIGTERM handler simply dey ignore SIGTERM when e run as PID 1. E go sit down for the full grace period, then system go kill am outright. This fit cut off any open connection or transaction wey never commit.
Shell wey dey in front of your program dey make this problem more likely, because shell na PID 1 and most shells no dey forward signals to child process. Some shells dey replace themselves with final command inside -c string, so sometimes your program still reach PID 1. E depend on the shell and the exact string, so no guess. Read am:
docker compose exec -T web cat /proc/1/cmdline | tr '\0' ' '; echoIf PID 1 print as /bin/sh -c ... instead of your program, get two fixes. Use exec form for the image, or keep the shell and hand the process over with exec:
services:
web:
image: myapp:1.4
command: /bin/sh -c 'exec myapp --config /etc/myapp.toml'exec dey replace the shell process with your program instead of forking child process, so your program inherit PID 1 and receive the signal.
Some programs dey spawn children but never reap dem. This one dey leave zombie processes, because PID 1 na also the reaper. Compose get switch for this:
services:
web:
image: myapp:1.4
init: true
stop_grace_period: 30sinit: true dey run small init process as PID 1. E dey forward signals to your process and reap children. stop_grace_period dey give shutdown wey really slow more time. If your program dey expect different signal, stop_signal: SIGQUIT dey change the signal wey Compose send. Read wetin image already request with docker image inspect --format '{{.Config.StopSignal}}' nginx:1.27.
Stack wey docker compose down dey always take ten seconds for each service dey show say nothing dey handle SIGTERM. Fix this one before you blame the tooling, and see difference between docker compose down and stop to know wetin each subcommand remove.
The same exec-versus-shell split dey show for one more place. Healthcheck wey dem write as test: ["CMD", "curl", "-f", "http://localhost/"] dey run binary directly, while test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"] dey run through shell so that || go mean something. How to write Compose healthchecks wey fail honestly cover the remaining part of that field.
Add one flag to official image
Na this most readers come for. You want add one extra flag to postgres, and you no want disturb the initialisation script.
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:Na only command: change, so docker-entrypoint.sh still dey run and still dey exec wetin you give am. Check the result instead make you assume say e correct:
docker compose up -d db
docker compose exec -T db psql -U postgres -c 'show max_connections;'The output suppose show 200. If e still show 100, run docker compose config and confirm say the command wey you expect dey inside the merged output. Compose dey merge override files by replacing command completely, no be by adding to am, so second file wey also set command: go silently win.
Compose dey expand the ${POSTGRES_PASSWORD} above for the host from your .env file, before the container dey exist. Env files and secrets for Compose explain where you fit safely keep that value.
Run one migration with docker compose run
docker compose run dey build new container from the same service definition, then e replace the command with anything wey you type after the service name. The image entrypoint still dey run, so the container dey prepared exactly like the long-running one.
docker compose run --rm app python manage.py migrate--rmdey delete the container when the command finish. If you no use am, every run go leave stopped container behind, and you go see am fordocker compose ps -a.- Ports no dey publish. A
runcontainer ignore the serviceports:unless you add--service-ports, so e no fit clash with the service wey already dey up. - Dependencies start first. Anything wey dey for
depends_ongo come up before your command, but--no-depsskip that. - The container get generated name like
myproject-app-run-9f2c1a, so e no go clash with the service container.
If you wan replace the entrypoint too, flag dey for am:
docker compose run --rm --entrypoint /bin/sh app -c 'python manage.py migrate'The argument list wey result na /bin/sh -c 'python manage.py migrate', because the words after the service name still be the command. docker compose exec na the other tool, and e dey work differently: e dey run process inside container wey already dey up, and e ignore both entrypoint: and command: completely. Use run for task wey need fresh container, and exec to inspect live one. The Compose command cheat sheet put the remaining subcommands side by side.
Why my container dey exit immediately?
Start with the exit code, because e go quickly narrow down the cause.
docker compose ps -a
docker compose logs appExit code 0 and no output. The command run finish. The commonest cause na say entrypoint: override carry the image CMD comot, so entrypoint run with empty argument list and get nothing to hand over.
Error wey end with permission denied. The script no get executable bit inside the image. Most times, nobody set the bit for the file inside the repository. Set am during build with COPY --chmod=0755 entrypoint.sh /entrypoint.sh.
Error wey end with no such file or directory for file wey you fit clearly see inside the image. The script get Windows line endings. Because of this, the first line read as #!/bin/sh plus carriage return byte. So the kernel search for interpreter wey get that byte for the name, but e no find am. Run dos2unix entrypoint.sh, then add * text eol=lf to .gitattributes so the problem no come back.
executable file not found in $PATH. The binary wey command: name no dey inside the image, or you write shell built-in like cd where only real program fit work.
Enter shell inside image wey entrypoint dey fail
When entrypoint die before you fit inspect anything, replace am:
docker compose run --rm --entrypoint /bin/sh appIf that return executable file not found in $PATH, the image no get shell at all. Distroless and scratch based images often no ship one. You still fit read the filesystem from outside without starting the entrypoint:
docker create --name probe myapp:1.4
docker export probe | tar -tv | head -40
docker rm probeWhen you need make the container stay up so you fit attach to am repeatedly, park am on top process wey no dey exit. Put this inside override file wey you no go commit:
services:
app:
entrypoint: ["tail", "-f", "/dev/null"]
command: []command: [] no strictly necessary, because setting entrypoint: don already clear the image's CMD, but writing am records the intention for whoever read the file next. Bring am up and enter inside:
docker compose -f compose.yaml -f compose.debug.yaml up -d app
docker compose exec app /bin/shNow run the real entrypoint by hand and monitor where e stop. This go show the error message for your terminal instead of inside container wey don die half a second ago. If you still dey assemble your first stack, first Compose stack for VPS explains the file layout wey everything above dey assume.
FAQ
Why my container dey exit immediately after docker compose up?
Check docker compose ps -a for the exit code. Exit 0 with no output usually mean say you set entrypoint: for the service. This also clear the image CMD, so the entrypoint run with empty argument list and finish. Add the arguments back with command:. Error wey end with permission denied mean say the entrypoint script no get executable bit. Error wey end with no such file or directory for file wey dey exist mean say the script get Windows line endings. So, the shebang line name interpreter wey no dey available.
If I set entrypoint for Compose, e go remove the image CMD?
Yes. If entrypoint no be null, Compose ignore any default command wey the image declare. This na documented behaviour and e match docker run --entrypoint. The reason be say image CMD dey written as arguments for that image ENTRYPOINT. Once you replace the entrypoint, the old arguments no longer get anything to belong to. Set command: for the same service if the new entrypoint still need arguments.
If command for Compose na string, shell go run am?
No. Unlike Dockerfile CMD, string for Compose command: dey split into arguments and execute directly, without any /bin/sh -c wrapper. So shell inside the container no go expand $VARIABLE. Call the shell yourself when you need one, like for command: /bin/sh -c 'echo "hello $$HOSTNAME"'. The doubled $$ escape the dollar sign, so Compose pass am through to the container instead of expanding am for the host.
Why docker compose down dey take ten seconds for one container?
Compose send SIGTERM to PID 1, wait for stop_grace_period (10 seconds by default), then send SIGKILL. The kernel no apply default signal actions to PID 1. So program wey no get SIGTERM handler ignore the signal and always wait for the full period. Find out wetin PID 1 really be with docker compose exec -T app cat /proc/1/cmdline | tr '\0' ' '. If na shell, change the image to exec form or write exec inside the shell string. If the process dey spawn children wey e never reap, set init: true for the service.