101

Not understanding what is happening when I try to execute two commands at runtime via CMD directive in `Dockerfile. I assumed that this should work:

CMD ["/etc/init.d/nullmailer", "start", ";", "/usr/sbin/php5-fpm"]

But it's not working. Container has not started. So I had to do it like this:

CMD ["sh", "-c", "/etc/init.d/nullmailer start ; /usr/sbin/php5-fpm"]

I don't understand. Why is that? Why first line is not the right way? Can somebody explain me these "CMD shell format vs JSON format, etc" stuff. In simple words.

Just to note - the same was with command: directive in docker-compose.yml, as expected.

8 Answers 8

70

I believe the difference might be because the second command does shell processing while the first does not. Per the official documentation, there are the exec and shell forms. Your first command is an exec form. The exec form does not expand environment variables while the shell form does. It is possible that by using the exec form the command is failing due to its dependence on shell processing. You can check this by running docker logs CONTAINERID

Your second command, the shell form, is equivalent to -

CMD /etc/init.d/nullmailer start ; /usr/sbin/php5-fpm

Excerpts from the documentation -

Note: Unlike the shell form, the exec form does not invoke a command shell. This means that normal shell processing does not happen. For example, CMD [ "echo", "$HOME" ] will not do variable substitution on $HOME. If you want shell processing then either use the shell form or execute a shell directly, for example: CMD [ "sh", "-c", "echo", "$HOME" ].

5
  • Probably command failed because of environment variables. Should I still use this exec form, as it is the preferred form? Why it is preferred? Or should I use simpler shell form?
    – Vladan
    Commented Apr 27, 2015 at 10:46
  • It failed because executing one command after another is a shell function. Environment variables is a red herring.
    – Bryan
    Commented Apr 27, 2015 at 12:02
  • If you are running multiple services in Docker, I would recommend using a process manager like supervisor. That way you launch only supervisord under the CMD section and it will take care of starting the services. You can check for details here - docs.docker.com/articles/using_supervisord
    – Daniel t.
    Commented Apr 27, 2015 at 14:36
  • This is an exact article I was just reading :) Thanks.
    – Vladan
    Commented Apr 29, 2015 at 17:24
  • 3
    I still don't understand why you need to do CMD [ "sh", "-c", "echo", "$HOME"]. Why not CMD ["sh", "-c", "echo $HOME"] or, for that matter, CMD ["sh -c echo $HOME"]?
    – sixty4bit
    Commented Jan 6, 2018 at 0:41
75

Don't make it hard on yourself. Just create a bash file "start.sh": 

#!/bin/bash

/usr/bin/command2 param1
/usr/bin/command1

in your Dockerfile do:

ADD start.sh /
RUN chmod +x /start.sh

CMD ["/start.sh"]
4
  • 2
    While I completely agree to KISS, this unfortunately might impact the performance in older versions of docker and/or unnecessary increase the size of the built image. This is because you're creating two layers with ADD and RUN, have a look at the best practices, minimize the number of layers.
    – luukvhoudt
    Commented Jun 28, 2020 at 13:01
  • 3
    My understanding is that this will run the script in a shell, causing the any resultant processes (e.g. webserver) to be a child process of the shell, and will not be forwarded SIGTERM, thus preventing those processes from gracefully exiting Commented Jun 6, 2022 at 3:01
  • in my case: CMD ["/usr/bin/bash", "/start.sh"] to make it properly run.
    – kholis
    Commented Apr 26 at 3:48
  • does it catch/forward SIGTERM?
    – Alexey Sh.
    Commented Jun 8 at 22:38
12

The json syntax of CMD (and RUN and ENTRYPOINT) pass the arguments to the kernel directly as an exec syscall. There is no separating of the command from the arguments by spaces, escaping of quotes, IO redirection, variable substitution, piping between commands, running multiple commands, etc, in the exec syscall. The syscall only takes the executable to run and list of arguments to pass to that executable, and it runs it.

Characters like $ to expand variables, ; to separate commands, (space) to separate arguments, && and || to chain commands, > for output redirection, | to pipe between commands, etc, are all features of the shell and need something like /bin/sh or /bin/bash to interpret and implement them.


If you switch to the string syntax of CMD, docker will run your command with a shell:

CMD /etc/init.d/nullmailer start ; /usr/sbin/php5-fpm

Otherwise, your second syntax does the exact same thing:

CMD ["sh", "-c", "/etc/init.d/nullmailer start ; /usr/sbin/php5-fpm"]

Note that I do not recommend running multiple commands this way inside of a container since there is no error handling if your first command fails, especially if it runs in the background. You also leave a shell running as pid 1 inside the container which will break signal handling, resulting in a 10 second delay and ungraceful kill of your container by docker. The signal handling can be mitigated by using the shell exec command:

CMD /etc/init.d/nullmailer start ; exec /usr/sbin/php5-fpm

However, handling processes silently failing in the background requires you switch to some kind of multi-process manager like supervisord, or preferably breakup your application into multiple containers and deploy them with something like docker-compose.

1
  • This is brilliant, exec is the solution to common problem of not being able to stop the container. Also, the common scenario is that you are forced to switch to Docker but you still want to run your app + some background service, such as Datadog agent. this above is a good solution solution if you don't want to go with a full docker cluster with separate containers running just to be able to collect metrics or traces for DD.
    – januszm
    Commented Mar 13, 2023 at 11:41
2

In Docker compose, this can be done as the following example:

command: ["sh", "-c", "
    apt update && apt install -y libldap-common;
    cp /ca.crt /usr/local/share/ca-certificates/;
    update-ca-certificates;
    exec apache2-foreground
  "]

The exec will switch the context of the main executable to apache2-forground.

1

I guess first command fails because in DOCKER CMD form, only the first parameter is executed, the rest is fed into this command.

The second form works because all commands seperated with ";" are fed into sh command, which executes them.

1

I don't think you should put semi comma after "start"

instead of using

CMD ["/etc/init.d/nullmailer", "start", ";", "/usr/sbin/php5-fpm"]

try

CMD ["/etc/init.d/nullmailer", "start", "/usr/sbin/php5-fpm"]

as docker uses "sh -c", above command will be executed as below

/etc/init.d/nullmailer start
/etc/init.d/nullmailer /usr/sbin/php5-fpm
1
  • 1
    The json syntax does not run commands with a shell, there is not sh -c in that scenario.
    – BMitch
    Commented Aug 7, 2019 at 14:39
1

For example, imagine you have two python commands to run python init_reset.py and python app.py. Then using CMD, you can combine the two commands with the single command

CMD python init_reset.py ; python app.py
2
  • You left out the brackets. Can we create valid syntax without the square brackets?
    – mr.zog
    Commented Feb 19, 2021 at 18:18
  • 1
    Yes @mr.zog, we can. Commented Feb 21, 2021 at 4:37
0

I found you can also do this in the Dockerfile:

ENTRYPOINT command && \
  command_2 && \
  another_command

You can probably substitute CMD for ENTRYPOINT and be ok - I just didn't try it. I found that the commands are executed in order and each command finishes before the next one starts, but if one of the commands (command_2 for example) disconnected itself from the terminal and commands after it needed it to be finished before they ran you might run into some issues.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .