Deploy NestJS to Production: A Practical Guide

Khimananda Oli 7 min read Programming and Languages
Deploy NestJS to Production: A Practical Guide

By Khimananda Oli | Last reviewed: August 2026

You have built a modular API with NestJS, but running it locally differs vastly from operating it under load. To successfully deploy NestJS to production, you must move beyond simple npm start commands and address containerization, process management, and observability. This guide provides the exact configuration patterns I use to run enterprise-grade Node.js services reliably.

How do you optimize Docker images when you deploy NestJS to production?

The most common mistake engineers make when they first deploy NestJS to production is shipping the entire development dependency tree into the runtime container. NestJS relies heavily on TypeScript compilation and decorators during the build phase, but none of these tools are needed at runtime. A standard node:22 image with dev dependencies can easily exceed 1GB; a properly optimized production image should be under 250MB.

I recommend a three-stage Dockerfile. The first stage installs dependencies and compiles code. The second stage prunes production-only modules. The final stage copies only the compiled artifacts and pruned modules into a clean Alpine base. This approach eliminates vulnerabilities hidden in dev packages and drastically reduces cold-start times in serverless or auto-scaling environments.

Source Codesrc/, package.jsontsconfig.jsonBuild Stagenpm cinpm run builddist/ generatedPrune Stagenpm ci --omit=devRemove .ts filesMinimal node_modulesRuntimenode:22-alpineCOPY dist/~180MB Final Size
Multi-stage Docker build strategy to deploy NestJS to production with minimal attack surface and fast startup.
# Stage 1: Build
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Prune
FROM node:22-alpine AS pruner
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force

# Stage 3: Runtime
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup -g 1001 -S nestjs && \
    adduser -S nestjs -u 1001
COPY --from=pruner /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
USER nestjs
EXPOSE 3000
CMD ["node", "dist/main.js"]

Notice the non-root user creation. Running Node.js as root inside a container is a critical security risk that fails most SOC 2 and ISO 27001 audits. If your team needs deeper guidance on securing Linux hosts before containerization, review our Ubuntu security hardening guide for foundational OS-level controls.

What is the best process manager to deploy NestJS to production?

NestJS runs on Node.js, which is single-threaded by default. In production, you must handle multiple CPU cores and automatic restarts on failure. The choice between PM2 and native container orchestration depends entirely on your infrastructure target.

CriteriaPM2 (Ecosystem)Kubernetes / ECSSystemd (Bare Metal)
Scaling ModelCluster mode (fork workers)Horizontal Pod AutoscalerManual or script-based
Restart PolicyBuilt-in watchdogLiveness probe + kubeletOnFailure directive
Log ManagementFile-based rotationstdout/stderr to collectorjournald integration
Best ForVPS, legacy serversCloud-native microservicesSingle-instance internal tools
OverheadLow (~30MB RAM)Medium (control plane cost)Minimal

If you are deploying to Kubernetes, do not use PM2. Kubernetes already handles process supervision, restarts, and scaling. Adding PM2 inside a pod creates a double-supervision problem where the container runtime and PM2 fight over process lifecycle. Instead, run node dist/main.js directly as PID 1. For VPS deployments without orchestration, PM2 cluster mode remains the gold standard for utilizing all available cores.

Configuring PM2 for Bare Metal

// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'nestjs-api',
    script: './dist/main.js',
    instances: 'max',        // Use all CPU cores
    exec_mode: 'cluster',
    autorestart: true,
    max_memory_restart: '512M',
    env_production: {
      NODE_ENV: 'production',
      PORT: 3000
    }
  }]
};

How do you configure health checks when you deploy NestJS to production?

Load balancers and orchestrators need to know if your application is actually serving traffic, not just running. NestJS provides the @nestjs/terminus module specifically for this purpose. Without proper health endpoints, rolling updates will cause downtime because the orchestrator cannot distinguish between a starting pod and a healthy one.

You need two distinct endpoints: /health/live for liveness (is the process alive?) and /health/ready for readiness (can it serve requests?). Liveness should be lightweight and never fail unless the process is truly deadlocked. Readiness should check database connections, cache availability, and external service dependencies.

Ingress / LBRoutes TrafficReadyNot ReadyPod A (Healthy)/health/ready → 200Pod B (Starting)/health/ready → 503DatabaseConnection CheckRedis CachePing Check
Readiness probe architecture ensuring zero-downtime deploys when you deploy NestJS to production.
// health.controller.ts
@Controller('health')
export class HealthController {
  constructor(private health: HealthCheckService, private db: TypeOrmHealthIndicator) {}

  @Get('live')
  live() { return this.health.check([() => ({ status: 'ok' })]); }

  @Get('ready')
  ready() {
    return this.health.check([
      () => this.db.pingCheck('database', { timeout: 3000 }),
    ]);
  }
}

In Kubernetes, map these to your deployment spec. Set initialDelaySeconds high enough for NestJS bootstrap (usually 10–15s) and periodSeconds to 10. Misconfigured probes are the #1 cause of CrashLoopBackOff in new NestJS deployments.

How do you manage secrets and configuration securely?

Never commit .env files or hardcode credentials. When you deploy NestJS to production, configuration should be injected at runtime through environment variables or a dedicated secrets manager. NestJS’s @nestjs/config module validates configuration at startup, preventing silent failures from missing variables.

For teams managing PostgreSQL backends alongside their NestJS API, proper credential rotation and backup strategies are equally critical. Refer to our PostgreSQL backup and restore guide to ensure your data layer matches your application-layer security posture.

  • Development: Use .env.local with dotenv, excluded from git.
  • Staging/Production (K8s): Use Kubernetes Secrets mounted as env vars or volumes.
  • Production (Advanced): HashiCorp Vault or AWS Secrets Manager with dynamic credentials.
// config.validation.ts
export class EnvironmentVariables {
  @IsString() DATABASE_URL: string;
  @IsInt()   PORT: number = 3000;
  @IsEnum(['development', 'production']) NODE_ENV: string;
}

// app.module.ts
ConfigModule.forRoot({
  validationSchema: classValidatorPlainToInstance(EnvironmentVariables),
  validationOptions: { abortEarly: false },
})

This validation schema ensures your application fails fast at startup rather than crashing mid-request due to a typo in an environment variable name. Fast failure is preferable to partial operation in production systems.

What observability stack should you use for NestJS in production?

Logging console.log statements is insufficient for production debugging. You need structured JSON logs correlated with request IDs, plus metrics exposed in Prometheus format. NestJS integrates cleanly with OpenTelemetry for distributed tracing and Pino or Winston for structured logging.

Effective observability requires understanding the distinction between signals. Our article on metrics, logs, and traces compared explains when to use each signal type. For NestJS specifically, instrument every HTTP endpoint with trace spans, emit metrics for business KPIs (signups, payments), and log only actionable events at appropriate severity levels.

NestJS AppOpenTelemetry SDKPino LoggerPrometheus ClientTracesJaeger / TempoDistributed TracingMetricsPrometheusTime-Series DBLogsLoki / ELKLog AggregationGrafanaUnified DashboardAlerting & SLOs
Complete observability stack for NestJS production deployments correlating traces, metrics, and logs.

Enable the NestJS OpenTelemetry instrumentation package to automatically capture HTTP spans, database queries, and message queue operations. Export traces via OTLP to your preferred backend. For metrics, expose a /metrics endpoint using prom-client and scrape it with Prometheus. This setup gives you end-to-end visibility without modifying business logic.

Deploy NestJS to Production: Your Next Steps

Successfully operating NestJS in production requires treating the framework as a compiled, containerized service rather than a development convenience. Optimize your Docker builds with multi-stage pipelines, choose the right process manager for your infrastructure, implement proper health checks, validate configuration at startup, and instrument observability from day one. These practices separate hobby projects from systems that survive traffic spikes and compliance audits.

If your team needs hands-on support architecting cloud-native Node.js deployments or preparing infrastructure for SOC 2 certification, reach out to discuss your specific requirements. I help engineering teams ship faster while maintaining security and reliability standards.

Frequently Asked Questions

Use Node.js 24 LTS. It offers the latest performance improvements and long-term support essential for stable NestJS deployments. Avoid odd-numbered releases or older LTS versions like Node 20, as they lack current security patches and native fetch optimizations required by modern NestJS modules.

Docker is preferred for consistency across environments and simpler scaling. Bare metal suits extreme low-latency needs but increases operational overhead. Most teams deploy containerized NestJS apps on Kubernetes or managed container services to balance isolation, reproducibility, and infrastructure management without vendor lock-in or configuration drift issues.

Never commit secrets to code. Use platform-native secret managers like AWS Secrets Manager, HashiCorp Vault, or Doppler. Inject variables at runtime via container orchestration tools. Validate all configs using Joi or class-validator during bootstrap to fail fast on missing values and prevent silent failures in production traffic handling.

PM2 remains the standard choice. It handles clustering, log rotation, and zero-downtime reloads natively. Configure ecosystem.config.js with exec_mode set to cluster and instances matching CPU cores. Enable source maps and monitoring plugins for real-time metrics without adding external agent overhead to your NestJS application stack.

Set worker count equal to available CPU cores. NestJS uses Node.js cluster module internally when enabled via PM2 or built-in adapters. Over-provisioning causes context switching overhead while under-provisioning wastes resources. Monitor event loop lag to fine-tune; four workers typically suit most quad-core production VMs running stateless API workloads efficiently.

Yes, always place Nginx or Caddy in front. NestJS lacks hardened HTTP parsing and TLS termination. Reverse proxies handle SSL, rate limiting, static files, and request buffering. This shields your app from slow-client attacks and offloads connection management, letting Node focus solely on business logic execution and JSON processing tasks.

Expose /health endpoint using @nestjs/terminus. Configure liveness probes to check basic responsiveness and readiness probes to verify database connections. Set initialDelaySeconds to allow TypeORM synchronization. Return proper HTTP status codes so orchestrators restart unhealthy pods automatically without manual intervention during rolling updates or node failures in production clusters.

Start with ten connections per worker for PostgreSQL. Calculate total pool as workers multiplied by per-worker limit. Monitor pg_stat_activity for idle versus active ratios. Adjust based on query patterns; read-heavy APIs need fewer connections than write-intensive batch processors. Always set statement timeouts to prevent connection exhaustion during traffic spikes or deadlocks.

Use lazy module loading and avoid heavy imports at root level. Bundle with webpack or esbuild to minimize package size. Pre-warm functions via scheduled invocations. Consider provisioned concurrency for critical paths. Serverless NestJS adds significant overhead compared to Express, so profile initialization time before committing to lambda-based architectures for latency-sensitive endpoints.

Stream logs to stdout only. Never write to local filesystem. Use structured JSON format with pino or winston for parseability. Externalize aggregation via Fluent Bit or Vector to platforms like Loki or Datadog. Implement log levels dynamically through admin endpoints to debug issues without redeploying or restarting containers during incidents.

Enable helmet middleware for headers, validate all inputs with class-transformer, and parameterize every database query. Implement CORS strictly, sanitize HTML outputs, and rotate JWT signing keys regularly. Run npm audit in CI pipelines. Disable verbose error responses in production to prevent stack trace leakage that aids attacker reconnaissance efforts.

Redis via @nestjs/cache-manager handles distributed caching effectively. Cache idempotent GET responses with TTLs matching data freshness requirements. Use cache-aside pattern for database results. Avoid in-memory caching in clustered setups due to inconsistency. Monitor hit ratios; below eighty percent indicates poor key design or insufficient TTL tuning for your workload patterns.

Use graceful shutdown hooks to finish in-flight requests before exiting. Configure SIGTERM handlers with timeout buffers. Deploy behind load balancers supporting connection draining. With Kubernetes, set preStop hooks and terminationGracePeriodSeconds appropriately. Combine with rolling update strategies ensuring new pods pass readiness checks before old ones terminate during release cycles.

OpenTelemetry provides vendor-neutral tracing and metrics. Use @nestjs/opentelemetry for automatic instrumentation of controllers, guards, and database calls. Export to Jaeger or Grafana Tempo. Complement with Prometheus client for custom business metrics. Avoid proprietary SDKs when possible to maintain portability across cloud providers and observability backends as infrastructure evolves.

Yes, SWC compiler reduces build times significantly versus tsc but produces identical runtime output. Enable swc in nest-cli.json for faster CI feedback loops. Runtime performance depends on V8 optimizations, not transpiler choice. Always test compiled artifacts thoroughly since edge cases in decorators or metadata reflection may behave differently under alternative compilation toolchains.