This will be your last Dockerfile for all of tour NodeJs applications.
( A summary this article is available in here also )
Introduction to Docker and Dockerfiles
Docker has revolutionized the way we develop and deploy applications by enabling containerization. Dockerfiles are the blueprints that define the steps to create container images, encapsulating the application and its dependencies. However, single-stage Dockerfiles, while simple, can lead to inefficiencies, particularly for Node.js applications. Multi-stage Dockerfiles offer a solution, providing several advantages in terms of image size, security, and build times.
What are the stages in multi stage Dockerfile ?
- development: install all the dependencies
- build: prepares the application for production with optimized bundle size
- production: sets up the production environment
Understanding Multi-Stage Dockerfiles
A multi-stage Dockerfile consists of multiple stages, each serving a specific purpose. Unlike single-stage Dockerfiles that combine all steps into a single image, multi-stage builds separate the build environment from the final runtime environment. This separation leads to several benefits
Benefits of Multi-Stage Dockerfiles
- Reduced Image Size: By keeping the build environment (with all its dependencies) out of the final image, multi-stage builds significantly reduce the image size. This translates to faster downloads, deployments, and smaller storage requirements.
- Improved Security: Only the necessary application files and dependencies are present in the final runtime image, minimizing the attack surface and potential vulnerabilities.
- Faster Build Times: Caching intermediate build stages can significantly speed up subsequent builds, especially when dependencies have been downloaded and installed previously.
Creating a Multi-Stage Dockerfile for Node.js
Here’s a breakdown of creating a multi-stage Dockerfile for a Node.js application:
Development Stage
FROM node:20-alpine AS development
WORKDIR /usr/src/app
COPY --chown=node:node package*.json ./
RUN npm ci -f
COPY --chown=node:node . .
USER node
Explanation
- This stage sets up the development environment.
- It uses a lightweight Node.js image (
20-alpine
variant) from a public container registry. - Sets the working directory to
/usr/src/app
. - Copies
package.json
andpackage-lock.json
(oryarn.lock
) to install dependencies efficiently usingnpm ci
. - Copies the application code into the container.
- Switches to a non-root user
node
for security reasons.
Build for Production Stage
FROM node:20-alpine AS build
WORKDIR /usr/src/app
COPY --chown=node:node package*.json ./
COPY --chown=node:node --from=development /usr/src/app/node_modules ./node_modules
COPY --chown=node:node . .
RUN npm run build
RUN npm ci -f --only=production && npm cache clean --force
USER node
Explanation
- This stage prepares the application for production deployment.
- It starts from the same Node.js image as the development stage.
- Copies
package.json
to install production dependencies. - Utilizes the
node_modules
directory generated during the development stage, saving time and resources by avoiding redundant dependency installations. - Copies the application code.
- Builds the application using the specified build command (
npm run build
). - Installs only production dependencies using
npm ci
and cleans the npm cache.
Production Stage
FROM node:20-alpine AS production
ENV NODE_ENV production
COPY --chown=node:node --from=build /usr/src/app/node_modules ./node_modules
COPY --chown=node:node --from=build /usr/src/app/dist ./dist
CMD [ "node", "dist/main.js" ]
Explanation
- This stage sets up the production environment.
- Starts from the same Node.js image.
- Copies the production dependencies and the built application (
dist
directory) from the build stage. - Specifies the command to run the application (
node dist/main.js
) as the default command when the container starts.
Complete Dockerfile
FROM node:20-alpine AS development
WORKDIR /usr/src/app
COPY --chown=node:node package*.json ./
RUN npm ci -f
COPY --chown=node:node . .
USER node
FROM node:20-alpine AS build
WORKDIR /usr/src/app
COPY --chown=node:node package*.json ./
COPY --chown=node:node --from=development /usr/src/app/node_modules ./node_modules
COPY --chown=node:node . .
RUN npm run build
RUN npm ci -f --only=production && npm cache clean --force
USER node
FROM node:20-alpine AS production
ENV NODE_ENV production
COPY --chown=node:node --from=build /usr/src/app/node_modules ./node_modules
COPY --chown=node:node --from=build /usr/src/app/dist ./dist
CMD [ "node", "dist/main.js" ]
Best Practices for Multi-Stage Dockerfiles
- Caching: Leverage Docker’s caching mechanism to optimize build times by caching intermediate stages.
- Multi-line Commands: Use continuation characters (
\
) or escape characters (\"
) for multi-line commands within Dockerfile instructions. - Environment Variables: Manage environment variables for different stages or throughout the build process using
ENV
instructions. - Multi-arch Builds: Consider building multi-architecture images for different CPU architectures using the
BUILD
instruction and the--platform
flag
Common Challenges and Troubleshooting
- Identifying Build Issues: Use
docker build -t <image-name>:<tag> -v .:/app --no-cache .
to identify build errors and troubleshoot issues. - Image Size Optimization:
- Analyze image size using
docker image inspect
anddu -sh
. - Consider further strategies like multi-arch builds, removing unnecessary files, and using smaller base images.
- Analyze image size using
Conclusion
This Dockerfile is structured to facilitate the development, build, and deployment of a NestJS aMulti-stage Dockerfiles offer a powerful approach to optimizing Node.js application deployments. By embracing this technique, you can achieve significant reductions in image size, improve security, and streamline your build processes. Experiment with multi-stage Dockerfiles in your projects to unlock these benefits and enhance your development workflow. Start by implementing the steps and best practices outlined above, and explore further resources to delve deeper into advanced multi-stage build techniques.
References
Multi-stage builds
Learn about multi-stage builds and how you can use them to improve your builds and get smaller imagesdocs.docker.com