How to Update a Service Without Waking Up Users?
Imagine: Friday evening, you decide to quickly push a bugfix to production. You hit deploy — and your messenger, online cinema, or CRM goes down for 30 seconds. Thousands of users at that moment see a 502 Bad Gateway. And if it's a payment gateway? Count the losses.
Zero-downtime deployment is not a luxury, but a standard for modern TypeScript services. In this article, we'll break down real techniques used in production: from graceful shutdown to blue-green deployment and rolling updates. You'll get ready-to-use TypeScript patterns.
Why is a Regular TypeScript Application Deployment Dangerous?
When you run npm run build && node dist/index.js, several problems occur:
- Connection breaks — the old process is killed while new requests are still coming in.
- In-memory data loss — unsaved sessions, cache, queues.
- Cold start — the new process hasn't warmed up yet but is already receiving traffic.
In TypeScript (Node.js), this is exacerbated by asynchronicity: you don't know which promises are still executing. But there is a solution.
Technique #1: Graceful Shutdown
The foundation of zero-downtime is proper handling of OS signals. When an orchestrator (Kubernetes, PM2) decides to stop your service, it sends SIGTERM. Your code should stop accepting new requests, wait for current ones to finish, and only then exit.
Implementation in TypeScript with Express
import express, { Request, Response } from 'express';import http from 'http';
const app = express();let server: http.Server;
app.get('/health', (req: Request, res: Response) => { res.status(200).json({ status: 'ok' });});
// Signal from Docker/K8sprocess.on('SIGTERM', async () => { console.log('SIGTERM received, starting graceful shutdown...'); // 1. Stop listening for new connections server.close(() => { console.log('HTTP server closed'); // 2. Close connections to DB, Redis, etc. // await db.disconnect(); process.exit(0); });
// 3. Give current requests time (30 sec timeout) setTimeout(() => { console.error('Forced shutdown due to timeout'); process.exit(1); }, 30000);});
const PORT = process.env.PORT || 3000;server = app.listen(PORT, () => { console.log(`Service running on port ${PORT}`);});Important: in TypeScript, use http.Server from the http module, not the Express type. The server.close() method correctly handles keep-alive connections.
Technique #2: Rolling Update
If you have multiple service replicas (3-5 instances running), a rolling update updates them one by one. While one is restarting, the others handle traffic.
Example for Kubernetes Deployment
# deployment.yamlapiVersion: apps/v1kind: Deploymentmetadata: name: my-typescript-servicespec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 # how many pods can be unavailable maxSurge: 1 # how many new pods can be created above the plan template: spec: containers: - name: app image: myregistry/service:v2 ports: - containerPort: 3000 livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 5 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 3000 initialDelaySeconds: 3 periodSeconds: 5Add a /ready endpoint to your TypeScript application that returns 200 only when the service