Both entrypoint and command define what runs when a container starts. They look similar, but they play different roles, and confusing them leads to surprising behavior.
The mental model
When a container starts, Docker runs:
<entrypoint> <command>
entrypointis the executablecommandis the default arguments passed to it
If the image’s Dockerfile has ENTRYPOINT ["python"] and CMD ["app.py"], the container runs python app.py.
Overriding from Compose
Both can be overridden in Compose:
services:
app:
image: python:3.12
entrypoint: ["python"]
command: ["app.py", "--debug"]
This runs python app.py --debug regardless of what the image’s Dockerfile defined.
When you typically only set command
Most of the time, you just want to run a different command on an image:
services:
# Run tests instead of the default app
tests:
image: myapp
command: ["pytest", "tests/"]
# Run a one-off migration
migrate:
image: myapp
command: ["./migrate.sh"]
The image’s ENTRYPOINT (often python, node, or /entrypoint.sh) handles the executable, and you just pass different arguments.
When you set entrypoint
Override entrypoint when you want to bypass the image’s default executable:
services:
# Image normally starts the app, but here we want a shell
debug:
image: myapp
entrypoint: ["/bin/sh"]
command: ["-c", "env && tail -f /dev/null"]
To completely disable the image’s entrypoint and run a fresh command:
services:
app:
image: myapp
entrypoint: []
command: ["echo", "hello from compose"]
Exec form vs shell form
There are two syntaxes:
# Exec form (recommended) — array, runs directly
command: ["python", "app.py"]
# Shell form — string, runs through /bin/sh -c
command: python app.py
Exec form is preferred because:
- Signals (SIGTERM, etc.) reach your process directly (Tip #44)
- No shell expansion gotchas
- Faster startup (one less process)
Use shell form only when you need shell features:
command: bash -c "wait-for-db && python app.py"
Override at runtime
docker compose run lets you override both at runtime:
# Override command
docker compose run --rm app pytest tests/
# Override entrypoint with --entrypoint
docker compose run --rm --entrypoint /bin/sh app
This is the cleanest way to debug a misbehaving service (Tip #34).
A common gotcha
If you define command: in Compose but the image already has an ENTRYPOINT that doesn’t expect arguments, things break:
# Dockerfile
ENTRYPOINT ["/start.sh"]
# compose.yml
services:
app:
image: myapp
command: ["bash"] # Becomes: /start.sh bash — probably not what you want
Either set entrypoint: [] to clear the image’s entrypoint, or use the existing entrypoint as designed.