Exec vs Shell Form

Exec Form, Shell Form and Signal Handling

CMD, ENTRYPOINT and RUN each have two forms. The exec form, a JSON array such as CMD ["node", "server.js"], starts the program directly. The shell form, CMD node server.js, runs /bin/sh -c "node server.js", putting a shell in front of your program. That is normal for RUN, but for CMD it breaks shutdown: docker stop signals PID 1 only, and BookNest's server.js needs that SIGTERM to close its server and database pool:

A graceful stop with the exec formShell
docker exec l3-api cat /proc/1/cmdline | tr '\0' ' '; echo
time docker stop l3-api
docker logs l3-api | tail -1
docker inspect l3-api --format 'exit code {{.State.ExitCode}}'
Output
node server.js
l3-api
real    0m0.439s
...
SIGTERM received, shutting down
exit code 0

Rebuilt FROM l3-booknest-api:1.0 with other CMD lines, the same application stopped very differently (timings from this shared 4-CPU host):

How the CMD form decides what happens on docker stop
CMD PID 1 Stop time Exit code Graceful?
["node", "server.js"] node server.js 0.49 s 0 Yes
node server.js /bin/sh -c node server.js 11.43 s 137 No, killed
["npm 2,036 ", "start"] npm start 0.98 s 1 No, npm error
shell form with docker run --init docker-init 0.36 s 143 No, node killed

The kernel drops signals sent to a namespace's PID 1 unless it has a handler; the shell has none and does not pass SIGTERM on, so after ten seconds Docker 514 sends SIGKILL (exit code 137 = 128 + 9). npm start adds its own shell and logs npm error signal SIGTERM. --init inserts tini 11,240 (https://github.com/krallin/tini 11,240 ), which reaps zombies and forwards signals, but only to its child, the shell. Use the exec form, call node directly, and end any wrapper script with exec node server.js so Node.js 2,131 becomes PID 1.