Deploy your Node.js application
Prerequisites
- Complete all the previous sections of this guide, starting with Containerize a Node.js application.
- Turn on Kubernetes in Docker Desktop.
Overview
In this section, you'll learn how to deploy your containerized Node.js application to Kubernetes using Docker Desktop. This deployment uses production-ready configurations including security hardening, auto-scaling, persistent storage, and high availability features.
You'll deploy a complete stack including:
- Node.js Todo application with 3 replicas.
- PostgreSQL database with persistent storage.
- Auto-scaling based on CPU and memory usage.
- Ingress configuration for external access.
- Security settings.
Create a Kubernetes deployment file
Create a new file called nodejs-sample-kubernetes.yaml in your project root:
# ========================================
# Node.js Todo App - Kubernetes Deployment
# ========================================
apiVersion: v1
kind: Namespace
metadata:
name: todoapp
labels:
app: todoapp
---
# ========================================
# ConfigMap for Application Configuration
# ========================================
apiVersion: v1
kind: ConfigMap
metadata:
name: todoapp-config
namespace: todoapp
data:
NODE_ENV: 'production'
ALLOWED_ORIGINS: 'https://yourdomain.com'
POSTGRES_HOST: 'todoapp-postgres'
POSTGRES_PORT: '5432'
POSTGRES_DB: 'todoapp'
POSTGRES_USER: 'todoapp'
---
# ========================================
# Secret for Database Credentials
# ========================================
apiVersion: v1
kind: Secret
metadata:
name: todoapp-secrets
namespace: todoapp
type: Opaque
data:
postgres-password: dG9kb2FwcF9wYXNzd29yZA== # base64 encoded "todoapp_password"
---
# ========================================
# PostgreSQL PersistentVolumeClaim
# ========================================
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: todoapp
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: standard
---
# ========================================
# PostgreSQL Deployment
# ========================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: todoapp-postgres
namespace: todoapp
labels:
app: todoapp-postgres
spec:
replicas: 1
selector:
matchLabels:
app: todoapp-postgres
template:
metadata:
labels:
app: todoapp-postgres
spec:
containers:
- name: postgres
image: postgres:18-alpine
ports:
- containerPort: 5432
name: postgres
env:
- name: POSTGRES_DB
valueFrom:
configMapKeyRef:
name: todoapp-config
key: POSTGRES_DB
- name: POSTGRES_USER
valueFrom:
configMapKeyRef:
name: todoapp-config
key: POSTGRES_USER
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: todoapp-secrets
key: postgres-password
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql
livenessProbe:
exec:
command:
- pg_isready
- -U
- todoapp
- -d
- todoapp
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- pg_isready
- -U
- todoapp
- -d
- todoapp
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
---
# ========================================
# PostgreSQL Service
# ========================================
apiVersion: v1
kind: Service
metadata:
name: todoapp-postgres
namespace: todoapp
labels:
app: todoapp-postgres
spec:
type: ClusterIP
ports:
- port: 5432
targetPort: 5432
protocol: TCP
name: postgres
selector:
app: todoapp-postgres
---
# ========================================
# Application Deployment
# ========================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: todoapp-deployment
namespace: todoapp
labels:
app: todoapp
spec:
replicas: 3
selector:
matchLabels:
app: todoapp
template:
metadata:
labels:
app: todoapp
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001
containers:
- name: todoapp
image: ghcr.io/your-username/docker-nodejs-sample:latest
imagePullPolicy: Always
ports:
- containerPort: 3000
name: http
protocol: TCP
env:
- name: NODE_ENV
valueFrom:
configMapKeyRef:
name: todoapp-config
key: NODE_ENV
- name: ALLOWED_ORIGINS
valueFrom:
configMapKeyRef:
name: todoapp-config
key: ALLOWED_ORIGINS
- name: POSTGRES_HOST
valueFrom:
configMapKeyRef:
name: todoapp-config
key: POSTGRES_HOST
- name: POSTGRES_PORT
valueFrom:
configMapKeyRef:
name: todoapp-config
key: POSTGRES_PORT
- name: POSTGRES_DB
valueFrom:
configMapKeyRef:
name: todoapp-config
key: POSTGRES_DB
- name: POSTGRES_USER
valueFrom:
configMapKeyRef:
name: todoapp-config
key: POSTGRES_USER
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: todoapp-secrets
key: postgres-password
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: '256Mi'
cpu: '250m'
limits:
memory: '512Mi'
cpu: '500m'
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
---
# ========================================
# Application Service
# ========================================
apiVersion: v1
kind: Service
metadata:
name: todoapp-service
namespace: todoapp
labels:
app: todoapp
spec:
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 3000
protocol: TCP
selector:
app: todoapp
---
# ========================================
# Ingress for External Access
# ========================================
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: todoapp-ingress
namespace: todoapp
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: 'letsencrypt-prod'
spec:
tls:
- hosts:
- yourdomain.com
secretName: todoapp-tls
rules:
- host: yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: todoapp-service
port:
number: 80
---
# ========================================
# HorizontalPodAutoscaler
# ========================================
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: todoapp-hpa
namespace: todoapp
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: todoapp-deployment
minReplicas: 1
maxReplicas: 5
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
---
# ========================================
# PodDisruptionBudget
# ========================================
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: todoapp-pdb
namespace: todoapp
spec:
minAvailable: 1
selector:
matchLabels:
app: todoappConfigure the deployment
Before deploying, you need to customize the deployment file for your environment:
Image reference: Replace
your-usernamewith your GitHub username or Docker Hub username:image: ghcr.io/your-username/docker-nodejs-sample:latestDomain name: Replace
yourdomain.comwith your actual domain in two places:# In ConfigMap ALLOWED_ORIGINS: "https://yourdomain.com" # In Ingress - host: yourdomain.comDatabase password (optional): The default password is already base64 encoded. To change it:
$ echo -n "your-new-password" | base64Then update the Secret:
data: postgres-password: <your-base64-encoded-password>Storage class: Adjust based on your cluster (current:
standard)
Understanding the deployment
The deployment file creates a complete application stack with multiple components working together.
Architecture
The deployment includes:
- Node.js application: Runs 3 replicas of your containerized Todo app
- PostgreSQL database: Single instance with 10Gi of persistent storage
- Services: Kubernetes services handle load balancing across application replicas
- Ingress: External access through an ingress controller with SSL/TLS support
Security
The deployment uses several security features:
- Containers run as a non-root user (UID 1001)
- Read-only root filesystem prevents unauthorized writes
- Linux capabilities are dropped to minimize attack surface
- Sensitive data like database passwords are stored in Kubernetes secrets
High availability
To keep your application running reliably:
- Three application replicas ensure service continues if one pod fails
- Pod disruption budget maintains at least one available pod during updates
- Rolling updates allow zero-downtime deployments
- Health checks on the
/healthendpoint ensure only healthy pods receive traffic
Auto-scaling
The Horizontal Pod Autoscaler scales your application based on resource usage:
- Scales between 1 and 5 replicas automatically
- Triggers scaling when CPU usage exceeds 70%
- Triggers scaling when memory usage exceeds 80%
- Resource limits: 256Mi-512Mi memory, 250m-500m CPU per pod
Data persistence
PostgreSQL data is stored persistently:
- 10Gi persistent volume stores database files
- Database initializes automatically on first startup
- Data persists across pod restarts and updates
Deploy your application
Step 1: Deploy to Kubernetes
Deploy your application to the local Kubernetes cluster:
$ kubectl apply -f nodejs-sample-kubernetes.yaml
You should see output confirming all resources were created:
namespace/todoapp created
secret/todoapp-secrets created
configmap/todoapp-config created
persistentvolumeclaim/postgres-pvc created
deployment.apps/todoapp-postgres created
service/todoapp-postgres created
deployment.apps/todoapp-deployment created
service/todoapp-service created
ingress.networking.k8s.io/todoapp-ingress created
poddisruptionbudget.policy/todoapp-pdb created
horizontalpodautoscaler.autoscaling/todoapp-hpa createdStep 2: Verify the deployment
Check that your deployments are running:
$ kubectl get deployments -n todoapp
Expected output:
NAME READY UP-TO-DATE AVAILABLE AGE
todoapp-deployment 3/3 3 3 30s
todoapp-postgres 1/1 1 1 30sVerify your services are created:
$ kubectl get services -n todoapp
Expected output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
todoapp-service ClusterIP 10.111.101.229 <none> 80/TCP 45s
todoapp-postgres ClusterIP 10.111.102.130 <none> 5432/TCP 45sCheck that persistent storage is working:
$ kubectl get pvc -n todoapp
Expected output:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
postgres-pvc Bound pvc-12345678-1234-1234-1234-123456789012 10Gi RWO standard 1mStep 3: Access your application
For local testing, use port forwarding to access your application:
$ kubectl port-forward -n todoapp service/todoapp-service 8080:80
Open your browser and visit http://localhost:8080 to see your Todo application running in Kubernetes.
Step 4: Test the deployment
Test that your application is working correctly:
Add some todos through the web interface
Check application pods:
$ kubectl get pods -n todoapp -l app=todoappView application logs:
$ kubectl logs -f deployment/todoapp-deployment -n todoappCheck database connectivity:
$ kubectl get pods -n todoapp -l app=todoapp-postgresMonitor auto-scaling:
$ kubectl describe hpa todoapp-hpa -n todoapp
Step 5: Clean up
When you're done testing, remove the deployment:
$ kubectl delete -f nodejs-sample-kubernetes.yaml
Summary
You've deployed your containerized Node.js application to Kubernetes. You learned how to:
- Create a comprehensive Kubernetes deployment file with security hardening
- Deploy a multi-tier application (Node.js + PostgreSQL) with persistent storage
- Configure auto-scaling, health checks, and high availability features
- Test and monitor your deployment locally using Docker Desktop's Kubernetes
Your application is now running in a production-like environment with enterprise-grade features including security contexts, resource management, and automatic scaling.
Related resources
Explore official references and best practices to sharpen your Kubernetes deployment workflow:
- Kubernetes documentation – Learn about core concepts, workloads, services, and more.
- Deploy on Kubernetes with Docker Desktop – Use Docker Desktop's built-in Kubernetes support for local testing and development.
kubectlCLI reference – Manage Kubernetes clusters from the command line.- Kubernetes Deployment resource – Understand how to manage and scale applications using Deployments.
- Kubernetes Service resource – Learn how to expose your application to internal and external traffic.