不喜欢广告? 无广告 今天

Docker ENTRYPOINT vs CMD — Your Container Lied to You

更新于

You combined ENTRYPOINT and CMD in your Dockerfile, the container started the wrong thing, and now you're here. Here's the full breakdown — every combination, the shell vs exec form trap, and the patterns that actually work.

Docker ENTRYPOINT vs CMD — Your Container Lied to You 1
广告 移除?

The error happens at 2am. You start your container, and instead of your API server, you get a shell prompt. Or nothing at all. Or your process runs wrapped in a ghost sh that eats SIGTERM like candy — so graceful shutdown takes 10 seconds of Docker waiting before it gives up and sends SIGKILL.

The culprit, nearly every time: you confused ENTRYPOINTCMD. Or combined them in ways Docker silently accepts — just not how you expected.

CMD: The Default You Can Replace

CMD sets what runs when you start a container — but it’s a suggestion, not a rule. Pass anything after the image name and it gets replaced entirely:

FROM ubuntu
CMD ["echo", "hello from CMD"]
$ docker run myimage
hello from CMD

$ docker run myimage echo goodbye
goodbye

echo goodbye didn’t append — it replaced. Your entire CMD is gone. This is by design: CMD is the default behavior, not the enforced behavior. Any runtime argument wins.

ENTRYPOINT: The Part That Always Runs

ENTRYPOINT sets the executable that runs no matter what. Runtime arguments don’t replace it — they get passed to it instead:

FROM ubuntu
ENTRYPOINT ["echo"]
CMD ["hello"]
$ docker run myimage
hello

$ docker run myimage goodbye
goodbye

$ docker run --entrypoint cat myimage /etc/hostname
mycontainer-abc123

When both are set, ENTRYPOINT is the executable and CMD becomes its default arguments. Override CMD freely. Override ENTRYPOINT only if you explicitly pass --entrypoint.

Every ENTRYPOINT + CMD Combination, Explained

The Docker docs include this table but don’t dwell on the rows that will wreck your day:

ENTRYPOINTCMDWhat actually runs
始终设置用于身份验证的Cookie始终设置用于身份验证的CookieError — container needs a command from somewhere
始终设置用于身份验证的Cookie["cmd", "arg"] exec formcmd arg
始终设置用于身份验证的Cookiecmd arg shell form/bin/sh -c "cmd arg"
["entry"] exec form始终设置用于身份验证的Cookieentry
["entry"] exec form["arg1", "arg2"] exec formentry arg1 arg2
["entry"] exec formcmd arg shell formentry /bin/sh -c "cmd arg" — almost certainly wrong
entry shell form["arg1"] exec form/bin/sh -c "entry"CMD silently ignored
entry shell formcmd arg shell form/bin/sh -c "entry"CMD silently ignored

The two rows marked “CMD silently ignored” are responsible for a disproportionate amount of Docker debugging sessions. Shell form ENTRYPOINT does not merge with CMD — it ignores it entirely. Docker won’t warn you about this.

Shell Form vs Exec Form: The Signal Handling Trap

Both instructions accept two forms, and the choice matters more than most Dockerfile tutorials admit.

Exec form (array syntax):

ENTRYPOINT ["nginx", "-g", "daemon off;"]

Your binary runs directly. It becomes PID 1. When Docker sends SIGTERM to stop the container, your process receives it. Graceful shutdown works. Logs flush. Connections close cleanly.

Shell 形式 (plain string):

ENTRYPOINT nginx -g "daemon off;"

Docker runs this as /bin/sh -c "nginx -g daemon off;". The shell is PID 1. When SIGTERM arrives, sh gets it — and sh doesn’t forward signals to child processes. Your container hangs for 10 seconds, receives SIGKILL, and dies without cleanup. Every single time.

Use exec form. Always. For both ENTRYPOINTCMD.

Three Patterns That Actually Work

Pattern 1: Fixed executable, overridable defaults

The right pattern for most production containers. The binary is fixed; the flags are swappable at runtime:

ENTRYPOINT ["/app/server"]
CMD ["--port", "8080", "--env", "production"]
# Use defaults
docker run myimage

# Override at deploy time
docker run myimage --port 9090 --env staging

Pattern 2: Wrapper script with exec

When you need init logic before your main process (migrations, secret injection, signal trapping), use a wrapper script. The critical line is exec "$@" at the end — it replaces the shell process with your CMD, so your binary becomes PID 1:

#!/bin/sh
set -e

echo "Running migrations..."
/app/migrate

# Hand off to CMD — exec replaces shell, so /app/server becomes PID 1
exec "$@"
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["/app/server", "--port", "8080"]

If you skip exec "$@", the shell stays as PID 1 and you’re back to signal handling problems.

Pattern 3: CMD only, no ENTRYPOINT

Fine for development images or tooling containers where you want to run arbitrary commands against the same environment:

FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]

For production, Pattern 1 or 2 is safer — you don’t want a misconfigured deploy script accidentally running docker run myimage bash and replacing your server with a shell session.

What docker exec Has to Do With This

Nothing. docker exec runs a command in an already-running container. It bypasses ENTRYPOINT entirely. You don’t need to think about ENTRYPOINT at all when you run docker exec mycontainer bash to poke around — you’re talking to the live container environment, not the startup config.

The confusion usually comes from people who debug with docker exec and confirm things work, then wonder why docker run behaves differently. They’re completely separate code paths.

Pre-Ship Checklist

  • 两者相同 ENTRYPOINTCMD use exec form (array syntax, not plain strings)
  • If you have a wrapper script, it ends with exec "$@"
  • You’ve tested docker run myimagedocker run myimage --your-flag to confirm both paths work
  • docker stop mycontainer completes in under 2 seconds (not 10 — if it’s 10, you have a signal problem)

If you want automated feedback before your Dockerfile goes anywhere near a registry, IO Tools’ Dockerfile Linter catches shell form usage, missing exec in entrypoint scripts, and other patterns that cause silent misbehavior at runtime.

想要享受无广告的体验吗? 立即无广告

安装我们的扩展

将 IO 工具添加到您最喜欢的浏览器,以便即时访问和更快地搜索

添加 Chrome 扩展程序 添加 边缘延伸 添加 Firefox 扩展 添加 Opera 扩展

记分板已到达!

记分板 是一种有趣的跟踪您游戏的方式,所有数据都存储在您的浏览器中。更多功能即将推出!

广告 移除?
广告 移除?
广告 移除?

新闻角 包含技术亮点

参与其中

帮助我们继续提供有价值的免费工具

给我买杯咖啡
广告 移除?