DevOps

Docker K8s Deployment Project

Deploy a microservices application using Docker containers and Kubernetes with Helm charts, ingress, monitoring, and auto-scaling.

By TechCoder TeamLast updated: 2026-06-02
In a Nutshell

Deploy a microservices application using Docker containers and Kubernetes with Helm charts, ingress, monitoring, and auto-scaling. This hands-on tutorial focuses on practical implementation of docker k8s deployment project concepts.

Project 2: Docker + Kubernetes Deployment

Deploy a complete microservices stack on Kubernetes with production-ready configurations.

Project Overview

┌─────────────────────────────────────────────────────────────┐
│                     Kubernetes Cluster                      │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│   Ingress Controller (NGINX)                                 │
│        │                                                     │
│        ▼                                                     │
│   ┌─────────────────────────────────────────────────────┐   │
│   │                     API Gateway                     │   │
│   │                   (Nginx Ingress)                  │   │
│   └─────────────────────────────────────────────────────┘   │
│        │                                                     │
│   ┌────┴────────┐    ┌──────────┐    ┌──────────┐          │
│   │  Frontend   │    │   API    │    │   API    │          │
│   │   (React)   │◄───│ (Orders) │◄───│ (Users)  │          │
│   │   3 Pods    │    │  3 Pods  │    │  2 Pods  │          │
│   └─────────────┘    └────┬─────┘    └──────────┘          │
│                           │                                  │
│                      ┌────┴────┐                            │
│                      │PostgreSQL│                            │
│                      │  1 Pod   │                            │
│                      └─────────┘                            │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Architecture Components

  • Frontend: React app served by Nginx
  • Orders API: Node.js/Express REST API
  • Users API: Python/Flask REST API
  • Database: PostgreSQL with persistent storage
  • Ingress: NGINX Ingress Controller with TLS
  • Monitoring: Prometheus + Grafana

Part 1: Application Development

1.1 Orders API (Node.js)

// orders-api/server.js
const express = require('express');
const { Pool } = require('pg');
const app = express();

const pool = new Pool({
  host: process.env.DB_HOST || 'localhost',
  port: process.env.DB_PORT || 5432,
  database: process.env.DB_NAME || 'orders',
  user: process.env.DB_USER || 'postgres',
  password: process.env.DB_PASSWORD || 'password',
});

app.use(express.json());

// Health check
app.get('/health', async (req, res) => {
  try {
    await pool.query('SELECT 1');
    res.json({ status: 'healthy', service: 'orders-api' });
  } catch (err) {
    res.status(503).json({ status: 'unhealthy', error: err.message });
  }
});

// Get orders
app.get('/orders', async (req, res) => {
  try {
    const result = await pool.query('SELECT * FROM orders ORDER BY created_at DESC');
    res.json(result.rows);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// Create order
app.post('/orders', async (req, res) => {
  const { customer_id, items, total } = req.body;
  try {
    const result = await pool.query(
      'INSERT INTO orders (customer_id, items, total) VALUES ($1, $2, $3) RETURNING *',
      [customer_id, JSON.stringify(items), total]
    );
    res.status(201).json(result.rows[0]);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Orders API running on port ${PORT}`);
});
# orders-api/Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
USER node
CMD ["node", "server.js"]

1.2 Users API (Python)

# users-api/app.py
from flask import Flask, jsonify
import psycopg2
import os

app = Flask(__name__)

def get_db_connection():
    return psycopg2.connect(
        host=os.environ.get('DB_HOST', 'localhost'),
        port=os.environ.get('DB_PORT', 5432),
        database=os.environ.get('DB_NAME', 'users'),
        user=os.environ.get('DB_USER', 'postgres'),
        password=os.environ.get('DB_PASSWORD', 'password')
    )

@app.route('/health')
def health():
    try:
        conn = get_db_connection()
        conn.cursor().execute('SELECT 1')
        conn.close()
        return jsonify({'status': 'healthy', 'service': 'users-api'})
    except Exception as e:
        return jsonify({'status': 'unhealthy', 'error': str(e)}), 503

@app.route('/users')
def get_users():
    conn = get_db_connection()
    cur = conn.cursor()
    cur.execute('SELECT * FROM users ORDER BY created_at DESC')
    users = cur.fetchall()
    cur.close()
    conn.close()
    return jsonify(users)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
# users-api/Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
USER nobody
CMD ["python", "app.py"]

1.3 Frontend (React)

# frontend/Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# frontend/nginx.conf
server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location /api/orders {
        proxy_pass http://orders-api:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    location /api/users {
        proxy_pass http://users-api:5000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
    }
}

Part 2: Kubernetes Manifests

2.1 Namespace and ConfigMap

# k8s/00-namespace.yml
apiVersion: v1
kind: Namespace
metadata:
  name: microservices
  labels:
    istio-injection: enabled
---
# k8s/01-configmap.yml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: microservices
data:
  DB_HOST: "postgres"
  DB_PORT: "5432"
  DB_NAME: "appdb"
  ORDERS_API_URL: "http://orders-api:3000"
  USERS_API_URL: "http://users-api:5000"

2.2 Secrets

# k8s/02-secret.yml
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
  namespace: microservices
type: Opaque
stringData:
  DB_USER: "postgres"
  DB_PASSWORD: "SecurePassword123!"

2.3 PostgreSQL StatefulSet

# k8s/03-database.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
  namespace: microservices
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
  namespace: microservices
spec:
  serviceName: postgres
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:15-alpine
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_USER
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: DB_USER
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: DB_PASSWORD
        - name: POSTGRES_DB
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: DB_NAME
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
        resources:
          requests:
            memory: "256Mi"
            cpu: "100m"
          limits:
            memory: "512Mi"
            cpu: "500m"
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: microservices
spec:
  selector:
    app: postgres
  ports:
  - port: 5432
    targetPort: 5432
  type: ClusterIP

2.4 Orders API Deployment

# k8s/04-orders-api.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: orders-api
  namespace: microservices
  labels:
    app: orders-api
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: orders-api
  template:
    metadata:
      labels:
        app: orders-api
    spec:
      containers:
      - name: api
        image: your-registry/orders-api:v1.0.0
        ports:
        - containerPort: 3000
        envFrom:
        - configMapRef:
            name: app-config
        - secretRef:
            name: db-secret
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
  name: orders-api
  namespace: microservices
spec:
  selector:
    app: orders-api
  ports:
  - port: 3000
    targetPort: 3000
  type: ClusterIP
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: orders-api-hpa
  namespace: microservices
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: orders-api
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

2.5 Ingress Configuration

# k8s/10-ingress.yml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: microservices-ingress
  namespace: microservices
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/rate-limit: "100"
    cert-manager.io/cluster-issuer: "letsencrypt"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - api.example.com
    secretName: api-tls
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /orders
        pathType: Prefix
        backend:
          service:
            name: orders-api
            port:
              number: 3000
      - path: /users
        pathType: Prefix
        backend:
          service:
            name: users-api
            port:
              number: 5000
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend
            port:
              number: 80

Part 3: Helm Chart

microservices-chart/
├── Chart.yaml
├── values.yaml
├── values-production.yaml
├── templates/
│   ├── _helpers.tpl
│   ├── configmap.yml
│   ├── secret.yml
│   ├── database.yml
│   ├── deployment.yml
│   ├── service.yml
│   ├── ingress.yml
│   └── hpa.yml
└── charts/
# Chart.yaml
apiVersion: v2
name: microservices
description: Microservices application chart
type: application
version: 1.0.0
appVersion: "1.0.0"
dependencies:
  - name: postgresql
    version: 12.1.0
    repository: https://charts.bitnami.com/bitnami
    condition: postgresql.enabled
# values.yaml
replicaCount: 3

image:
  repository: your-registry/orders-api
  tag: latest
  pullPolicy: IfNotPresent

ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
  hosts:
    - host: api.example.com
      paths:
        - path: /orders
          pathType: Prefix

autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70

resources:
  limits:
    cpu: 200m
    memory: 256Mi
  requests:
    cpu: 100m
    memory: 128Mi
# Install Helm chart
helm dependency build ./microservices-chart
helm install microservices ./microservices-chart -f values-production.yaml

# Upgrade
helm upgrade microservices ./microservices-chart

# Rollback
helm rollback microservices 1

Part 4: Deployment Steps

# 1. Build and push images
docker build -t your-registry/orders-api:v1.0.0 ./orders-api
docker build -t your-registry/users-api:v1.0.0 ./users-api
docker build -t your-registry/frontend:v1.0.0 ./frontend

docker push your-registry/orders-api:v1.0.0
docker push your-registry/users-api:v1.0.0
docker push your-registry/frontend:v1.0.0

# 2. Create namespace and secrets
kubectl create namespace microservices
kubectl apply -f k8s/02-secret.yml

# 3. Deploy database
kubectl apply -f k8s/03-database.yml

# 4. Wait for database
kubectl wait --for=condition=ready pod -l app=postgres -n microservices

# 5. Deploy applications
kubectl apply -f k8s/04-orders-api.yml
kubectl apply -f k8s/05-users-api.yml
kubectl apply -f k8s/06-frontend.yml

# 6. Deploy ingress
kubectl apply -f k8s/10-ingress.yml

# 7. Verify deployment
kubectl get pods -n microservices
kubectl get svc -n microservices
kubectl get ingress -n microservices

# 8. Check logs
kubectl logs -l app=orders-api -n microservices --tail=100

# 9. Port forward for testing
kubectl port-forward svc/orders-api 3000:3000 -n microservices

Verification

  1. Health Checks: curl http://api.example.com/health
  2. Orders API: curl http://api.example.com/orders
  3. Users API: curl http://api.example.com/users
  4. Frontend: Open http://api.example.com in browser
  5. HPA: kubectl get hpa -n microservices
  6. Logs: kubectl logs deployment/orders-api -n microservices

Deliverables

  • [ ] 3 microservices with Dockerfiles
  • [ ] Kubernetes manifests for all components
  • [ ] Helm chart for deployment
  • [ ] Working ingress configuration
  • [ ] HPA for auto-scaling
  • [ ] Health checks and monitoring endpoints

Next Steps

Proceed to the AWS Infrastructure project using Terraform.