Learn Docker With My Newest Course

Dive into Docker takes you from "What is Docker?" to confidently applying Docker to your own projects. It's packed with best practices and examples. Start Learning Docker →

Docker Tip #63: Difference between an Array and String Based CMD


The official terms for this are exec form and shell form commands. Both do nearly the same thing, but there's an important difference.

Using [] is considered “exec form” and the plain string command is considered “shell form”.

Exec Form

Runs your CMD’s binary as is, along with any arguments you optionally pass in.

Example CMD:
CMD ["gunicorn", "-c", "python:config.gunicorn", "hello.app:create_app()"]

Shell Form

Runs your CMD’s binary through a shell which has the added benefit of using any shell functionality you want (such as using pipes and &&, etc.).

Example CMD:
CMD gunicorn -c "python:config.gunicorn" "hello.app:create_app()"

Which One Should You Use?

Shell form sounds better in theory, but it will prevent Unix signals from reaching your container correctly, such as SIGTERM. It also means the shell process ends up being PID 1 instead of whatever binary you’re running in your CMD.

Showing what PID 1 is using both methods:
# The output of `ps` when you use exec form:
  1   root     0:00   {gunicorn} /usr/local/bin/python /usr/local/bin/gunicorn
 15   root     0:02   {gunicorn} /usr/local/bin/python /usr/local/bin/gunicorn
188   root     0:00   ps

# The output of `ps` when you use shell form:
  1   root     0:00   /bin/sh -c gunicorn -c python:config.gunicorn hello.app:create_app()
  6   root     0:00   {gunicorn} /usr/local/bin/python /usr/local/bin/gunicorn
 16   root     0:00   {gunicorn} /usr/local/bin/python /usr/local/bin/gunicorn
 29   root     0:00   ps

Docker (and I) both recommend that you use exec form whenever possible, which really is most of the time. If you need to do complicated shell scripting when a container starts, you should probably use an ENTRYPOINT script.

But what about Docker Compose:

If you’re curious, Docker Compose appears to convert shell form into exec form by splitting your command: by spaces. I didn’t look at the source code to see if this is the full story, but I did notice that PID 1 is still gunicorn when using the custom command listed below:

    command: >
      gunicorn --reload -c "python:config.gunicorn" "hello.app:create_app()"

The official Docker Compose documentation says it supports the [] syntax too if you want to be more explicit. If anyone knows the exact story behind this, please leave a comment below and I’ll update the post.

Here’s a few more examples of exec form in the context of web apps written in a few different frameworks.

Free Intro to Docker Email Course

Over 5 days you'll get 1 email per day that includes video and text from the premium Dive Into Docker course. By the end of the 5 days you'll have hands on experience using Docker to serve a website.