JSONArgsRecommended: JSON arguments recommended for CMD to prevent unintended behavior related to OS signals
is a build-check warning: the image builds. It flags a CMD written in shell form,
such as CMD sh /loop.sh. Write it in exec form,
CMD ["sh", "/loop.sh"], and your program, not a wrapping shell, receives the
stop signal.
Whether the shell form actually hurts depends on the image and the command. In the tests below it made no difference on Alpine, and on Ubuntu it turned a clean one-second stop into a kill with exit code 137.
The error
1 warning found (use docker --debug to expand):
- JSONArgsRecommended: JSON arguments recommended for CMD to prevent unintended behavior related to OS signals (line 3)
The build exited with code 0. The terminal colour codes in the captured lines are left out.
Why it happens
A shell-form CMD runs as /bin/sh -c "...". If that shell stays
process 1, docker stop sends SIGTERM to the shell, the shell does not pass it
on, and Docker kills the container after its grace period. The test script traps SIGTERM,
prints got SIGTERM and exits 0, so the logs show who received the signal.
Measured with docker stop and a stopwatch: on alpine,
CMD sh /loop.sh and even CMD echo booting; sh /loop.sh left
sh /loop.sh as process 1; the script printed got SIGTERM, exit code
0, about one second. On ubuntu:24.04, CMD echo booting; sh /loop.sh
left /bin/sh -c echo booting; sh /loop.sh as process 1; the stop took 3.4
seconds, the exit code was 137 and got SIGTERM never appeared.
The fix
FROM ubuntu:24.04
COPY loop.sh /loop.sh
CMD ["sh", "-c", "echo booting; exec sh /loop.sh"]
With this CMD process 1 was sh /loop.sh, the stop took 1.0 second,
the log showed got SIGTERM and the exit code was 0. The exec makes
the shell replace itself with the script. For a single command, the plain exec form
CMD ["sh", "/loop.sh"] built on Alpine without the warning and stopped the same
way.
How it was reproduced
A three-line Dockerfile per case and a loop.sh that traps TERM, built with
docker build --progress=plain -t fix8-bld-q2 fix8-bld-q2 and its variants, then
docker run -d, docker exec ... cat /proc/1/cmdline and a timed
docker stop from PowerShell. Docker Desktop engine 29.9.2, images
alpine and ubuntu:24.04. Names are this test's own.
Frequently asked
- Is JSONArgsRecommended an error?
- No. It is a build-check warning and the image builds. It suggests exec form so that stop signals reach your program.
- Why does docker stop take seconds and exit with 137?
- Process 1 did not exit on SIGTERM, often because a shell-form CMD left /bin/sh as process 1. Docker then kills the container, which gives exit code 137.
- How do I keep a shell in CMD but still get signals?
- Use exec form with sh -c and put exec before the last command, for example CMD ["sh", "-c", "echo booting; exec sh /loop.sh"].
More decoded errors in the Fixes category, including the systemctl in a container post about what process 1 is.