Skip to content

docs: clarify that ENTRYPOINT discards an inherited CMD - #7009

Open
basisworks wants to merge 1 commit into
moby:masterfrom
basisworks:docs-entrypoint-discards-inherited-cmd
Open

docs: clarify that ENTRYPOINT discards an inherited CMD#7009
basisworks wants to merge 1 commit into
moby:masterfrom
basisworks:docs-entrypoint-discards-inherited-cmd

Conversation

@basisworks

Copy link
Copy Markdown

Closes the documentation half of moby/moby#35027 (open since 2017, no replies).

The gap

The reference already carries a short note under Understand how CMD and ENTRYPOINT interact:

If CMD is defined from the base image, setting ENTRYPOINT will reset CMD to an empty value. In this scenario, CMD must be defined in the current image to have a value.

That's correct but under-specified, and it lives at the bottom of a section a reader only reaches if they already suspect the interaction exists. Two things it doesn't say:

  1. The rule is scoped to the build stage, not the image — "current image" is ambiguous in a multi-stage file.
  2. Order doesn't matter. A CMD in the same stage survives whether it is written before or after ENTRYPOINT. Readers reasonably assume ENTRYPOINT after CMD would clobber it.

Neither the ENTRYPOINT section nor the CMD section mentions the behavior at all, so the most common way to hit it — adding a wrapper entrypoint to a base image that only sets CMD, like nginx — gives you an image that exits immediately with no obvious cause.

Behavior verified against the source

dispatchEntrypoint in frontend/dockerfile/dockerfile2llb/convert.go:

d.image.Config.Entrypoint = args
if !d.cmd.IsSet {
    d.image.Config.Cmd = nil
}

d.cmd.IsSet is set by MarkUsed from validateUsedOnce at the top of dispatchCmd, and dispatchState is per-stage. So:

  • CMD then ENTRYPOINTIsSet is true, CMD is preserved.
  • ENTRYPOINT then CMDCMD is cleared, then immediately reset by the CMD instruction.

Both orders keep a stage-local CMD; only an inherited one is dropped. The classic builder in moby/moby (daemon/builder/dockerfile/dispatchers.go) does the same thing via d.state.cmdSet, so the documented rule holds for both.

Changes

  • Expanded the note to state the stage scoping and the order-independence.
  • Added the wrapper-script example that motivates the rule, with the fix.
  • Added one line under ENTRYPOINT pointing at the interaction section.

Docs only — no code or behavior change.

The reference noted that setting ENTRYPOINT resets a base image's CMD,
but the note was easy to miss and left two things unsaid: that the rule
is scoped to the build stage, and that the order of CMD and ENTRYPOINT
within that stage doesn't matter.

Expand the note, add the entrypoint-wrapper case that people actually
trip over, and cross-reference it from the ENTRYPOINT section.

Signed-off-by: basisworks <basisworfks.dev@gmail.com>
Comment on lines +2132 to +2133
Setting `ENTRYPOINT` also discards any `CMD` inherited from the base image,
unless the same build stage defines its own `CMD`. For more information, see

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Setting `ENTRYPOINT` also discards any `CMD` inherited from the base image,
unless the same build stage defines its own `CMD`. For more information, see
Setting `ENTRYPOINT` also discards any `CMD` inherited from the base image.
For more information, see

Comment on lines +2379 to +2380
> have one. The order of the two instructions doesn't matter: a `CMD` in the
> same stage is kept whether it appears before or after `ENTRYPOINT`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather drop this since it's irrelevant.

Suggested change
> have one. The order of the two instructions doesn't matter: a `CMD` in the
> same stage is kept whether it appears before or after `ENTRYPOINT`.
> have one.

Comment on lines +2382 to +2409
This is easy to miss when you add an entrypoint wrapper to a base image that
only defines a `CMD`. The following Dockerfile produces an image with no `CMD`
at all, because the `nginx` image supplies its command as a `CMD`, and setting
`ENTRYPOINT` discards it:

```dockerfile
FROM nginx
COPY entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
```

A wrapper script ending in `exec "$@"` receives no arguments here, so it runs
`exec` with nothing to execute, falls through to the end of the script, and the
container exits immediately. Restate the command to keep the base image's
behavior:

```dockerfile
FROM nginx
COPY entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]
```

To check what a base image sets, inspect its configuration:

```console
$ docker image inspect --format '{{json .Config.Cmd}}' nginx
```

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is too specific for the Dockerfile reference. The note above already documents the general behavior. This turns that into troubleshooting for one particular wrapper-script pattern.

Can we remove this example and the inspection command, and keep the concise explanation of the inherited CMD behavior above? If this needs a worked example, a synthetic base image would demonstrate the rule more accurately, but I don’t think the reference needs that level of detail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants