🐳 Section 14 · Question #5

What is the Difference Between CMD and ENTRYPOINT

Both ENTRYPOINT and CMD define the command executed when a container starts (docker run), but they fulfill fundamentally different responsibilities:

🟢 Junior Level

30-Second Interview Answer

Both ENTRYPOINT and CMD define the command executed when a container starts (docker run), but they fulfill fundamentally different responsibilities:

  • ENTRYPOINT: Defines the fixed, immutable executable binary of the container. It transforms the container into a standalone executable tool (e.g., java). Overriding it requires an explicit --entrypoint flag.
  • CMD: Supplies default arguments to the ENTRYPOINT program. It is designed to be easily overridden by passing CLI arguments at the end of docker run.

Real-World Analogy: Function Call in Code

Think of container invocation as a method call in Java:

java   -jar app.jar   --server.port=8080
▲      ▲              ▲
│      │              │
[   ENTRYPOINT  ]     [     CMD        ]
(Function / Binary)   (Default Arguments)

Combined Best-Practice Pattern

# Fixed executable binary and base parameter:
ENTRYPOINT ["java", "-jar", "app.jar"]

# Default overridable parameter:
CMD ["--server.port=8080"]

CLI Execution Scenarios:

  1. Default startup:
    docker run order-service
    # Resulting command: java -jar app.jar --server.port=8080
    
  2. Overriding default arguments at runtime:
    docker run order-service --server.port=9090
    # Resulting command: java -jar app.jar --server.port=9090 (CMD is replaced completely!)
    

🟡 Middle Level

Syntactic Forms: Exec Form vs Shell Form

Dimension Exec Form (Industry Standard) Shell Form (Anti-Pattern)
Syntax ENTRYPOINT ["java", "-jar", "app.jar"] ENTRYPOINT java -jar app.jar
Execution Mechanism Directly invoked via kernel system call execve() Wrapped implicitly in /bin/sh -c "..."
Process Hierarchy Application runs directly as PID 1 Shell runs as PID 1, Java runs as PID 2
Signal Handling Kernel signals (SIGTERM) delivered to Java Shell intercepts and drops SIGTERM
Graceful Shutdown 🟢 Works correctly (Spring shutdown hooks run) ❌ Broken (Killed abruptly with SIGKILL after 30s)

Interaction Matrix: ENTRYPOINT and CMD Combinations

Dockerfile Configuration CLI Command Resulting Command Executed by Kernel
CMD ["java", "-jar", "app.jar"] docker run my-app java -jar app.jar
CMD ["java", "-jar", "app.jar"] docker run my-app bash bash (CLI completely replaces CMD)
ENTRYPOINT ["java", "-jar", "app.jar"] docker run my-app java -jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"] docker run my-app --debug java -jar app.jar --debug (Appended)
ENTRYPOINT ["java"] + CMD ["-version"] docker run my-app java -version
ENTRYPOINT ["java"] + CMD ["-version"] docker run my-app -jar app.jar java -jar app.jar (Replaces CMD)

Kubernetes Pod Specification Mapping

When translating container definitions into Kubernetes manifests, Docker’s terminology is mapped differently:

Kubernetes Pod Spec Field Corresponding Dockerfile Directive
command: ENTRYPOINT
args: CMD
apiVersion: v1
kind: Pod
metadata:
  name: payment-service
spec:
  containers:
  - name: app
    image: payment-service:1.0
    command: ["java"]                                              # Overrides image ENTRYPOINT
    args: ["-jar", "/app.jar", "--spring.profiles.active=k8s"]    # Overrides image CMD

[!WARNING] In Kubernetes, specifying the command field completely wipes out both the ENTRYPOINT and CMD from the original Dockerfile (unless args is also explicitly specified).


🔴 Senior Level

The PID 1 Problem & Kernel Signal Immunity

In UNIX systems, PID 1 (init or systemd) has unique kernel privileges and responsibilities:

  1. Signal Immunity: The Linux kernel does not apply default signal handlers to PID 1. If PID 1 does not install an explicit signal handler for SIGTERM, the kernel ignores the signal.
  2. Why Shell Form Breaks Graceful Shutdown:
    • With ENTRYPOINT java -jar app.jar, /bin/sh runs as PID 1, and the JVM runs as a child process (PID 2).
    • When Kubernetes terminates a Pod (kubectl delete pod), the Kubelet dispatches SIGTERM to the container’s PID 1 (/bin/sh).
    • /bin/sh receives SIGTERM but does not propagate it to child processes.
    • The JVM never receives notification to shut down. Active HTTP requests are not drained, transactions in progress are not committed, and connection pools are not closed.
    • When terminationGracePeriodSeconds (default: 30s) expires, the kernel issues an uncatchable SIGKILL (Exit Code 137), brutally terminating the JVM and potentially corrupting data.

The Entrypoint Wrapper Pattern & POSIX exec "$@"

Enterprise applications often require pre-startup operations: fetching dynamic secrets from HashiCorp Vault, setting up file permissions, or running database migrations. This is implemented using an entrypoint script:

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

# Script acts as entrypoint; CMD supplies the Java binary command
ENTRYPOINT ["/entrypoint.sh"]
CMD ["java", "-jar", "app.jar"]
#!/bin/sh
# entrypoint.sh
set -e

echo "Initializing environment, secrets, and database certificates..."

# ⚠️ CRITICAL: Must use the POSIX 'exec' command!
exec "$@"

Why exec "$@" is Mandatory

The shell built-in exec triggers the kernel system call execve(2):

  • It replaces the current shell process image with the target binary (java), retaining the exact same PID 1.
  • If you execute "$@" without exec, the shell remains PID 1, spawns Java as PID 2, and the signal forwarding problem returns!

Zombie Process Reaping & Init Systems (Tini)

If a containerized application spawns child processes (via Java’s ProcessBuilder or OS utilities):

  • When a child process terminates, it becomes a Zombie ([defunct]) until its parent reaps its exit code via waitpid(2).
  • If the parent process exits before its children, the orphans are reparented to PID 1.
  • If the root container process (like a custom Java runtime) does not reap orphaned zombies, they permanently occupy slots in the host kernel process table, eventually causing PID Exhaustion.
  • Solution: Use a lightweight init manager such as Tini:
    ENTRYPOINT ["/usr/bin/tini", "--", "java", "-jar", "app.jar"]
    

    (Or run containers with the Docker flag: docker run --init).


4 Tricky Questions with In-Depth Answers

1. In a Kubernetes manifest, if you configure only command: ["java"] without specifying args, what happens to CMD from the original Dockerfile?

Answer: The CMD parameters from the Dockerfile are completely discarded! According to the Kubernetes container runtime specification:

  • If command is set, it overrides the Docker ENTRYPOINT.
  • If args is omitted, Kubernetes sets the argument array to empty ([]), entirely ignoring any default CMD declared in the underlying Dockerfile.
  • As a result, the container executes java with no arguments, prints JVM usage information to stdout, and immediately exits with code 1 or 0, landing the Pod in a CrashLoopBackOff state.

2. What happens if ENTRYPOINT is defined in Shell Form and CMD is defined in Exec Form? Are arguments from CMD passed to the application?

Answer: No, the CMD arguments are completely ignored. Consider this configuration:

ENTRYPOINT java -jar app.jar
CMD ["--server.port=8080"]

Docker compiles the shell form into /bin/sh -c "java -jar app.jar". Under POSIX shell conventions, parameters passed after /bin/sh -c "command" are treated as arguments for the shell itself ($0, $1), not the command string inside quotes. Consequently, the Docker daemon never passes the CMD array or any docker run trailing arguments to java -jar app.jar. The application runs strictly with no arguments.

3. In an entrypoint wrapper script, why is exec "$@" written with double quotes and an at-sign rather than exec $*?

Answer: The distinction lies in shell Word Splitting:

  • $* (unquoted): Expands all arguments as a single flat string and splits them on whitespace characters (IFS). If an argument contains spaces—such as a JVM property -Dapp.name="Payment Service"—it gets improperly fragmented into two invalid tokens: -Dapp.name="Payment and Service".
  • "$@" (double-quoted with at-sign): Preserves each individual argument exactly as passed, retaining embedded spaces and special characters. Combined with exec, it replaces the shell process with the target command under PID 1 with pristine argument integrity.

4. Can you pass JVM memory parameters (e.g. -Xmx1g) via CMD if ENTRYPOINT is declared as ["java", "-jar", "app.jar"]?

Answer: No, doing so will cause the JVM to ignore the setting or fail! The syntax of the java binary requires VM options to be specified before the -jar option: java [options] -jar filename [args...]. If ENTRYPOINT is fixed as ["java", "-jar", "app.jar"], any arguments supplied by CMD are appended after app.jar: java -jar app.jar -Xmx1g. The JVM treats -Xmx1g as an application argument passed to public static void main(String[] args), not as a heap configuration flag! Correct approach: Inject JVM flags using the JAVA_TOOL_OPTIONS environment variable (ENV JAVA_TOOL_OPTIONS="-Xmx1g"), or declare ENTRYPOINT ["java"] with CMD ["-jar", "app.jar"].


🎯 Interview Cheat Sheet

Architectural Comparison

| Aspect | ENTRYPOINT | CMD | |—|—|—| | Core Responsibility | Fixed executable binary / command | Default overridable parameters | | CLI Override Difficulty | Hard (requires --entrypoint /bin/sh) | Easy (docker run my-image arg1 arg2) | | Kubernetes Equivalent | command: | args: | | Mandatory Format | Exec Form ["binary", "arg"] | Exec Form ["param1", "param2"] | | Signal Behavior | Application is PID 1; receives SIGTERM | Appended to ENTRYPOINT |

Production Golden Rules

  1. Always use Exec Form: Never use string shell form (ENTRYPOINT java ...); it breaks graceful shutdown by running behind an unresponsive /bin/sh PID 1.
  2. Standard Microservice Pattern: Use ENTRYPOINT ["java", "-jar", "app.jar"] for the fixed command and CMD ["--server.port=8080"] for default arguments.
  3. Always use exec "$@" in Wrapper Scripts: Ensures the target process replaces the shell and maintains PID 1.
  4. Never put JVM heap flags in CMD: Use JAVA_TOOL_OPTIONS instead.

Red Flags (DO NOT Say)

  • ❌ “ENTRYPOINT and CMD are synonyms; it doesn’t matter which one you use.” (Fails basic understanding of container execution lifecycle and argument overriding).
  • ❌ “Shell form is safe and makes no difference.” (Shell form breaks OS signal handling, preventing graceful shutdown and forcing SIGKILL).
  • ❌ “In Kubernetes, command corresponds to CMD and args corresponds to ENTRYPOINT.” (It is the exact opposite: command = ENTRYPOINT, args = CMD).