Both specify what runs when a container starts, and the difference is genuinely confusing until you see them combined:
CMD provides the default command — but it's fully overridden if you pass a command to docker run.ENTRYPOINT is the command that always runs — arguments passed to docker run are appended to it rather than replacing it.# CMD alone — docker run myimage echo hi replaces the whole command
CMD ["node", "server.js"]
docker run myimage # runs: node server.js
docker run myimage echo hi # runs: echo hi (CMD fully overridden)
# ENTRYPOINT + CMD together — CMD becomes the DEFAULT ARGUMENTS to ENTRYPOINT
ENTRYPOINT ["node"]
CMD ["server.js"]
docker run myimage # runs: node server.js
docker run myimage worker.js # runs: node worker.js (only CMD's part is overridden)
This combination is the standard pattern for images meant to behave like a fixed executable with a configurable default argument — ENTRYPOINT pins down what runs, CMD supplies a default for what to run it on, and the caller can override just that part without needing to know or repeat the executable itself.
Both also come in two syntactic forms with real behavioral differences: the exec form (["node", "server.js"]) runs the command directly as PID 1, receiving signals (like SIGTERM from docker stop) directly; the shell form (node server.js, no brackets) runs it via /bin/sh -c "...", which means the shell is PID 1 and your actual process is a child — signals sent to the container go to the shell, which may not forward them, so docker stop can end up waiting the full timeout and then force-killing instead of the app shutting down gracefully. Prefer the exec form for anything that needs to handle shutdown signals cleanly.