Today, I’ll walk you through how to dockerize a NestJS application. This will help you package your app into a container that can run consistently across different environments. Let’s get started!
Step 1: Setting Up the Dockerfile
First, we need to create a Dockerfile in the root directory of your NestJS project. This file will contain instructions for building your Docker image.
Here’s what the basic Dockerfile might look like:
# Use Node.js version 20 as the base image
FROM node:20
# Set the working directory in the container
WORKDIR /usr/src/app
# Copy package.json and package-lock.json
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy the rest of the application code
COPY . .
# Build the application
RUN npm run build
# Run the application
CMD ["node", "dist/main.js"]
This Dockerfile does the following:
- Uses Node.js version 20 as the base image.
- Sets the working directory to
/usr/src/app. - Copies
package.jsonandpackage-lock.jsonto the working directory. - Installs the dependencies using
npm install. - Copies the rest of the application code into the container.
- Builds the application using
npm run build. - Specifies the command to run the application with
node dist/main.js.
Create a .dockerignore file to exclude unnecessary files from the Docker image.
node_modules
npm-debug.log
dist
.git
.env
*.md
.gitignore
Step 2: Testing the Docker Container Locally
Now, let’s test our Docker image locally to make sure everything works as expected.
Build the Docker Image:
Open your terminal at the root of your project and run:
docker build -t nest-app .
This command builds the Docker image and tags it with nest-app.
Run the Docker Container:
After building the image, you can run it with:
docker run -p 3000:3000 nest-app
This command starts a new container from the nest-app image and maps port 3000 of the container to port 3000 on your local machine.
Verify the Application:
Open your web browser and go to http://localhost:3000. If everything is set up correctly, you should see your NestJS application running.
Step 3: Optimizing for Production
To optimize the Dockerfile for production, we’ll make a few changes to reduce the image size and improve security.
Use Alpine Node Images:
Alpine images are smaller and more efficient. Update the base image to:
FROM node:20-alpine
Set NODE_ENV Environment Variable:
Set NODE_ENV to production to optimize the application for production:
ENV NODE_ENV production
Use npm ci Instead of npm install:
npm ci is more reliable for automated environments like Docker builds. Replace RUN npm install with:
RUN npm ci
Add USER Instruction:
Run the application as a non-root user for better security. Add this after installing dependencies:
USER node
Use Multistage Builds:
Multistage builds allow you to create a smaller final image by separating the build and runtime environments. Here’s an example of a multistage Dockerfile:
# Development stage
FROM node:20-alpine AS development
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci
COPY . .
USER node
# Build stage
FROM node:20-alpine AS build
WORKDIR /usr/src/app
COPY package*.json ./
COPY --from=development /usr/src/app/node_modules ./node_modules
COPY . .
RUN npm run build
ENV NODE_ENV production
RUN npm ci --only=production && npm cache clean --force
USER node
# Production stage
FROM node:20-alpine AS production
WORKDIR /usr/src/app
COPY --from=build /usr/src/app/node_modules ./node_modules
COPY --from=build /usr/src/app/dist ./dist
CMD ["node", "dist/main.js"]
This setup uses three stages:
-
development: For local development with all dependencies. -
build: Builds the application for production. -
production: The final stage that runs the production build.
Step 4: Rebuilding and Running the Optimized Image
After updating your Dockerfile, rebuild the image:
docker build -t nest-app .
Then, run the new optimized container:
docker run -p 3000:3000 nest-app
Check the size of the new image with:
docker images
You should see a significant reduction in the image size, making it more efficient for production use.
Troubleshooting Common Issues
-
Error: Cannot find module ‘webpack’: Ensure you’re using the correct Node.js version in your base image. For example, use
FROM node:20-alpineinstead ofFROM node:14-alpine. -
Error: nest command not found: This usually happens if the Nest CLI is not installed. In a multistage build, make sure you copy over the
node_modulesdirectory from the development stage where the CLI is installed.
Using Different Package Managers
If you’re using pnpm instead of npm, your Dockerfile would look slightly different. Here’s an example:
# Development stage
FROM node:20 AS development
RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
WORKDIR /usr/src/app
COPY pnpm-lock.yaml ./
RUN pnpm fetch --prod
COPY . .
RUN pnpm install
USER node
# Build stage
FROM node:20 AS build
RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
WORKDIR /usr/src/app
COPY pnpm-lock.yaml ./
COPY --from=development /usr/src/app/node_modules ./node_modules
COPY . .
RUN pnpm build
ENV NODE_ENV production
RUN pnpm install --prod
USER node
# Production stage
FROM node:20-alpine AS production
WORKDIR /usr/src/app
COPY --from=build /usr/src/app/node_modules ./node_modules
COPY --from=build /usr/src/app/dist ./dist
CMD ["node", "dist/main.js"]
Using Fastify with NestJS
If you’re using Fastify instead of Express, you’ll need to modify your main.ts to listen on 0.0.0.0:
import { NestFactory } from '@nestjs/core';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);
await app.listen(process.env.PORT || 3000, '0.0.0.0');
}
bootstrap();
That’s it! You now have a dockerized NestJS application that’s optimized for production. If you want to deploy your NestJS app, check out sliplane.io
Cheers,
Jonas
