This is a step-by-step guide to writing a Dockerfile for a NestJS project that creates a production-optimized image.
Then it's configured with this Dockerfilelocal developmentas well as deployment in containers, for example withrun in the cloud.
Set up? let's go diving
PS: If you just want to copy-paste the production-ready Dockerfile, just go ahead with thisSection.
Get a free NestJS cheat sheetGet access to my free NestJS cheat sheet and learn advanced tips and techniques to improve developer workflow and NestJS applications in production.
index
- Writing the Dockerfile
- Test the container locally
- Optimize the Dockerfile for production
- Using images of alpine knots
- Add a NODE_ENV environment variable
- Use npm ci instead of npm install
- USER Declaration
- Using Layered Builds
- put it all together
- Troubleshooting
- Error: Could not find module 'webpack'
- Error: nest command not found
- Dockerfile with pnpm package manager
- NestJS Dockerfile con Fastify
- Conclution
Writing the Dockerfile
A container image is an isolated software package that contains everything you need to run your code. You can define container images by writing aDockerfile
containing the instructions for creating the image.
Now let's add the Dockerfile:
toqueDockerfile
And then we add the instructions to the Dockerfile. See the comments that explain each step:
Dockerfile
# basic imageVONus: 18# Create application directoryWORKED/usr/src/application# Use a wildcard to ensure that "package.json" and "package-lock.json" are copiedCOPY OFPackage*.json ./# Install application dependenciesCORREinstall npm# Source of the package applicationCOPY OF. .# Create a "dist" folder with the production buildCORREnpm-Run-Create# Start the server with the production buildCMD["That","dist/principal.js"]
resembling a.ignore.git
file we can add a.dockerignore
File that prevents certain files from being included in the image build.
toque.dockerignore
Then exclude the following files from imaging:
.dockerignore
Dockerfile.dockerignorenode_modulesnpm-debug.logDistance
Test the container locally
Now let's test locally that the Dockerfile behaves as expected.
First, let's create the image using your terminal command in the root of your project (you can change itnest-cloud-run
with the name of your project). don't forget those.
!
stevedorecompile -t nest-cloud-run.
You can verify that the image was created by runningdockable images
This will produce a list of Docker images you have on your local machine:
stevedorePhotosREPOSITORY TAG ID SIZE OF CREATED IMAGEnest-cloud-run spätestens 004f7f22213931seconds before10,24 GB
Now, let's start the container and run the image with this command (make sure to use the same image name as above):
stevedorerun -p80:3000 nest-cloud-run
You can now access the NestJS application by visitinghttp://localhost
in your browser (onlyhttp://localhost
no port numbers).
While running the container, I ran into some problems on my computer, mainly due to port conflicts on other containers I was running.
If you encounter similar problems, try running the commanddocker rm -f $(docker ps -aq)
which stops and kills all running containers.
Optimize the Dockerfile for production
Now that we've confirmed that the image works locally, let's try to reduce the size of the image and make it more efficient for production. We also want to make sure that the image is as safe as possible.
Deployment tools like Cloud Run take image size into account when calculating charges, so it's a good idea to keep the image size as small as possible.
Running the commanddockable images
gives us the size of our image:
stevedorePhotosREPOSITORY TAG ID SIZE OF CREATED IMAGEnest-cloud-run spätestens 004f7f22213931seconds before10,24 GB
1.24 GB is too big! Let's dive back into ourDockerfile
and make some adjustments.
Using images of alpine knots
That's itrecommendedto use images of alpine knots when trying to optimize image size. Wearus: 18-alpine
Instead ofus: 18
It single-handedly reduces the image size from 1.24 GB to 466 MB.
Add a NODE_ENV environment variable
Many libraries have built-in optimizations when theNODE_ENV
The environment variable is set toProduction
, so we can set this environment variable on the Dockerfile build by adding the following line to our Dockerfile:
ENVProduction NODE_ENV
By the way, checkit's tutorialif you are interested in using environment variables with configuration files in NestJS.
Use npm ci instead of npm install
npm recommends using itI like you
Instead ofinstall npm
in building your image. Here is a quote fromyour websiteto the reason:
"I like you
This is similar toinstall npm
, except that it's designed to be used in automated environments such as test platforms, continuous integration, and deployment, or in any situation where you want to make sure you're doing a clean install of your dependencies.
This fits perfectly with what we're doing, so let's use it.I like you
Instead ofinstall npm
in our dockerfile.
CORREI like you
USER statement
By default, if youOF THE USER
statement in your Dockerfile, the image will run with root privileges. This is a security risk, so let's add oneOF THE USER
Instructions for our Dockerfile.
The node image we are using already has a username created for us.that
, then we use this:
OF THE USERthat
as long as you use themCOPY OF
It's also a good idea to add a flag to ensure the user has the correct permissions.
You can achieve this using--chown=it:it
as long as you use themCOPY statement
, for example:
COPY OF --chown=that thatPackage*.json ./
Using Layered Builds
In your Dockerfile you can definemulti-level structuresThis allows you to sequentially create the most optimized image by creating multiple images.
In addition to using a small image, multi-stage builds are where the biggest optimizations can be made.
Dockerfile
################### BUILD FOR LOCAL DEVELOPMENT##################VONus: 18-alpineSedeveloping# ... your build instructions for development here################### BUILD FOR PRODUCTION################### Base image for productionVONus: 18-alpineSebuild up# ... your build instructions here################### PRODUCTION################### Base image for productionVONus: 18-alpineSeProduction# ... here are your production instructions
This multi-stage construction uses 3 stages:
developing
- This is the stage where we build the image for local development.build up
- This is the stage where we create the image for production.Production
- We copy the relevant production build files and start the server.
If you're not interested in using Docker to run your NestJS application locally, you can combine step 1 and step 2 into one phase.
However, the beauty of the multi-step setup above is that you have a single Dockerfile that you can use in local development (combined with adocker-compose.yml
file) and also creates a production-optimized Docker image.
If you're interested in using this Dockerfile layered with Docker Compose for local development (with hot reload), take a lookthis post.
put it all together
Using all the techniques described above, here is the Dockerfile we will use to build our production-optimized image:
Dockerfile
################### BUILD FOR LOCAL DEVELOPMENT##################VONus: 18-alpineSedeveloping# Create application directoryWORKED/usr/src/application# Copy the application dependency manifests to the container image.# A wildcard is used to ensure that "package.json" and "package-lock.json" (if available) are copied.# Copying this first will prevent npm installation from being rerun every time the code changes.COPY OF --chown=that thatPackage*.json ./# Install the application dependencies using the "npm ci" command instead of "npm install".CORREI like you# Source of the package applicationCOPY OF --chown=that that. .# Use image node user (instead of root user)OF THE USERthat################### BUILD FOR PRODUCTION##################VONus: 18-alpineSebuild upWORKED/usr/src/applicationCOPY OF --chown=that thatPackage*.json ./# To run "npm run build", we need access to the Nest CLI, which is a developer dependency. In the development phase above, we ran `npm ci` which installed all the dependencies so we could copy the node_modules directory from the development imageCOPY OF --chown=that that --von=developing/usr/src/app/node_modules ./node_modulesCOPY OF --chown=that that. .# Run the build command that creates the production packageCORREnpm-Run-Create# set the environment variable NODE_ENVENVProduction NODE_ENV# Running `npm ci` removes the existing node_modules directory and passing --only=production ensures that only production dependencies are installed. This ensures that the node_modules directory is as optimized as possible.CORREnpm ci --only=producción && npm cache clean --forceOF THE USERthat################### PRODUCTION##################VONus: 18-alpineSeProduction# Copy the packaged code from the build phase to the production imageCOPY OF --chown=that that --von=build up/usr/src/app/node_modules ./node_modulesCOPY OF --chown=that that --von=build up/usr/src/app/dist./dist# Start the server with the production buildCMD["That","dist/principal.js"]
After updating yourDockerfile
, you need to run the commands again to create your image:
stevedorecompile -t nest-cloud-run.
And then the command to start your container:
stevedorerun -p80:3000 nest-cloud-run
when you walkdockable images
To check the size of our image again, you'll notice that it's now significantly smaller:
stevedorePhotosREPOSITORY TAG ID SIZE OF CREATED IMAGEnest-cloud-run spätestens 004f7f22213931Seconds before 189 MB
Troubleshooting
You may encounter the following errors:
Error: Could not find module 'webpack'
You are probably using the wrong node version in your base image if you get errors like the following:
Error: Could not find module 'webpack'
For example, instead of usingDE us: 14-alpine
, useDE us: 18-alpine
to solve this problem.
Error: nest command not found
when you runnpm-Run-Create
, use the Nest CLI to generate the build files.
Nest CLI is a developer dependency. So if you get the errornest command not found
, you should:
- (Recommended Option): Run
npm-Run-Create
in a multi-tier Dockerfile setup where you can add production and development dependencies (usingI like you
) - Update your package.json file to include the Nest CLI package in your production dependencies. The only downside to this approach is that it increases the size of your node_modules, which results in a larger image.
The recommended option is the one implemented in the Dockerfile mentioned earlier in this tutorial if you want an example of how it works.
Dockerfile with pnpm package manager
If you use pnpm as a package manager in your NestJS project, the Dockerfile should look like this:
Dockerfile
################### BUILD FOR LOCAL DEVELOPMENT##################VONus: 18SedevelopingCORREcurl -f https://get.pnpm.io/v6.16.js | no - add --global pnpmWORKED/usr/src/applicationCOPY OF --chown=that thatpnpm-lock.yaml ./CORRElook for pnpm --prodCOPY OF --chown=that that. .CORREinstall pnpmOF THE USERthat################### BUILD FOR PRODUCTION##################VONus: 18Sebuild upCORREcurl -f https://get.pnpm.io/v6.16.js | no - add --global pnpmWORKED/usr/src/applicationCOPY OF --chown=that thatpnpm-lock.yaml ./COPY OF --chown=that that --von=developing/usr/src/app/node_modules ./node_modulesCOPY OF --chown=that that. .CORREpnpm-buildENVProduction NODE_ENVCORREpnpm install --prodOF THE USERthat################### PRODUCTION##################VONus: 18-alpineSeProductionCOPY OF --chown=that that --von=build up/usr/src/app/node_modules ./node_modulesCOPY OF --chown=that that --von=build up/usr/src/app/dist./distCMD["That","dist/principal.js"]
NestJS Dockerfile con Fastify
If you are using Fastify as your server in NestJS instead of the default Express server, you need to change the server to be listed.0.0.0.0
.
For example, I would edit theEar()
paper inprincipal.ts
File, Archive:
principal.ts
to import {nest factory} Von '@nestjs/núcleo';to import {Fastify Adapter,Nest Fastify app,} Von '@nestjs/platform-fastify';to import {application module} Von './aplicación.módulo';asynchronous occupation Ear() { untilApplication= expectnest factory.cry<Nest Fastify app>(application module, nuevo Fastify Adapter(), ); expectApplication.I'm listening(process.environment.PORTA || 3000, '0.0.0.0');}Ear();
This is noticeable insideFastify-Documentsif you want to read more about it.
Conclution
In summary, here is our production-optimized Docker image for a NestJS project (without explanatory comments):
Dockerfile
################### BUILD FOR LOCAL DEVELOPMENT##################VONus: 18-alpineSedevelopingWORKED/usr/src/applicationCOPY OF --chown=that thatPackage*.json ./CORREI like youCOPY OF --chown=that that. .OF THE USERthat################### BUILD FOR PRODUCTION##################VONus: 18-alpineSebuild upWORKED/usr/src/applicationCOPY OF --chown=that thatPackage*.json ./COPY OF --chown=that that --von=developing/usr/src/app/node_modules ./node_modulesCOPY OF --chown=that that. .CORREnpm-Run-CreateENVProduction NODE_ENVCORREnpm ci --only=producción && npm cache clean --forceOF THE USERthat################### PRODUCTION##################VONus: 18-alpineSeProductionCOPY OF --chown=that that --von=build up/usr/src/app/node_modules ./node_modulesCOPY OF --chown=that that --von=build up/usr/src/app/dist./distCMD["That","dist/principal.js"]
Here are some additional resources related to production deployment that you may find useful:
- Using the NestJS Logger
- Add a CI pipeline with some automated unit tests
- Deploy the NestJS app to Cloud Run
Do you have any other tweaks I can make to the docker image above? Write them in the comments below!
FAQs
How do I deploy NestJS app to production? ›
- Prerequisites.
- Start a NestJS project.
- Configure a PORT environment variable.
- Prepare the Docker image.
- Test the container locally.
- Manually deploying to Cloud Run.
- Check your gcloud CLI project is set.
- Use gcloud run deploy.
Using a minimal base image:
It is generally recommended to use a minimal base image, such as Alpine Linux, as a starting point for building a Docker image. This can help to reduce the size and complexity of the final image, leading to better performance and faster build times.
Multiple Dockerfiles
Staging and production should be using the same image, built from the same Dockerfile to guarantee that they are as-similar-as-possible.
- Build images.
- Run your image as a container.
- Use containers for development.
- Run tests.
- Configure CI/CD.
- Deploy your app.
- Writing the Dockerfile.
- Test the container locally.
- Optimize Dockerfile for production.
- Use Alpine node images.
- Add a NODE_ENV environment variable.
- Use npm ci instead of npm install.
- The USER instruction.
- Use multistage builds.
326 companies reportedly use NestJS in their tech stacks, including kevin., MAK IT, and quero.
How to use Docker in production environment? ›- Constantly Changing Technology Ecosystem. ...
- Enforcing Policy and Controls. ...
- Deploying Containers Across Environments. ...
- Start Small. ...
- Use Docker Hosting Services. ...
- Use a Private Image Registry and Scan Images. ...
- Docker Monitoring and Logging.
Docker is great for developing web applications, but if your end-product is a desktop application, then we would suggest you not to use Docker. As it doesn't provide the environment for running the software with a graphical interface, you would need to perform additional workarounds.
Do I need to build Docker every time? ›You only need to build the image once, and use it until the installed dependencies (like Python packages) or OS-level package versions need to be changed. Not every time your code is modified. Just because you're mounting the code directory, does not mean you can't ADD code to the image.
Is Dockerfile deprecated? ›The post quickly allayed fears with a TL;DR: "Docker as an underlying runtime is being deprecated in favor of runtimes that use the Container Runtime Interface (CRI) created for Kubernetes.
Is it OK to use Docker in production? ›
- Docker integrates perfectly with the concept of DevOps, especially in the area of versioning: development and production are carried out in the same container. Put simply, if the application works on the Dev side, it will also work on the Ops side.
Can you have 2 Dockerfiles? ›Introduction. Docker is a handy tool for containerization. It's so useful that sometimes, we want to have more than one Dockerfile in the project. Unfortunately, this goes against the straightforward convention of naming all Dockerfiles just “Dockerfile”.
Can you publish a port in a Dockerfile? ›You can expose a port through your Dockerfile or use --expose and then publish it with the -P flag. This will bind the exposed port to your Docker host on a random port (verified by running docker container ls ). You can expose a port through your Dockerfile or use --expose and then publish it with the -p 80:80 flag.
How do you deploy a project to production? ›- Automate As Much As Possible. ...
- Build and Pack Your Application Only Once. ...
- Deploy the Same Way All the Time. ...
- Deploy Using Feature Flags In Your Application. ...
- Deploy in Small Batches, and Do It Often.
JWT or JSON Web Token is an industry standard RFC 7519 method for representing claims securely between two parties. Passport is the most popular Node authentication library, well-known by the community and successfully used in many production application, NestJS has supported it outside the box with @nestjs/passport.
What ORM does NestJS use? ›For integrating with SQL and NoSQL databases, Nest provides the @nestjs/typeorm package. Nest uses TypeORM because it's the most mature Object Relational Mapper (ORM) available for TypeScript. Since it's written in TypeScript, it integrates well with the Nest framework.
Should I Dockerize my application? ›Docker is very useful for web applications running on a server or console-based software. But if your product is a standard desktop application, especially with a rich GUI, Docker may not be the best choice.
Why not to use NestJS? ›...
Do not use NestJS if:
- You are building micro-services. ...
- You have complicated API arguments or responses that need good documentation and validation. ...
- Your project is a one-off.
Another use case can be enterprise-level web applications and ecommerce applications. NestJS is better suited for these as it is scalable, well-structured, and great for building large web applications. For building fintech and streaming applications, ExpressJS is better suited.
Is NestJS difficult to learn? ›NestJS provides very clean and well-documented guides for beginners to build simple to complex applications with the NestJS typescript framework. With the documentation, it's straightforward to get started, and almost all your development questions have already been covered in the documentation.
How do I run NestJS in production mode? ›
- pull the required repo into a 'hosted' directory.
- check the node version.
- install node_modules and build native scripts etc.
- build the production distribution.
- run the production JS scripts.
- Automate As Much As Possible. ...
- Build and Pack Your Application Only Once. ...
- Deploy the Same Way All the Time. ...
- Deploy Using Feature Flags In Your Application. ...
- Deploy in Small Batches, and Do It Often.
- Create a simple Node. ...
- Write the Dockerfile and build the Docker image.
- Push the Docker image to the GitHub container registry.
- Deploy the Dockerized Node. ...
- Automate deployment with GitHub Actions.
- Introduction.
- Basic tips.
- Deployment requirements.
- Step 1: get the code in the deployment branch.
- Step 2: get the code on the deployment host.
- Step 3: configuration and other prep work.
- Step 4: synchronize the changes to the cluster.
- Test and monitor your live code.