Back to Blog

Zero Downtime Deployment NestJS Blue Green Rolling Strategy

Published: June 24, 2026
Zero Downtime Deployment NestJS Blue Green Rolling Strategy

That pit in your stomach when you deploy on a Friday afternoon — it is rarely the code you are worried about. The code is probably fine. The deployment is where you discover your health check lies about readiness, your migration renames a column that three old pods still reference, and your rollback has never actually been tested. For 90% of mid-to-large enterprises, a single hour of downtime costs more than $300,000 (ITIC, 2024). The Friday deploy anxiety is a six-figure problem wearing a technical costume. A proper zero downtime deployment nestjs blue green rolling strategy is the cure.

Zero downtime deployment nestjs blue green rolling strategies exist precisely to turn that deploy from a bet into a process. A rolling update swaps instances gradually while the service stays live. A blue-green deployment flips all traffic at once between two identical environments. Both work. Both fail in different ways. The choice between them depends on your database, your rollback speed requirements, and how much infrastructure cost your budget tolerates.

This post covers both strategies for NestJS specifically — graceful shutdown with shutdown hooks, health checks that actually catch problems, database migration patterns that keep old and new code compatible, and the exact decision criteria for picking one strategy over the other. If you have deployed NestJS and wondered why requests dropped during the switch, the answer is here.

Server room with network cables representing zero downtime deployment nestjs blue green rolling infrastructure

What Every Zero-Downtime Strategy Requires

Before choosing between rolling and blue-green, the prerequisites are the same. Getting these prerequisites right is the foundation of any zero downtime deployment nestjs blue green rolling approach. Skip any of these and neither strategy works.

Multiple instances. You cannot do zero-downtime with one instance. Whatever replaces it — even in a fraction of a second — drops the request being processed. Run at least two replicas in production. Kubernetes makes this the default; if you are on a single-container platform, you need a minimum of two.

A health check that tells the truth. The load balancer needs to know which instances can handle traffic. A liveness probe that always returns 200 is worse than no health check. The readiness probe should verify the database connection, the Redis cache, and whatever else the endpoint needs to serve a real request. NestJS provides @nestjs/terminus for this, and we will cover the setup below.

Graceful shutdown. When the orchestrator terminates an old instance, it sends SIGTERM. If the NestJS process ignores it or exits immediately, in-flight requests die. The application must stop accepting new connections, finish the ones in progress, close database connections, then exit.

Backward-compatible database migrations. During a rolling update, old and new code run simultaneously. If the new code renames a column, the old code crashes looking for the old name. Every migration must work with both versions.

A rollback plan. You will deploy something bad eventually. The question is how fast you can undo it. Rolling updates roll back by redeploying the previous image — this takes minutes. Blue-green rolls back by switching the load balancer back to the old environment — this takes seconds.

Zero Downtime Deployment NestJS Blue Green Rolling: Rolling Updates

Rolling updates are the default deployment strategy in Kubernetes. The orchestrator creates new pods with the new image, waits for the health check to pass, then terminates old pods. The process repeats until every pod runs the new version. Traffic never stops flowing because some pods are always running.

Kubernetes controls this with two parameters in the Deployment manifest:

YAML
1spec:
2  replicas: 4
3  strategy:
4    type: RollingUpdate
5    rollingUpdate:
6      maxSurge: 1
7      maxUnavailable: 0

maxSurge: 1 means one extra pod can be created above the desired count. maxUnavailable: 0 means all four original pods must stay running during the update. Kubernetes creates one new pod, waits for it to be ready, then terminates one old pod. The result: always at least four pods serving traffic, never a gap.

The Kubernetes rolling update documentation describes this in detail, including the default values and how the parameters interact. For production NestJS services, we set maxUnavailable: 0 and maxSurge: 1 or 25%, which gives safety with a modest resource overhead.

Rolling updates are cost-efficient. They require only a small buffer of extra resources (the surge). The trade-off is rollback speed — to revert, you redeploy the previous image and wait for the same gradual process. For most stateless NestJS APIs, this is acceptable. For stateful services or deployments where every second of bad behaviour matters, you may want blue-green instead.

Blue-Green Deployment: Instant Traffic Switching

Blue-green is the other half of the zero downtime deployment nestjs blue green rolling conversation. If rolling updates are the safe default, blue-green is the fast-revert option when you need it.

Blue-green deployment keeps two identical production environments. Call them blue and green. At any moment, one serves production traffic and the other sits idle — or runs a staging copy of the application against the same database schema (but not the same data).

When you deploy, you update the idle environment, run smoke tests against it, then switch the load balancer to point at it. The old environment becomes the new idle, ready for the next deploy or for an instant rollback by switching back.

The key difference from rolling updates: blue-green switches all traffic at once. There is no gradual drain. The switch happens in a single load balancer configuration change. This means:

  • Rollback is instant. The old environment still has the previous version running. Switch the load balancer back. Done.
  • Smoke testing is safer. You can test against the new environment with production-scale infrastructure before any user sees it.
  • Infrastructure cost doubles. You pay for two full environments. If you run 8 replicas in production, you need 16 total during the switch — 8 for the active environment, 8 for the idle one. You can scale down the idle environment between deploys, but that adds complexity.

For the database layer, both environments must connect to the same database (since switching environments should not switch databases). This means the migration constraint from rolling updates applies here too — old and new code must be schema-compatible.

Blue-green works best when you need sub-second rollback, have the infrastructure budget, or need to verify a deployment against production-scale data before exposing it to traffic.

Database Migrations for Zero-Downtime Deployments

Database migrations are the hardest part of any zero-downtime strategy. A schema change that works in isolation breaks when old and new code run side by side. The solution is the expand-contract pattern, and it is not optional.

Expand. Add the new column, table, or index without removing the old one. Deploy application code that writes to both old and new locations. Old code continues reading and writing old columns. New code reads from the new location and writes to both.

SQL
1-- Expand: add new column alongside old one
2ALTER TABLE users ADD COLUMN email_new VARCHAR(255);
3-- Old code uses `email`, new code uses `email_new`
4-- Both are populated during the transition

Backfill. Migrate existing data from old to new in batches. A single UPDATE on a large table locks it for the duration. Batch in chunks of 1000 rows with a short sleep between batches.

Deploy new code. Update the application to read from the new column exclusively. The old column still exists — it is populated but not read. Both old and new code coexist because the old code reads from the old column and the new code reads from the new column.

Contract. After verifying no old pods remain, remove the old column.

SQL
1-- Contract: remove old column once all pods use the new one
2ALTER TABLE users DROP COLUMN email;

Never combine expand and contract into one step. The ALTER TABLE users RENAME COLUMN email TO email_new approach breaks every running old pod immediately. The expand-contract pattern adds a few extra lines of SQL and saves you from a production outage.

Graceful Shutdown in NestJS

When Kubernetes terminates a pod during a rolling update, it sends SIGTERM and waits for the termination grace period (default 30 seconds). If the process does not exit in time, it receives SIGKILL. Graceful shutdown means intercepting SIGTERM, draining in-flight requests, cleaning up resources, and exiting cleanly — all before the SIGKILL arrives.

NestJS supports this natively with lifecycle hooks. Enable shutdown hooks in your main.ts:

TypeScript
1import { NestFactory } from '@nestjs/core';
2import { AppModule } from './app.module';
3
4async function bootstrap() {
5  const app = await NestFactory.create(AppModule);
6
7  // Enable shutdown hooks for graceful shutdown
8  app.enableShutdownHooks();
9
10  await app.listen(3000);
11}
12bootstrap();

With shutdown hooks enabled, NestJS listens for system signals. When SIGTERM arrives, it calls the onApplicationShutdown lifecycle hook on every module that implements it. Use this to close database connections, Redis clients, and message queue consumers:

TypeScript
1import { Injectable, OnApplicationShutdown } from '@nestjs/common';
2import { DataSource } from 'typeorm';
3
4@Injectable()
5export class DatabaseService implements OnApplicationShutdown {
6  constructor(private dataSource: DataSource) {}
7
8  async onApplicationShutdown(signal: string) {
9    console.log(`Received ${signal} — closing database connections`);
10    await this.dataSource.destroy();
11  }
12}

The HTTP server also needs explicit draining. NestJS's enableShutdownHooks stops the HTTP server when it receives the shutdown signal, but it does not handle in-flight request draining. For that, configure a preStop hook in your Kubernetes pod spec:

YAML
1spec:
2  containers:
3    - name: nestjs-app
4      image: my-registry/nestjs-app:latest
5      lifecycle:
6        preStop:
7          exec:
8            command: ["sh", "-c", "sleep 10"]

The sleep 10 gives the load balancer time to remove this pod from its endpoint list before the NestJS process receives SIGTERM. Without this delay, the load balancer continues routing traffic to a pod that is already shutting down. The Node.js signal events documentation covers the underlying signal handling, and the NestJS lifecycle events page documents the shutdown hook interface.

Set the pod's terminationGracePeriodSeconds to a value that exceeds your expected drain time plus the preStop sleep. If your requests take up to 10 seconds to complete and you have a 10-second preStop hook, set the grace period to 30 seconds:

YAML
1spec:
2  terminationGracePeriodSeconds: 30

Server rack with green and blue indicator lights representing load balancing and traffic switching

Health Checks That Actually Work

A health check endpoint that always returns 200 is not a health check. It is a lie that the load balancer believes. For zero-downtime deployments, two types of probe matter:

Liveness probe. Is the process alive and responding? If this fails, the orchestrator restarts the pod. Keep it simple — it should check that the NestJS application has booted successfully.

Readiness probe. Can this instance accept traffic? This must check the database connection, Redis, and any external dependency that would cause the endpoint to return errors. If the database is down, the readiness probe should fail, and the load balancer should stop sending traffic to this pod.

NestJS provides @nestjs/terminus for structured health checks:

TypeScript
1import { Module } from '@nestjs/common';
2import { TerminusModule } from '@nestjs/terminus';
3import { HealthController } from './health.controller';
4
5@Module({
6  imports: [TerminusModule],
7  controllers: [HealthController],
8})
9export class HealthModule {}
10
11// health.controller.ts
12import { Controller, Get } from '@nestjs/common';
13import { HealthCheckService, TypeOrmHealthIndicator } from '@nestjs/terminus';
14
15@Controller('health')
16export class HealthController {
17  constructor(
18    private health: HealthCheckService,
19    private db: TypeOrmHealthIndicator,
20  ) {}
21
22  @Get()
23  check() {
24    return this.health.check([
25      () => this.db.pingCheck('database', { timeout: 3000 }),
26    ]);
27  }
28}

Map this to Kubernetes probes:

YAML
1readinessProbe:
2  httpGet:
3    path: /health
4    port: 3000
5  initialDelaySeconds: 5
6  periodSeconds: 10
7  timeoutSeconds: 3
8livenessProbe:
9  httpGet:
10    path: /health
11    port: 3000
12  initialDelaySeconds: 15
13  periodSeconds: 20
14  timeoutSeconds: 5

The readiness probe runs every 10 seconds with a 3-second timeout. If the database ping takes longer than 3 seconds or fails, the pod is removed from the load balancer. This means a deployment that breaks the database connection is caught before it takes down the service.

Rollback: When Things Go Wrong

A deployment will fail eventually. The rollback strategy is not an afterthought — it is part of the deployment definition.

For rolling updates, rollback means reverting the Deployment's image tag to the previous version. Kubernetes handles this natively:

Bash
1kubectl rollout undo deployment/nestjs-app

This triggers the same rolling update process in reverse — gradually replacing new pods with old ones. It takes as long as the original deployment. If the issue is urgent (a security vulnerability in the new code, or a database corruption), waiting through a gradual rollback is painful.

For blue-green deployments, rollback means switching the load balancer back to the previous environment. If blue was live and green was the new deployment, switch back to blue. This takes seconds — whatever the load balancer configuration change latency is.

The rollback should be tested regularly. A rollback you have never tested is a rollback that will fail. Run a rollback drill every quarter: deploy a deliberately broken change, verify the monitoring catches it, and measure the time from detection to full recovery.

Which Strategy to Use and When

There is no universal winner. The choice depends on your constraints:

Use rolling updates when:

  • You run stateless NestJS APIs behind a Kubernetes Service
  • Infrastructure cost is a constraint (blue-green doubles your bill)
  • You can tolerate a few minutes of degraded performance during rollback
  • Your database migrations follow the expand-contract pattern
  • Your team is experienced with Kubernetes

Use blue-green when:

  • You need instant rollback capability (sub-second decision)
  • You have the infrastructure budget for two environments
  • You want to run smoke tests against production-scale infrastructure before the switch
  • Your compliance requirements mandate pre-production verification on identical hardware
  • The application is stateful or session-dependent

Use neither when:

  • You run a single instance. Add more instances first.
  • Your health checks lie. Fix the probes before choosing a strategy.
  • Your migration renames columns in a single step. Adopt expand-contract first.
  • Your shutdown is not graceful. Enable shutdown hooks and preStop hooks.

The Kubernetes Deployments API defaults to rolling updates for a reason. For most SaaS NestJS applications, rolling updates with proper readiness probes and graceful shutdown handle the job. Blue-green is worth the extra cost when rollback speed is a compliance requirement — you know if this applies to you.

Close-up of ethernet cables connected to a network switch representing high-availability server deployment

Summary

A solid zero downtime deployment nestjs blue green rolling strategy requires getting five things right before you choose a specific approach:

  • Multiple instances. You need at least two replicas. One instance cannot switch without dropping requests.
  • Graceful shutdown. app.enableShutdownHooks(), a preStop hook with sleep 10, and terminationGracePeriodSeconds set to at least 30 seconds. Implement OnApplicationShutdown on every service that holds a connection.
  • Honest health checks. Readiness probes that verify the database connection using @nestjs/terminus. A readiness probe that lies is worse than no probe.
  • Expand-contract migrations. Never rename or drop a column in the same deployment that adds the new one. Backward-compatible schema changes are the single biggest differentiator between teams that achieve zero-downtime and teams that pretend they do.
  • Tested rollback. Run a rollback drill quarterly. Know exactly how long it takes from detection to recovery.

Both rolling updates and blue-green deployments work. Rolling updates are cost-efficient and the Kubernetes default. Blue-green offers instant rollback at double the infrastructure cost. The right choice depends on your budget and your rollback speed requirements, not on which one sounds more impressive in a blog post.

We use rolling updates with Kubernetes on our NestJS projects, following the patterns in our NestJS project structure post and the Docker multi-stage builds post that produces the images we deploy. The CI/CD configuration post shows how the pipeline automates the rollout and includes a rollback workflow.

The Friday afternoon deploy anxiety? It does not disappear entirely. But when your readiness probe catches a bad connection before traffic hits it, when your shutdown hook drains the last request cleanly, and when your rollback finishes in seconds instead of a panicked image rebuild — that anxiety shrinks to almost nothing. Almost. It is still Friday afternoon.

Frequently Asked Questions

Zero downtime deployment for NestJS means updating your application without dropping any in-flight requests or causing service unavailability. It requires at least two running instances, a load balancer, and a graceful shutdown strategy that drains active connections before the old instance terminates. Use rolling updates for gradual replacement or blue-green for instant traffic switching.

Enable shutdown hooks in NestJS with app.enableShutdownHooks() in your main.ts, then listen for SIGTERM in a lifecycle hook. On signal receipt, stop accepting new connections, drain in-flight requests with a timeout, close database connections, then exit. Set a termination grace period in your orchestrator that exceeds your drain timeout by at least 10 seconds.

Rolling updates gradually replace old instances with new ones, one at a time — cost-efficient but slower rollback. Blue-green maintains two full environments (blue = live, green = staging) and switches traffic instantly — faster rollback (just switch back to blue) but requires double the infrastructure. Choose rolling for stateless APIs on Kubernetes, and blue-green for stateful services or when you need instant rollback.

Use the expand-contract pattern: add new columns without removing old ones, deploy code that writes to both, backfill data in batches, switch reads to the new schema, then remove old columns in a separate migration. Never use ALTER TABLE that renames or removes columns in a single step — it will break running old pods during a rolling update.

Two types: liveness probes (is the process alive?) and readiness probes (can this instance accept traffic?). NestJS with @nestjs/terminus provides a HealthCheckService that can verify database connections, Redis, and external API reachability. The readiness probe must fail before the pod is terminated — this removes the instance from the load balancer pool before the shutdown sequence begins.

Portrait of Umar Farooq

About Umar Farooq

Umar Farooq is the founder and lead engineer of Codify SaaS. He builds B2B SaaS products and web applications on modern TypeScript stacks and enterprise Java, and writes code-first guides drawn from real production work — the schema decisions, the migrations that almost went wrong, and the performance fixes that actually moved the numbers. When he recommends an approach, he shows the code and explains the trade-offs.

Read full bio