NestJS is a progressive Node.js framework for building server-side applications. It brings structure and strong typing to backend development with its modular architecture and TypeScript-first approach. Docker is the natural choice for packaging NestJS apps into portable, reproducible containers. This guide covers the full containerization process, from a basic Dockerfile to production optimizations, health checks, and database connectivity.
Prerequisites
You need the following installed:
- Node.js 20.11+ and npm
- Docker Engine with Docker Compose v2+
- NestJS CLI (
npm install -g @nestjs/cli)
Creating a NestJS Project
Skip this if you have an existing project.
Scaffold a new NestJS application:
# Create a new NestJS project
nest new my-nest-app
cd my-nest-appVerify the build:
# Compile TypeScript to JavaScript
npm run buildThe compiled output goes to the dist/ directory.
Understanding NestJS Build Output
NestJS compiles TypeScript into JavaScript. The production container only needs the compiled dist/ directory, node_modules (production dependencies only), and the package.json. You do not need TypeScript, the NestJS CLI, or dev dependencies at runtime.
Writing the Dockerfile
Create a Dockerfile in your project root. This uses a three-stage approach: install dependencies, build, and run.
The first stage installs all dependencies:
# Stage 1: Install dependencies
FROM node:20-alpine AS deps
WORKDIR /app
# Copy package files
COPY package.json package-lock.json ./
# Install all dependencies (including dev deps for building)
RUN npm ciThe second stage builds the TypeScript code:
# Stage 2: Build the application
FROM node:20-alpine AS build
WORKDIR /app
# Copy dependencies from the previous stage
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Compile TypeScript
RUN npm run build
# Prune dev dependencies after build
RUN npm prune --omit=devThe third stage creates the lean production image:
# Stage 3: Production image
FROM node:20-alpine AS production
WORKDIR /app
# Create a non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Copy only what is needed for production
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/package.json ./
# Switch to non-root user
USER appuser
EXPOSE 3000
# Start the NestJS application
CMD ["node", "dist/main.js"]The .dockerignore File
Keep the build context clean and fast:
node_modules
dist
.git
.gitignore
.vscode
*.md
test
.env
.env.*
coverageBuilding and Running
Build the image:
# Build the production image
docker build -t my-nest-app:latest .Run the container:
# Run the container on port 3000
docker run -d -p 3000:3000 --name nest-app my-nest-app:latestTest it:
# Check the health endpoint (default NestJS response)
curl http://localhost:3000You should see "Hello World!" or whatever your default route returns.
Docker Compose with a Database
Most NestJS applications connect to a database. Here is a Compose file that runs the app alongside PostgreSQL.
This Compose file sets up the NestJS app with PostgreSQL and proper networking:
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- DATABASE_HOST=postgres
- DATABASE_PORT=5432
- DATABASE_USER=myuser
- DATABASE_PASSWORD=mypassword
- DATABASE_NAME=mydb
- NODE_ENV=production
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypassword
POSTGRES_DB: mydb
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myuser -d mydb"]
interval: 10s
timeout: 5s
retries: 5
volumes:
pgdata:The depends_on with condition: service_healthy ensures the database is ready before the app starts.
Environment Variables
NestJS applications commonly use the @nestjs/config module for environment configuration. Pass environment variables through Docker rather than baking them into the image.
Use an env file with Docker Compose:
services:
app:
build: .
env_file:
- .env.production
ports:
- "3000:3000"Create .env.production:
DATABASE_HOST=postgres
DATABASE_PORT=5432
DATABASE_USER=myuser
DATABASE_PASSWORD=mypassword
DATABASE_NAME=mydb
JWT_SECRET=your-production-secret
NODE_ENV=productionNever commit .env files containing real credentials to version control.
Development Workflow
For local development, you want hot reload with nest start --watch. Set up a development Compose file.
This configuration mounts your source code and runs the dev server:
services:
app-dev:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
- "9229:9229"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
depends_on:
- postgres
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: devuser
POSTGRES_PASSWORD: devpassword
POSTGRES_DB: devdb
ports:
- "5432:5432"The development Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
EXPOSE 3000 9229
# Run the NestJS dev server with file watching and the debug port
CMD ["npm", "run", "start:debug"]Port 9229 allows you to attach a debugger from your IDE.
Health Checks
NestJS has a dedicated health check module. Install it and set up an endpoint.
Install the terminus health check package:
npm install @nestjs/terminusCreate a health controller:
// src/health/health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService } from '@nestjs/terminus';
@Controller('health')
export class HealthController {
constructor(private health: HealthCheckService) {}
@Get()
@HealthCheck()
check() {
// Returns a structured health status response
return this.health.check([]);
}
}Register it in a health module and import that module into your AppModule:
// src/health/health.module.ts
import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';
import { HealthController } from './health.controller';
@Module({
imports: [TerminusModule],
controllers: [HealthController],
})
export class HealthModule {}Add a health check to your Docker Compose service:
services:
app:
build: .
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3Graceful Shutdown
NestJS supports graceful shutdown, which is important for containerized applications that may be stopped or restarted at any time.
Enable shutdown hooks in your main.ts:
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Enable graceful shutdown hooks
app.enableShutdownHooks();
await app.listen(3000);
}
bootstrap();Docker sends a SIGTERM signal when stopping a container, followed by SIGKILL if the container does not exit before the stop timeout. With shutdown hooks enabled, NestJS invokes lifecycle hooks so providers can close resources such as database connections, and the HTTP adapter waits for in-flight responses to finish before closing.
Optimizing the Image
Check your image size:
docker images my-nest-appA well-built NestJS image typically weighs 150-200MB. Most of that is Node.js and production node_modules. To reduce it further:
- Use
node:20-alpine(not the full Debian-based image) - Run
npm prune --omit=devafter building to strip dev dependencies - Avoid installing unnecessary system packages
Security Hardening
Run as a non-root user (already included in the Dockerfile above). Additionally:
# Keep the runtime user unprivileged
USER appuser
# Set NODE_ENV to production so libraries can apply production behavior
ENV NODE_ENV=productionScan your image for vulnerabilities:
# Use Docker Scout to check for known vulnerabilities
docker scout cves my-nest-app:latestConclusion
NestJS and Docker complement each other well. The framework's TypeScript compilation step maps cleanly to a multi-stage Docker build, and the modular architecture makes it easy to add health checks and graceful shutdown handling. Start with the three-stage Dockerfile shown here, add Docker Compose for database integration, and layer in environment variable management and security hardening. The result is a production-ready NestJS container that your team can deploy with confidence.
Nawaz Dhandala
Author@nawazdhandala • Feb 08, 2026 •
Improve this Blog Post
All our blog posts are open source. Found a typo, want to add more detail, or have a better explanation? Anyone can contribute and make this post better for everyone.