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:
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}}'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):
| 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.