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 /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
- Health Checks:
curl http://api.example.com/health - Orders API:
curl http://api.example.com/orders - Users API:
curl http://api.example.com/users - Frontend: Open
http://api.example.comin browser - HPA:
kubectl get hpa -n microservices - 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.