Skip to content

use-exec-form

Docker container takes 10 seconds to stop? Shell form CMD swallows SIGTERM. How exec form fixes signal handling and graceful shutdown.

On this page

CMD node server.js (shell form) doesn't run your app as PID 1 — it runs /bin/sh -c "node server.js", and the shell becomes PID 1 with your app as its child. When Docker sends SIGTERM on docker stop, the shell receives it and does not forward it. Your app never hears the shutdown request. This rule flags shell-form CMD and ENTRYPOINT instructions.

What the rule catches

Dockerfile — the shell eats SIGTERM
FROM node:22-slim
WORKDIR /app
COPY . .
CMD node server.js

Scanning this file reports:

⚠ WARN [docker-doctor/use-exec-form]
  CMD instruction uses shell form instead of exec form. In shell form, the command runs under '/bin/sh -c', which does not pass signals to child processes.

Why it matters

The visible symptom is every docker stop and every rolling deploy hanging for the 10-second grace period and ending in SIGKILL. The invisible symptom is worse: your graceful-shutdown code — draining requests, closing database connections, flushing queues — never runs, because the process is killed, not asked to exit. Exec form (CMD ["node", "server.js"]) makes your app PID 1 so signals reach it directly.

How to fix it

Write CMD/ENTRYPOINT instructions as JSON arrays (e.g. ENTRYPOINT ["node", "index.js"]) so OS signals (like SIGTERM) are forwarded correctly.

Dockerfile — the app is PID 1 and receives signals
FROM node:22-slim
WORKDIR /app
COPY . .
CMD ["node", "server.js"]

Exec form is a JSON array, so it must use double quotes, and there is no shell: $VAR, pipes, and && won't work inside it. If you genuinely need shell features at startup, wrap them in an entrypoint script that ends with exec node server.jsexec replaces the shell so the app still ends up as PID 1. Then make sure the app actually handles SIGTERM; PID 1 gets no default signal handlers.

Rule details

  • Rule keydocker-doctor/use-exec-form
  • Category — Best Practices
  • Default severitywarning
  • Applies to — Dockerfiles

Explain this rule from the CLI:

npx @docker-doctor/cli@latest rules explain docker-doctor/use-exec-form

Change its severity — or turn it off — in your config file:

// docker-doctor.config.ts
export default {
  rules: {
    "docker-doctor/use-exec-form": "off",
  },
};

Severity affects the health score: error findings cost more points than warning, and info costs the least.