Docker CMD and ENTRYPOINT Tester

Work out what a container actually executes, and what ends up as PID 1. The answer decides whether docker stop is graceful or a ten second wait followed by a kill.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Test. Nothing leaves this tab.

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

Shell form

PID 1 is a shell, so docker stop waits the full grace period and then kills

CMD /app/server --port 8080

A shell ENTRYPOINT with a CMD

The CMD is thrown away entirely, and nothing says so

ENTRYPOINT /app/server
CMD ["--port", "8080"]

Run arguments over a CMD

They replace the whole CMD rather than merging with it

ENTRYPOINT ["/app/server"]
CMD ["--port", "8080"]
docker run -d myimage --verbose

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Using shell form for a long-running service

    PID 1 becomes /bin/sh, which does not forward SIGTERM. Every stop waits the full grace period and ends in SIGKILL, so no shutdown handler ever runs.

    Instead:Use exec form: ENTRYPOINT ["/app/server"]. If a shell is genuinely needed, exec through it so your process replaces the shell.

  2. Pairing a shell-form ENTRYPOINT with a CMD

    CMD is only appended to an exec-form ENTRYPOINT. In shell form it is discarded, so the defaults you wrote are never used and nobody is told.

    Instead:Put the ENTRYPOINT in exec form, then CMD becomes its default arguments as intended.

  3. Putting flags that must always apply in the CMD

    Any argument on docker run replaces the whole CMD, so all the defaults disappear at once.

    Instead:Keep required flags in the ENTRYPOINT and use CMD only for things a caller should be able to change.

Shell form puts a shell at PID 1, and a shell does not forward SIGTERM

That single fact is behind most of the questions about why containers take so long to stop and why shutdown handlers never run.

What actually happens on docker stop

Docker sends SIGTERM to PID 1 and waits ten seconds. With shell form, PID 1 is /bin/sh -c and your program is its child, so the shell receives the signal, does nothing with it, and your process never hears about it. After the grace period Docker sends SIGKILL. Every stop takes the full ten seconds, no cleanup runs, and in-flight requests are dropped rather than drained.

Exec form runs the program directly

ENTRYPOINT ["/app/server"] makes your binary PID 1, so the signal arrives where the handler is. This is the form to use for anything long-running, and the only reason not to is if you genuinely need shell features like variable expansion or a pipe.

If you must use a shell, exec through it

ENTRYPOINT ["/bin/sh", "-c", "exec /app/server"] runs the shell, and exec replaces the shell process with yours, so your program ends up as PID 1 anyway. Without the exec keyword the shell stays and the problem returns.

A shell-form ENTRYPOINT throws the CMD away

CMD is appended to ENTRYPOINT only when ENTRYPOINT is in exec form. In shell form the whole thing becomes one string and the CMD is never used, so default arguments silently do nothing and arguments passed to docker run do nothing either.

Arguments on the run line replace the CMD entirely

They do not merge. A CMD carrying three default flags loses all three the moment somebody passes one argument, which is how a container ends up running without flags nobody realised were defaults. Anything that must always apply belongs in the ENTRYPOINT.

PID 1 has one more job

It reaps orphaned child processes. A program that never wrote that code accumulates zombies when it spawns subprocesses. --init inserts a tiny init that handles both reaping and signal forwarding, and is the right answer when the program at PID 1 was never designed to be one.

More docker tools

Docker Container Image Reference Parser Is that a registry or a Docker Hub user? Dockerfile Linter What this image will do to you later docker run to Kubernetes YAML And the flags that have no equivalent Docker Compose to Kubernetes And why depends_on has no equivalent Docker Logging Driver Generator json-file never rotates Dockerfile Non-Root USER Generator Numeric, or Kubernetes rejects it Docker OCI Image Labels Generator One of them actually does something Docker Hub Pull Rate Limit Calculator Counted per IP, not per person Docker Registry Storage Calculator Deleting a tag frees nothing Docker Build Context Analyzer All of it uploads before anything runs Docker Compose to docker run Converter Names stop resolving Docker Compose Version Migrator version: is obsolete now Docker Compose Network and Port Viewer Everything reaches everything Docker Command Explainer Which flags hand over the host Docker BuildKit Cache Mount Generator apt deletes the cache you just mounted Docker BuildKit Secret Mount Generator ARG is in docker history forever Docker .dockerignore Pattern Tester *.log does not match logs/app.log Docker Image Manifest Viewer unknown/unknown is not broken Docker Image Config Viewer Anyone who can pull can read your Env Docker Registry Token Decoder Check the scope before the credentials Docker HEALTHCHECK Generator Docker does nothing when it fails Docker Network Subnet Calculator 31 networks, then it stops Docker History Analyzer The rm did not save anything Docker Run to Compose Converter Some flags have no equivalent Docker Port Mapping Tester -p reaches past your firewall Docker -v to --mount Converter -v invents missing directories Docker Restart Policy Simulator The backoff resets after ten seconds Docker Exit Code Explainer 137 is two different problems Docker CPU Limit Converter --cpu-shares limits nothing Docker Memory Limit Calculator -m 512m allows a gigabyte Docker Compose Interpolation Tester An unset variable is an empty string Docker Environment Variable Precedence Tester Your shell does not reach the container Docker Compose Override Merge Previewer An override can add a port, never remove one Dockerfile Multi-Stage Build Analyzer Which stages reach the image Dockerignore Validator It is not .gitignore

Elsewhere on the site