In many projects that are containerized, especially in cases where development is also done locally withdocker-compose, teams often have two Dockerfiles, 1 for Development, the other for Production. If you happen to have multiple environments likepre-prod
,staging
and so on, some teams could have different Dockerfiles for these environments.
docker build has a very nice--target
option, that could be used to target a specificstage from the Dockerfile.
Let's take a very simple example: let's say we are trying to build a simple frontend app with npm, on Development, we start the app withnpm run dev
but on production, we need to runnpm run generate
and then copy the artefacts to Nginx's html folder to run, we would need to have a Dockerfile as below for Development:
FROM node:18-alpineWORKDIR /usr/srcCOPY ./package.json ./RUNnpminstallCOPY . .CMD ["npm", "run", "dev"]EXPOSE 3000
This is simply a Dockerfile that starts a frontend app withnpm run dev
exposing port 3000.
However, we typically do not runnpm run dev
on production, extra steps are required to generate the static files from the application and then these files are served bynginx
or any other webserver.
To write the production Dockerfile for this app, we need to build the app and also serve with the desired web server.
Docker Multi-Stage builds
Dockermulti-stage builds allows us to use multipleFROM
commands in a single Dockerfile, it allows us to selectively copy files from different stages into another.
For our case, we need a base, dev, build and prod stages, we are going to build our app for production withnpm run generate
and we need to serve the built files with nginx. Our production Dockerfile will therefore look like this:
FROMnode:18-alpineASbuildWORKDIR /usr/srcCOPY ./package.json ./RUNnpminstallCOPY . .RUNnpm run generateFROM nginx:1.25.1-alpineCOPY --from=build /usr/src/dist /usr/share/nginx/htmlEXPOSE 80CMD ["nginx", "-g", "daemon off;"]
Notice theAS build
on the first stage, all commands before any otherFROM
is considered a stage on their own.
After runningnpm run generate
in the app, it generates files into/usr/src/dist
.
In the second stage, where we are basing that onnginx
, we simply copy those files from the build stage using
COPY --from=build /usr/src/dist /usr/share/nginx/html
Updating this Dockerfile to serve both Dev and Production
Now that we have seen multi-stage docker builds in action, we can leverage this by having abase
stage,build
stage and finally aprod
stage.
To achieve this we update the docker file to this:
FROMnode:18-alpineasbaseWORKDIR /usr/srcCOPY ./package.json ./RUNnpminstallCOPY . .FROMbaseASdevEXPOSE 3000CMD ["npm", "run", "dev"]FROMbaseASbuildRUNnpm run buildFROMnginx:1.25.1-alpineasprodCOPY --from=build /usr/src/dist /usr/share/nginx/htmlEXPOSE 80CMD ["nginx", "-g", "daemon off;"]
There are 4 stages in this docker file.
Thebase
stage
In this stage, we build the base image, copying the files from the project and simply doingnpm install
Thedev
stage
This is the stage that we want to target if we are building this image fordev
. More on how to do this in a bit.
Thebuild
stage
This stage exists so that theprod
stage would be able to copy the generated static files for serving.
Theprod
stage
This is the stage that we would target when building this image for production.
How to target astage
in Dockerfile
Finally, now that we have a Dockerfile that can servedev
andproduction
, to build an image that is specific for dev, we use the--target argument as so:
docker build-t mutistage:latest--target=dev
This would make Docker only build up till thedev
stage and nothing else.
Similarly to build for production we simply do
docker build-t mutistage:latest--target=prod
With this, we can maintain the same file for these two environments.
Top comments(3)

- Email
- LocationUnited States, Virginia
- EducationUniversity of Mary Washington (current student)
- Joined
I'm about a year late for this, but thank you so much! Saved me a few hours.
For further actions, you may consider blocking this person and/orreporting abuse