I know there are already many answers, but I did some more digging and wanted to share how exactly docker build handles ENTRYPOINT and CMD.
tl;dr: there's barely any fundamental difference in the two forms.
Step 1: shell to exec/JSON conversion. There are two formats for both keywords: "shell form" and "exec/JSON form." The online docs call it "exec form" while related warning messages from docker build call it "JSON form." I'll use "JSON form" for reasons that will be clear later.
- shell form:
ENTRYPOINT "mycommand arg1"
- JSON form:
ENTRYPOINT ["mycommand", "arg1"]
The conversion: ENTRYPOINT "foo bar", gets converted to JSON form,
ENTRYPOINT ["/bin/sh", "-c", "\"foo bar\""]. Arguments in JSON form are not processed further.
ENTRYPOINT and CMD are converted separately.
Step 2: concatenation. The two resulting JSON lists of strings are concatenated, ENTRYPOINT then CMD.
This produces one big combined exec/JSON form.
Step 3: execution. running the container with docker run and no added command line arguments (which would overwrite CMD) effectively does exec $@ where $@ are the strings from step 2.
Special cases:
If ENTRYPOINT or CMD aren't specified then they are effectively converted to an empty list in step 2.
If both aren't specified then there's no command at all. Running the image without specifying one or both at the command line will raise a "no command specified" error.
How to verify: you can make a simple Dockerfile like this:
FROM scratch
ENTRYPOINT "foo"
CMD "bar"
Next run docker build -t args-test:latest . to build it.
Then you can use docker inspect args-test:latest | less to see what the lists of tokens are for CMD and ENTRYPOINT, recorded as "Cmd" and "Entrypoint" respectively. You'll see that their values in the image are always either null or a JSON list of strings. Hence "JSON form."
Example 1:
ENTRYPOINT ["echo"]
CMD "foo" # --> ["/bin/sh", "-c", "foo"]
Concatenated JSON form: ["echo", "/bin/sh", "-c", "\"foo\""]
Prints to shell: /bin/sh -c "foo"
Why: ENTRYPOINT is in JSON form already and not modified. CMD is in "exec" form so it converted to JSON form (see step 1). The result runs echo with the tokens produced from the CMD conversion to JSON form.
Example 2:
ENTRYPOINT "echo" # --> ["/bin/sh", "-c", "echo"]
CMD ["foo"]
Concatenated JSON form: ["/bin/sh", "-c", "echo", "foo"]
Prints to shell: empty line
Why: ENTRYPOINT is in exec form so it gets converted to JSON form ["/bin/sh", "-c", "echo"]. CMD is already in JSON form and is not modified. The resulting command is thus /bin/sh -c echo foo.
The latter is a bit of a shell puzzler. sh -c takes echo to be the command string, sets $0 to foo (and would set $1, $2, etc. to later parameters if there were any), then runs echo with no parameters. That prints the blank line.
The difference in practice: still not a lot, both CMD and ENTRYPOINT can be overridden but in different ways:
- to override
CMD: docker run my-image foo bar will overwrite CMD to ["foo", "bar"]
- to override
ENTRYPOINT: docker run my-image --entrypoint foo will overwrite ENTRYPOINT to be foo
The other main difference is ENTRYPOINT comes first. So when the image is run, the first token in the JSON form of ENTRYPOINT is the executable. This can matter for signal handling and other edge cases. This is the reason you get warnings from docker build if you use shell form instead of JSON form: to push you toward using the JSON forms to avoid such hard to debug issues.
Finally, from the docker-run docs themselves for --entrypoint:
The ENTRYPOINT of an image is similar to a COMMAND because it specifies what executable to run when the container starts, but it is (purposely) more difficult to override.
ADDandCOPYCMDandENTRYPOINTboth have different forms to be written, exec and shell form. So make yourself a favor and understand the subtle differences in behavior depending on the form being used. Then read docs.docker.com/engine/reference/builder/….