Multi-Stage Dockerfile for Node.js

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 and package-lock.json (or yarn.lock) to install dependencies efficiently using npm 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 and du -sh.
    • Consider further strategies like multi-arch builds, removing unnecessary files, and using smaller base images.

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

node
Node.js is a JavaScript-based platform for server-side and networking applications.hub.docker.com

3 thoughts on “Multi-Stage Dockerfile for Node.js

Leave a comment