Docker Multi-Stage for Divergence in Dockerfile

2 min readJan 16, 2023


PS: I am not a big fan of this approach. I prefer having separate dockerfiles for local and CI. That allows both of them to diverge independently, even though there might be some duplication. But if you want to have multiple builds with minor differences using the same dockerfile, you can consider this approach.

Docker Multi-Stage build is a powerful tool that can help you in a lot of different scenarios. The scenario we’re going to discuss today is actually quite simple: We want all our hosted environments and our local environments to run the app differently. For example, on the local machine, you would not want your Java build to be created everytime, or you might want to run your node app in watch mode.

Here is an example of how you can achieve it.

I’ve taken the simplest possible node app you can create. We have 2 ways to start the app — start and start:local . Assume that start runs the app in a standard manner, whereas start:local runs it in watch mode.

You can use this Dockerfile to support both local and hosted environments —

FROM node:16 as local
COPY . .
RUN yarn install
RUN yarn build
EXPOSE 33333
CMD ["yarn", "start:local"]

FROM local as integration
CMD ["yarn", "start"]

With multi-stage builds, docker will use the entire Dockerfile to build the app, and the last CMD will replace the one above it, unless we pass an intermediate stage as our target.

If we explicitly specify the target local , it will then run the app in watch mode (yarn start:local).

docker build --target local .

If we explicitly specify integration as our target, we’ll get the hosted version (yarn start).

docker build --target integration .

If we don’t specify any target, it builds the app till the end, and the last CMD replaces the previous one, so we get the hosted version (yarn start).

docker build .

Important Note for Docker Compose

Docker Compose has a bug (or a feature), which will not give you the correct build even if you specify the correct intermediate target, unless you explicitly pass the --build flag, which defeats the purpose. So if you’re using docker-compose for local development, multi-stage builds could very well be meaningless.