Suggest an editImprove this articleRefine the answer for “CMD vs ENTRYPOINT in Dockerfile: what is the difference?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**`CMD` and `ENTRYPOINT`** both define what runs when a container starts. `CMD` is fully overridable by `docker run`. `ENTRYPOINT` is fixed; `CMD` becomes its default arguments. ```dockerfile ENTRYPOINT ["echo"] CMD ["hello"] # docker run img -> echo hello # docker run img bye -> echo bye (CMD replaced, ENTRYPOINT stays) # docker run --entrypoint /bin/sh img # -> /bin/sh (ENTRYPOINT explicitly overridden) ``` **Key:** use `ENTRYPOINT` when the image is one tool (like a CLI). Use `CMD` alone when the image is a service that takes no args. The combo `ENTRYPOINT + CMD` gives you a fixed command with replaceable defaults.Shown above the full answer for quick recall.Answer (EN)Image**`CMD` and `ENTRYPOINT`** are two Dockerfile instructions that both define what runs when a container starts. The difference shows up the moment a user passes arguments to `docker run`. ## Theory ### TL;DR - `CMD ["prog", "arg"]` = **default command, fully replaceable**. `docker run img otherCmd` swaps it out entirely. - `ENTRYPOINT ["prog"]` = **fixed first part of the command**. Anything after `docker run img ...` becomes its arguments (or `CMD` does if you do not pass any). - The combo `ENTRYPOINT + CMD` is the standard pattern for CLI-style images: fixed entrypoint, default args you can override. - **Use exec form** (`["prog", "arg"]`, JSON array). Shell form (`prog arg`) wraps everything in `/bin/sh -c`, which breaks signal handling. - `--entrypoint` on `docker run` overrides `ENTRYPOINT` itself; `CMD` is overridden just by trailing args. ### Quick example ```dockerfile FROM alpine:3.21 ENTRYPOINT ["echo"] CMD ["hello"] ``` ```bash $ docker build -t demo . $ docker run --rm demo hello # ENTRYPOINT (echo) + CMD (hello) $ docker run --rm demo bye bye # ENTRYPOINT (echo) + new CMD (bye) $ docker run --rm --entrypoint /bin/sh demo -c 'ls /' bin lib usr # ↑ ENTRYPOINT explicitly overridden; CMD becomes argv to /bin/sh ``` Three runs, three behaviors. The `ENTRYPOINT + CMD` split is what makes that flexibility possible. ### The four common patterns #### Pattern 1: `CMD` only — service with no args ```dockerfile FROM nginx:1.27-alpine CMD ["nginx", "-g", "daemon off;"] ``` ```bash $ docker run myimg -> nginx -g "daemon off;" $ docker run myimg sh -> sh (CMD fully replaced) ``` Use this when the image runs one fixed service and the user never needs to change the command. Most service images. #### Pattern 2: `ENTRYPOINT` only — image is a CLI tool ```dockerfile FROM alpine:3.21 RUN apk add --no-cache curl ENTRYPOINT ["curl"] ``` ```bash $ docker run myimg https://example.com # -> curl https://example.com $ docker run myimg --help # -> curl --help ``` The image **becomes** the command. Users pass curl flags directly to `docker run`. #### Pattern 3: `ENTRYPOINT + CMD` — fixed command with default args ```dockerfile FROM alpine:3.21 RUN apk add --no-cache curl ENTRYPOINT ["curl"] CMD ["--help"] ``` ```bash $ docker run myimg -> curl --help (default) $ docker run myimg https://example.com # -> curl https://example.com (override) ``` The canonical "image as a CLI with helpful default behavior" pattern. #### Pattern 4: shell form (avoid in production) ```dockerfile CMD nginx -g "daemon off;" # shell form ``` Docker silently wraps this as `/bin/sh -c 'nginx -g "daemon off;"'`. The actual PID 1 in the container is `/bin/sh`, not nginx. SIGTERM goes to sh, not your app. Result: `docker stop` waits the full grace period and then SIGKILLs. **Always use exec form (`["prog", "arg"]`)** for production images. ### Comparison table | Aspect | `CMD` only | `ENTRYPOINT` only | `ENTRYPOINT + CMD` | |---|---|---|---| | `docker run img` runs | the CMD | the ENTRYPOINT | ENTRYPOINT + CMD | | `docker run img foo bar` runs | `foo bar` | ENTRYPOINT + `foo bar` | ENTRYPOINT + `foo bar` | | `--entrypoint X img foo bar` runs | `X foo bar` | `X foo bar` | `X foo bar` | | Best for | services with no args | image-as-CLI | CLI with default args | ### Common mistakes **Using shell form and wondering why `docker stop` is slow** ```dockerfile # WRONG: shell form, sh is PID 1, signals do not reach nginx CMD nginx -g "daemon off;" # RIGHT: exec form, nginx is PID 1, SIGTERM reaches it CMD ["nginx", "-g", "daemon off;"] ``` With shell form, `docker stop` sends SIGTERM to `/bin/sh`, which ignores it. After 10 seconds Docker sends SIGKILL. Your app dies hard, no graceful cleanup. **Trying to expand env vars in exec form** ```dockerfile # WRONG: exec form does NOT expand $VAR CMD ["echo", "$HOME"] # prints literal $HOME # RIGHT 1: shell form (accept the PID 1 trade-off, or use --init) CMD echo $HOME # RIGHT 2: invoke a shell explicitly in exec form CMD ["/bin/sh", "-c", "echo $HOME"] ``` Exec form is `execve()`-based and does no shell parsing. If you need env-var expansion, you have to call a shell yourself. **Two `CMD` or two `ENTRYPOINT` lines** Only the **last one wins**. Earlier lines are silently ignored. ```dockerfile CMD ["echo", "first"] CMD ["echo", "second"] # Container runs: echo second ``` No error, no warning. Easy to miss in a long Dockerfile. **Forgetting that `docker run img sh` replaces `CMD`, not `ENTRYPOINT`** ```dockerfile ENTRYPOINT ["my-app"] CMD ["--default-flag"] ``` ```bash $ docker run img sh # runs: my-app sh (NOT a shell) $ docker run --entrypoint sh img # actually a shell ``` With an `ENTRYPOINT`, you cannot drop into a shell with just trailing args. You need `--entrypoint`. ### Real-world usage - **Service images** (`nginx`, `postgres`, `redis`): `CMD` only. The default command starts the daemon; advanced users override with `docker run img <custom-args>` if needed. - **CLI images** (`alpine/git`, `peter-evans/dockerhub-description`, `aws-cli`): `ENTRYPOINT` set to the binary. The image *is* the CLI. - **Hybrid utilities** (`postgres:16`'s entrypoint runs init scripts then execs postgres): `ENTRYPOINT ["docker-entrypoint.sh"]` + `CMD ["postgres"]`. The entrypoint script is a wrapper that does setup then `exec "$@"` to run the CMD. - **CI/CD-friendly images:** `ENTRYPOINT ["my-tool"]` so jobs can run `docker run myimg <flags>` without remembering the binary name. ### Follow-up questions **Q:** What is the difference between exec form and shell form? **A:** Exec form `["prog", "arg"]` runs the binary directly via `execve()` — your program is PID 1, signals reach it, no extra shell process. Shell form `prog arg` wraps the command in `/bin/sh -c '...'`, so `/bin/sh` is PID 1. Production images should always use exec form. **Q:** Why does my `CMD` not start the container? **A:** `CMD` only runs when no command is passed to `docker run`. If you do `docker run img bash`, the `CMD` is replaced by `bash`. Also: `CMD` does not run if there is also an `ENTRYPOINT` that ignores its arguments. And only the *last* `CMD` in the Dockerfile counts. **Q:** What does the `docker-entrypoint.sh` pattern do? **A:** It is a shell script set as `ENTRYPOINT`. Inside, it does setup (env-var validation, init dirs, run migrations), then `exec "$@"` to replace itself with the actual app process. The `exec` makes the app PID 1 (so signals work). Used by `postgres`, `mysql`, `redis`, and many official images. **Q:** Can I override both `ENTRYPOINT` and `CMD` at once? **A:** Yes: `docker run --entrypoint /bin/sh myimg -c 'echo hi'`. The `--entrypoint` swaps the entrypoint; everything after the image name becomes the new args (the original `CMD`). **Q:** (Senior) When would you NOT use the `ENTRYPOINT + CMD` pattern even for a CLI image? **A:** When users genuinely need to run unrelated commands inside the same image — e.g., a debug image where you want `docker run img sh` to just work. With a hard `ENTRYPOINT`, that needs `--entrypoint sh`. For tightly-scoped CLIs, the entrypoint is right; for general-purpose images, leaving entrypoint empty (or `[""]`) keeps `docker run img <anything>` flexible. ## Examples ### Service image — `CMD` only ```dockerfile FROM nginx:1.27-alpine COPY nginx.conf /etc/nginx/nginx.conf COPY html/ /usr/share/nginx/html/ EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ``` ```bash $ docker run -d -p 8080:80 mywebsite # uses CMD $ docker run -it mywebsite sh # CMD replaced with sh ``` The image runs nginx by default but stays usable for debugging. ### CLI image — `ENTRYPOINT + CMD` ```dockerfile FROM alpine:3.21 RUN apk add --no-cache curl ENTRYPOINT ["curl"] CMD ["--help"] ``` ```bash $ docker run --rm mycurl -> curl --help $ docker run --rm mycurl https://api.github.com -> curl https://api.github.com $ docker run --rm --entrypoint sh mycurl -> shell, for debugging ``` The image *is* curl. The default behavior (showing help) makes the first run informative; passing args gives you the real tool. ### Postgres-style entrypoint script ```dockerfile FROM postgres:16-alpine # Inherits: # ENTRYPOINT ["docker-entrypoint.sh"] # CMD ["postgres"] # The entrypoint script (provided by base image) does: # - validate POSTGRES_PASSWORD/POSTGRES_DB env vars # - run initdb if data dir is empty # - run /docker-entrypoint-initdb.d/* SQL files on first start # - exec "$@" # becomes: exec postgres ``` ```bash $ docker run -e POSTGRES_PASSWORD=dev postgres:16-alpine # entrypoint runs init logic, then exec's postgres which becomes PID 1 ``` The pattern: entrypoint = setup wrapper, CMD = the real command, `exec "$@"` at the end so the real command becomes PID 1 and gets signals correctly.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.