Share feedback
Answers are generated based on the documentation.

Deploy your Node.js application

Prerequisites

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: todoapp

Configure the deployment

Before deploying, you need to customize the deployment file for your environment:

  1. Image reference: Replace your-username with your GitHub username or Docker Hub username:

    image: ghcr.io/your-username/docker-nodejs-sample:latest
  2. Domain name: Replace yourdomain.com with your actual domain in two places:

    # In ConfigMap
    ALLOWED_ORIGINS: "https://yourdomain.com"
    
    # In Ingress
    - host: yourdomain.com
  3. Database password (optional): The default password is already base64 encoded. To change it:

    $ echo -n "your-new-password" | base64
    

    Then update the Secret:

    data:
      postgres-password: <your-base64-encoded-password>
  4. 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 /health endpoint 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 created

Step 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           30s

Verify 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   45s

Check 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       1m

Step 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:

  1. Add some todos through the web interface

  2. Check application pods:

    $ kubectl get pods -n todoapp -l app=todoapp
    
  3. View application logs:

    $ kubectl logs -f deployment/todoapp-deployment -n todoapp
    
  4. Check database connectivity:

    $ kubectl get pods -n todoapp -l app=todoapp-postgres
    
  5. Monitor 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.


Explore official references and best practices to sharpen your Kubernetes deployment workflow: