1. Introduction đ
Writing a working Node.js application is only half the job â getting it running reliably in production is the other half. This tutorial covers the full deployment lifecycle: build processes, containerization, orchestration, CI/CD, monitoring, and zero-downtime deployment strategies.
Information
2. Preparing for Production đ§ŗ
Before deploying, an application should be audited against a production readiness checklist.
- All secrets loaded from environment variables or a secrets manager â none hardcoded.
- NODE_ENV=production set correctly.
- Dependencies audited and locked via package-lock.json.
- Logging, health checks, and graceful shutdown implemented.
Tip
3. Production Build đ¨
For TypeScript projects, compile source into plain JavaScript before deploying â never ship ts-node or run .ts files directly in production.
Terminal
npm run build
node dist/index.jspackage.json (scripts)
{
"scripts": {
"build": "tsc -p tsconfig.build.json",
"start": "node dist/index.js"
}
}4. Environment Configuration âī¸
Configuration should vary only through environment variables, never through code branches per environment.
src/config.ts
interface Config {
port: number;
env: "development" | "staging" | "production";
databaseUrl: string;
}
export const config: Config = {
port: Number(process.env.PORT ?? 3000),
env: (process.env.NODE_ENV as Config["env"]) ?? "development",
databaseUrl: process.env.DATABASE_URL as string,
};Best Practice
5. Process Managers đ§ââī¸
Process managers keep a Node.js application running, automatically restarting it on crashes and managing logs.
- PM2 â feature-rich, widely used for traditional VM/bare-metal deployments.
- systemd â OS-level service management, no extra dependency.
- Container orchestrators (Docker, Kubernetes) â handle restarts at the infrastructure level instead.
6. PM2 đ
PM2 is a popular process manager for Node.js, offering clustering, auto-restart, and built-in log management.
Terminal
npm install pm2 -g
pm2 start dist/index.js --name my-app -i max
pm2 save
pm2 startupecosystem.config.json
{
"apps": [
{
"name": "my-app",
"script": "dist/index.js",
"instances": "max",
"exec_mode": "cluster",
"env": { "NODE_ENV": "production" }
}
]
}Tip
7. Docker đŗ
Docker packages an application with its runtime and dependencies into a portable, reproducible container image.
Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/index.js"]Best Practice
8. Docker Compose đ§ą
Docker Compose defines multi-container applications (app, database, cache) declaratively for local development or simple deployments.
docker-compose.yml
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
depends_on:
- db
db:
image: postgres:16-alpine
environment:
- POSTGRES_PASSWORD=pass
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:Terminal
docker compose up -d9. Kubernetes â¸ī¸
Kubernetes orchestrates containers at scale, handling scheduling, scaling, self-healing, and rolling updates automatically.
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: myregistry/my-app:1.0.0
ports:
- containerPort: 3000
readinessProbe:
httpGet: { path: /health, port: 3000 }
resources:
limits: { memory: "256Mi", cpu: "500m" }Information
10. Nginx đ
Nginx is commonly placed in front of Node.js applications to handle TLS termination, static file serving, and request routing.
/etc/nginx/sites-available/my-app
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}11. Reverse Proxy đ
A reverse proxy sits between clients and your Node.js application, handling concerns that don't belong in application code.
Tip
12. HTTPS & SSL đ
Production traffic must always be encrypted. Let's Encrypt provides free, automatically renewable TLS certificates.
Terminal
sudo certbot --nginx -d example.com -d www.example.comImportant
13. Domains & DNS đ
DNS records map human-readable domain names to server IP addresses or other resource records.
| Record Type | Purpose |
|---|---|
| A | Maps a domain to an IPv4 address |
| AAAA | Maps a domain to an IPv6 address |
| CNAME | Aliases one domain to another |
| TXT | Verification records, SPF/DKIM for email |
Note
14. Load Balancing âī¸
Load balancers distribute incoming traffic across multiple application instances to improve throughput and resilience.
nginx load balancing
upstream node_app {
least_conn;
server 10.0.0.1:3000;
server 10.0.0.2:3000;
server 10.0.0.3:3000;
}
server {
location / {
proxy_pass http://node_app;
}
}Tip
15. Horizontal Scaling âī¸
Horizontal scaling adds more instances of your application rather than making a single instance bigger.
- Ensure the application is stateless â no in-memory session or cache that other instances can't access.
- Move shared state to Redis, a database, or a distributed cache.
- Add instances behind a load balancer as traffic grows.
Best Practice
16. Vertical Scaling âī¸
Vertical scaling increases the resources (CPU, memory) of a single instance rather than adding more of them.
Caution
17. CI/CD Pipelines đ
Continuous Integration and Continuous Deployment automate testing, building, and releasing code on every change.
18. GitHub Actions âī¸
GitHub Actions is a widely used CI/CD platform integrated directly into GitHub repositories.
.github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test
- run: npm run build
- name: Build & push Docker image
run: |
docker build -t myregistry/my-app:${{ github.sha }} .
docker push myregistry/my-app:${{ github.sha }}19. Monitoring đĄ
Monitoring provides visibility into an application's health and performance after deployment, catching problems before users report them.
- APM tools â New Relic, Datadog, Dynatrace for request-level tracing.
- Metrics â Prometheus + Grafana for custom dashboards and alerting.
- Uptime monitoring â Pingdom, UptimeRobot for external availability checks.
20. Logging đ
Centralized, structured logging is essential once you have more than one instance â you can't ssh into every server to check logs individually.
src/logger.ts
import pino from "pino";
const logger = pino({
level: process.env.LOG_LEVEL ?? "info",
formatters: { level: (label) => ({ level: label }) },
});
logger.info({ requestId: "abc123", path: "/api/users" }, "Request handled");Tip
21. Health Checks â¤ī¸âđŠš
Health check endpoints let load balancers and orchestrators know whether an instance is ready to serve traffic.
src/routes/health.ts
router.get("/health", async (req, res) => {
try {
await db.query("SELECT 1");
res.status(200).json({ status: "ok" });
} catch {
res.status(503).json({ status: "unavailable" });
}
});Best Practice
22. Graceful Shutdown đ
On shutdown, an application should stop accepting new requests but finish in-flight ones before exiting â abruptly killing the process can drop active connections.
src/index.ts
const server = app.listen(3000);
process.on("SIGTERM", () => {
console.log("SIGTERM received, shutting down gracefully...");
server.close(() => {
console.log("Closed remaining connections.");
process.exit(0);
});
setTimeout(() => process.exit(1), 10_000);
});Important
23. Zero-Downtime Deployment đ
Zero-downtime deployment ensures users never see errors or interruptions while a new version is being rolled out.
- Rolling updates â replace instances one at a time, keeping others serving traffic.
- Blue-green deployment â run two identical environments, switch traffic instantly once the new one is verified.
- Canary releases â gradually shift a small percentage of traffic to the new version before a full rollout.
Tip
24. Backup & Recovery đž
Regular, tested backups are a critical safety net â a backup that has never been restored isn't a real backup.
- Automate database backups on a fixed schedule (e.g. daily snapshots).
- Store backups in a separate region or provider from the primary database.
- Periodically test restoring from backup to verify the process actually works.
- Document a clear disaster recovery runbook with defined RTO/RPO targets.
25. Cloud Deployment âī¸
Major cloud providers offer managed compute options that abstract away much of the underlying infrastructure.
| Provider | Common Options |
|---|---|
| AWS | ECS, EKS, Elastic Beanstalk, EC2 |
| Google Cloud | Cloud Run, GKE, App Engine |
| Azure | App Service, AKS |
| Platform-as-a-Service | Render, Railway, Fly.io, Heroku |
Note
26. Serverless Deployment đŠī¸
Serverless platforms run your code on-demand without managing servers, scaling automatically and billing per invocation.
src/handler.ts
import type { APIGatewayProxyHandler } from "aws-lambda";
export const handler: APIGatewayProxyHandler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify({ message: "Hello from Lambda!" }),
};
};Caution
27. Performance Monitoring đ
Post-deployment, track key performance indicators continuously to catch regressions before they affect many users.
- Request latency (p50, p95, p99).
- Error rate and status code distribution.
- Event loop lag and memory usage over time.
- Database query latency and connection pool saturation.
Reference
28. Best Practices â
- Automate builds and deployments through CI/CD â avoid manual, ad-hoc deploys.
- Keep application instances stateless to support horizontal scaling.
- Implement health checks, graceful shutdown, and structured logging from the start.
- Use infrastructure-as-code (Terraform, Pulumi) to make environments reproducible.
- Test backup restoration regularly, not just backup creation.
29. Common Mistakes â ī¸
| Mistake | Consequence |
|---|---|
| Deploying manually via SSH/FTP | Inconsistent, unrepeatable deployments |
| No graceful shutdown handling | Dropped requests and corrupted data on restart |
| Storing session state in memory | Breaks horizontal scaling entirely |
| Ignoring health checks in orchestration | Traffic routed to unready or unhealthy instances |
| Never testing backup restoration | Backups may be unusable when actually needed |
30. Frequently Asked Questions â
Question
Answer
Question
Answer
Question
Answer
Question
Answer
31. Summary đ
Deploying Node.js applications reliably involves far more than running node index.js on a server. From containerization and CI/CD pipelines to load balancing, health checks, and zero-downtime deployment, each piece contributes to an application that stays available and recoverable under real-world conditions.
- Build once, deploy the same artifact across every environment.
- Containerize with Docker and orchestrate with Kubernetes or a managed platform as needed.
- Automate testing and deployment through CI/CD pipelines.
- Implement health checks, graceful shutdown, and centralized logging.
- Monitor continuously and keep tested backups ready for recovery.