NestJS gives you structure out of the box — modules, dependency injection, decorators — but it doesn't tell you how to ship it. This guide covers a clean, production-ready deployment: a lean Docker image, the right build pipeline, graceful shutdown, and a managed database.
Build vs. runtime: don't ship your toolchain
NestJS compiles TypeScript to JavaScript. Your production image only needs the compiled dist/ folder and production dependencies — not @nestjs/cli, not TypeScript, not your dev dependencies. A multi-stage Docker build keeps the final image small and the attack surface minimal.
# --- build stage ---
FROM node:20-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# --- runtime stage ---
FROM node:20-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/main.js"]The --omit=dev flag in the runtime stage drops dev dependencies, often halving image size.
Production-ready bootstrap
Your main.ts should bind to 0.0.0.0 (not localhost, which won't accept external traffic inside a container), read the port from the environment, and enable graceful shutdown.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableShutdownHooks();
app.enableCors();
const port = process.env.PORT ?? 3000;
await app.listen(port, '0.0.0.0');
}
bootstrap();enableShutdownHooks() lets Nest's lifecycle events fire on SIGTERM, so you can close database connections and finish in-flight requests during a rolling deploy.
A managed database with TypeORM or Prisma
Whichever ORM you use, read the connection string from the environment. Here's TypeORM:
import { TypeOrmModule } from '@nestjs/typeorm';
TypeOrmModule.forRoot({
type: 'postgres',
url: process.env.DATABASE_URL,
autoLoadEntities: true,
synchronize: false, // NEVER true in production
});Never set synchronize: true in production. It auto-alters your schema based on entities and can silently drop columns. Use migrations:
npm run typeorm migration:runRun migrations as a release step before the new code goes live, using the expand/contract pattern (add columns first, remove them in a later release).
Health checks with Terminus
NestJS ships an official health-check module, @nestjs/terminus:
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private db: TypeOrmHealthIndicator,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([() => this.db.pingCheck('database')]);
}
}Wire this endpoint into your platform's readiness probe so traffic only routes to healthy instances.
Deploying on PandaStack
PandaStack auto-detects Node projects and runs your build automatically. The flow:
- 1Create a PostgreSQL database in the dashboard —
DATABASE_URLis injected into your service automatically. - 2Connect your Git repo as a container app. PandaStack detects your Dockerfile, or uses buildpacks if you don't have one (it knows
npm run buildthennode dist/main.js). - 3Set env vars like
JWT_SECRETin the dashboard. - 4Push to your branch — it builds and deploys with live logs.
Builds run in rootless BuildKit inside ephemeral Kubernetes Jobs, images go to Artifact Registry, and deploys happen via Helm. You get rollbacks, deploy history, automatic SSL, and server-side metrics without adding an SDK.
| Setting | Value |
|---|---|
| Install command | npm ci (auto) |
| Build command | npm run build (auto) |
| Start command | node dist/main.js |
| Port | 3000 (from PORT) |
| Database | Managed PostgreSQL (DATABASE_URL) |
Things that bite people
- Binding to
localhost— the container can't receive external traffic. Always0.0.0.0. - Forgetting
NODE_ENV=production— disables certain optimizations and can leave dev-only behavior on. synchronize: true— fine for prototyping, dangerous in production.- Not enabling shutdown hooks — abrupt termination drops requests and leaks DB connections.
References
- NestJS deployment guide: https://docs.nestjs.com/deployment
- NestJS Terminus health checks: https://docs.nestjs.com/recipes/terminus
- TypeORM migrations: https://typeorm.io/migrations
- Node.js Docker best practices: https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md
- Docker multi-stage builds: https://docs.docker.com/build/building/multi-stage/
---
PandaStack's free tier includes container apps, a managed database, and automatic SSL — connect your NestJS repo and it builds, deploys, and wires the database in for you. Try it at https://dashboard.pandastack.io