Docker CMD Instruction Can Be Tricky in Multi-Stage Builds

In Dockerfiles, the CMD instruction defines the command to be executed in containers created from a Docker image. There can only be one CMD instruction in a Dockerfile, if more than one is defined, only the last one takes effect. As example:

FROM alpine

CMD ["echo", "first instruction"]
CMD ["echo", "second instruction"]

In this case the second CMD instruction will be executed. The output of running a container will be:

bash-5.2$ docker run multiple-cmd:latest
second instruction

In single-stage builds it doesn’t matter where the CMD instruction is defined in Dockerfile, as example, in the below file although the CMD instruction is defined in the middle of the file, it will still be used as the default command to be executed by containers:

FROM alpine
CMD ["echo", "Hello!"]
RUN apk update

However, in multi-stage builds, if the same command will be executed in all stages, the CMD instruction should be defined before all the stages:

FROM alpine AS base

--> CMD ["echo", "Hello!"] <--

FROM base AS stage1
RUN echo "stage1 instructions"

FROM base AS stage2
RUN echo "stage2 instructions"

In this case, the images built for stage1 and stage2 will execute the same command:

  • For stage1:

    > docker build -f Dockerfile --target stage1 --tag docker-image:stage1 .
    > docker run docker-image:stage1
    Hello!
    
  • For stage2:

    > docker build -f Dockerfile --target stage2 --tag docker-image:stage2 .
    > docker run docker-image:stage2
    Hello!
    

The tricky case happens when the CMD instruction is defined after one of the stages, and there is only one CMD instruction in the file, as example:

FROM alpine AS base

FROM base AS stage1
RUN echo "stage1 instructions"
--> CMD ["echo", "Hello!"] <--

FROM base AS stage2
RUN echo "stage2 instructions"

In this case, building an image for stage2 will skip the CMD instruction and inherit the CMD instruction of the base image (alpine):

> docker build -f Dockerfile --target stage2 --tag docker-image:tricky .
> docker run docker-image:tricky
>

The instruction was skipped because docker builder executes the instructions in the dependent stages (in this case base stage) in addition to the instructions of the specified stage (stage2). But doesn’t execute any of the instructions in other stages.