Shell form: Commands are written without
 brackets and are run by the container’s shell, such as
/bin/sh -c. Example:
# /bin/sh -c 'echo $HOME'
RUN echo $HOME
# /bin/sh -c 'echo $PATH'
CMD echo $PATH
Depending on the shell, commands will execute as child processes of the shell, which has some potentially negative consequences at the cost of some features, described below.
Exec form: Commands are written with
 brackets and are run directly, not through a shell. Example:
CMD ["sleep", "1s"]
This removes a potentially extra process in the process tree, which may be desirable.
These are the recommended forms to use for each instruction:
RUN: shell form, because of the shell features described below.
ENTRYPOINT: exec form, because of the signal trapping described below.
CMD: exec form, because of the signal trapping described below.
The general idea is to use the exec form unless you need shell features – and if you need shell features in the
CMD, then consider writing a shell script and executing it with the exec form.
There are real use cases where you may not want to follow the above recommendations, so let’s explore the main consequences of the two forms.
In the shell form, commands will inherit environment variables from the shell, such as
FROM alpine:latest # Shell: echoes "/root" as set by the shell RUN echo $HOME # Exec: echoes "$HOME" because nothing is set RUN ["echo", "$HOME"]
However, both forms behave the same when it comes to environment variables set by the
FROM alpine:latest ENV VERSION=1.0.0 # Shell: echoes "1.0.0" because Docker does the substitution RUN echo $VERSION # Exec: echoes "1.0.0" because Docker does the substitution RUN ["echo", "$VERSION"]
The main thing you lose with the exec form is all the useful shell features: subcommands, piping output, chaining commands, I/O redirection, and more. These kinds of commands are only possible with the shell form:
FROM ubuntu:latest # Shell: run a speed test RUN apt-get update \ && apt-get install -y wget \ && wget -O /dev/null http://speedtest.wdc01.softlayer.com/downloads/test10.zip \ && rm -rf /var/lib/apt/lists/* # Shell: output the default shell location CMD which $(echo $0)
Most Dockerfiles are written with the shell form for
RUN for the niceties as well as layer reduction.
Most shells do not forward process signals to child processes, which means the
SIGINT generated by pressing
CTRL-C may not stop a child process:
# Note: Alpine's `/bin/sh` is really BusyBox `ash`, and when # `/bin/sh -c` is run it replace itself with the command rather # than spawning a new process, unlike Ubuntu's `bash`, meaning # Alpine doesn't exhibit the forwarding problem FROM ubuntu:latest # Shell: `bash` doesn't forward CTRL-C SIGINT to `top` ENTRYPOINT top -b # Exec: `top` traps CTRL-C SIGINT and stops ENTRYPOINT ["top", "-b"]
This is the main reason to use the exec form for both
There’s a special version of the exec form for
CMD where if the first item in the array isn’t a command then all items will be used as parameters for the
FROM alpine:latest # Exec: default container command ENTRYPOINT ["sleep"] # Exec: not a command, so it becomes a parameter CMD ["1s"]
This is not possible with the shell form.
In general, use the shell form for
RUN, and the exec form for everything else.