Table of Contents
- Introduction
- Understanding Shell and Exec Forms
- How Shell Form CMD Affects ENTRYPOINT
- Signal Handling Pitfalls with Shell Form
- Best Practices
- Examples
Introduction
In my previous blog, we explored Docker's RUN, CMD, and ENTRYPOINT commands, focusing on their purpose and how they interact to define container behavior. This article dives deeper into the distinction between shell and exec forms, emphasizing their impact on signal handling and how improper usage can lead to unexpected issues. Let's explore and learn best practices to avoid pitfalls.
Understanding Shell and Exec Forms
Docker supports two syntaxes for specifying CMD and ENTRYPOINT: shell form and exec form.
Shell Form
- Written as a simple string.
- Example:
CMD echo "Hello, World!" - Executes commands via
/bin/shby default (or/bin/bashon some systems).
Exec Form
- Written as a JSON array of strings.
- Example:
CMD ["echo", "Hello, World!"] - Executes commands directly, bypassing the shell.
| Feature | Shell Form | Exec Form |
|---|---|---|
| Syntax | String | JSON Array |
| Command Execution | Through Shell (e.g., /bin/sh) | Direct Execution |
| Signal Handling | Managed by Shell | Direct to Process |
How Shell Form CMD Affects ENTRYPOINT
When combining CMD and ENTRYPOINT, their interplay depends on the form used:
Example: Using Shell Form CMD with ENTRYPOINT
ENTRYPOINT ["echo"] CMD "Hello, World!"
In this example:
ENTRYPOINTspecifiesechoas the primary process.CMDshould provide additional arguments toecho.
Behavior:
docker run my-imageshould runecho "Hello, World!".- However, because
CMDis in shell form, it is interpreted as/bin/sh -c "Hello, World!". This string cannot be passed directly toENTRYPOINTas intended, leading to unexpected behavior.
If you want to understand the behavior in more detail, you can visit the official Docker documentation.
Signal Handling Pitfalls with Shell Form
Signal handling is critical for cleanly stopping or restarting Docker containers. For example, running docker kill --signal INT should terminate the main process.
The Problem
When using shell form:
- Docker wraps the command in a shell process (e.g.,
/bin/sh -c). - Signals (like
INT,TERM) are sent to the shell process, not directly to the application.
Python Example
Consider the following Dockerfile:
FROM python:3.9-slim ENTRYPOINT ["python"] CMD "-m http.server"
Running docker kill --signal INT <container> results in:
- The
INTsignal is sent to/bin/sh. /bin/shdoes not forward the signal to the Python process.- The Python process continues running, even though the container should stop.
Best Practices
Always Use Exec Form
Using the exec form ensures:
- Direct execution of the intended process.
- Proper signal handling, as signals are sent directly to the main process.
Combine ENTRYPOINT and CMD Thoughtfully
- Use
ENTRYPOINTfor the main command or application. - Use
CMDfor default arguments or configurations.
Examples
Correct Usage: Exec Form CMD and ENTRYPOINT
FROM python:3.9-slim ENTRYPOINT ["python"] CMD ["-m", "http.server"]
Behavior:
docker run my-imagerunspython -m http.server.- Signals are sent directly to the Python process.
Conclusion
Understanding the difference between shell and exec forms in Docker can save you from subtle bugs, especially around signal handling. By adhering to best practices and using the exec form, you ensure that your containers behave predictably, even under unexpected conditions.