Day 8 - Deploy a Small App on Kubernetes
Deploy your first application to Kubernetes using Deployments, Services, and ConfigMaps. Learn the fundamentals of K8s.
Description
You have a containerized application that runs perfectly with Docker, but you need to deploy it to Kubernetes for better scalability, self-healing, and orchestration. Time to learn the Kubernetes basics.
Task
Deploy a Node.js application to Kubernetes with proper configuration.
Requirements:
- Create Kubernetes Deployment
- Expose application via Service
- Configure environment variables with ConfigMap
- Add health checks (liveness and readiness probes)
- Verify application is running and accessible
Target
- ✅ Application deployed to Kubernetes
- ✅ Service routes traffic to pods
- ✅ Health checks working
- ✅ Application accessible via LoadBalancer or NodePort
- ✅ Multiple replicas running
Sample App
Application Code
app.js
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
const appName = process.env.APP_NAME || 'MyApp';
const version = process.env.VERSION || '1.0.0';
let healthy = true;
let ready = true;
// Simulate startup time
setTimeout(() => {
ready = true;
console.log('Application is ready!');
}, 5000);
app.get('/', (req, res) => {
res.json({
app: appName,
version: version,
hostname: require('os').hostname(),
uptime: process.uptime(),
timestamp: new Date().toISOString()
});
});
app.get('/health', (req, res) => {
if (healthy) {
res.status(200).json({ status: 'healthy' });
} else {
res.status(503).json({ status: 'unhealthy' });
}
});
app.get('/ready', (req, res) => {
if (ready) {
res.status(200).json({ status: 'ready' });
} else {
res.status(503).json({ status: 'not ready' });
}
});
// Endpoint to toggle health (for testing)
app.post('/health/toggle', (req, res) => {
healthy = !healthy;
res.json({ healthy });
});
app.listen(port, () => {
console.log(`${appName} v${version} listening on port ${port}`);
});
Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
USER node
CMD ["node", "app.js"]
package.json
{
"name": "k8s-demo-app",
"version": "1.0.0",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"dependencies": {
"express": "^4.18.0"
}
}
View Solution
Solution
Kubernetes Manifests
1. Namespace
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: demo-app
labels:
name: demo-app
environment: development
2. ConfigMap
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: demo-app
data:
APP_NAME: "Advent Demo App"
VERSION: "1.0.0"
PORT: "3000"
LOG_LEVEL: "info"
NODE_ENV: "production"
3. Deployment
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
namespace: demo-app
labels:
app: demo-app
version: v1
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
version: v1
spec:
containers:
- name: app
image: your-registry/demo-app:1.0.0
imagePullPolicy: Always
ports:
- containerPort: 3000
name: http
protocol: TCP
envFrom:
- configMapRef:
name: app-config
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
4. Service
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: demo-app
namespace: demo-app
labels:
app: demo-app
spec:
type: LoadBalancer # Use NodePort for local clusters
selector:
app: demo-app
ports:
- name: http
port: 80
targetPort: 3000
protocol: TCP
sessionAffinity: None
5. All-in-One (Optional)
# all-in-one.yaml
---
apiVersion: v1
kind: Namespace
metadata:
name: demo-app
---
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: demo-app
data:
APP_NAME: "Advent Demo App"
VERSION: "1.0.0"
PORT: "3000"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
namespace: demo-app
spec:
replicas: 3
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
- name: app
image: your-registry/demo-app:1.0.0
ports:
- containerPort: 3000
envFrom:
- configMapRef:
name: app-config
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: demo-app
namespace: demo-app
spec:
type: LoadBalancer
selector:
app: demo-app
ports:
- port: 80
targetPort: 3000
Explanation
Key Kubernetes Concepts
1. Deployment
Purpose: Manages pod lifecycle
spec:
replicas: 3 # Number of pod copies
strategy:
type: RollingUpdate # Update strategy
Benefits:
- Self-healing (replaces failed pods)
- Scaling (adjust replicas)
- Rolling updates (zero-downtime deployments)
2. Service
Purpose: Stable network endpoint
spec:
type: LoadBalancer # External access
selector:
app: demo-app # Routes to matching pods
Service Types:
ClusterIP: Internal only (default)NodePort: Accessible via node IPLoadBalancer: Cloud load balancer
3. ConfigMap
Purpose: Configuration management
envFrom:
- configMapRef:
name: app-config
Separates config from code.
4. Health Checks
Liveness Probe: Is the app alive?
livenessProbe:
httpGet:
path: /health
port: 3000
Restarts pod if failing.
Readiness Probe: Ready for traffic?
readinessProbe:
httpGet:
path: /ready
port: 3000
Removes from service if not ready.
5. Resource Management
resources:
requests: # Minimum needed
cpu: 100m
memory: 128Mi
limits: # Maximum allowed
cpu: 200m
memory: 256Mi
Ensures fair resource allocation.
Try to solve the challenge yourself first!
Click "Reveal Solution" when you're ready to see the answer.
Result
Build and Push Image
# Build the Docker image
docker build -t your-registry/demo-app:1.0.0 .
# Push to registry
docker push your-registry/demo-app:1.0.0
# Or use local registry (Minikube)
eval $(minikube docker-env)
docker build -t demo-app:1.0.0 .
Deploy to Kubernetes
# Apply all manifests
kubectl apply -f namespace.yaml
kubectl apply -f configmap.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
# Or apply all at once
kubectl apply -f all-in-one.yaml
# Watch rollout
kubectl rollout status deployment/demo-app -n demo-app
# deployment "demo-app" successfully rolled out
Verify Deployment
# Check pods
kubectl get pods -n demo-app
# NAME READY STATUS RESTARTS AGE
# demo-app-5d7f8c9b4d-abc12 1/1 Running 0 30s
# demo-app-5d7f8c9b4d-def34 1/1 Running 0 30s
# demo-app-5d7f8c9b4d-ghi56 1/1 Running 0 30s
# Check service
kubectl get svc -n demo-app
# NAME TYPE EXTERNAL-IP PORT(S) AGE
# demo-app LoadBalancer <pending> 80:30123/TCP 1m
# Get service details
kubectl describe svc demo-app -n demo-app
# For Minikube, get URL
minikube service demo-app -n demo-app --url
# http://192.168.49.2:30123
Test Application
# Get the service URL
SERVICE_URL=$(minikube service demo-app -n demo-app --url)
# Test the app
curl $SERVICE_URL
# {
# "app": "Advent Demo App",
# "version": "1.0.0",
# "hostname": "demo-app-5d7f8c9b4d-abc12",
# "uptime": 42.5,
# "timestamp": "2025-12-08T12:00:00.000Z"
# }
# Test health endpoint
curl $SERVICE_URL/health
# {"status":"healthy"}
# Test readiness
curl $SERVICE_URL/ready
# {"status":"ready"}
Validation
Health Check Validation
# Check pod health status
kubectl get pods -n demo-app -o wide
# Should show all pods Running and Ready 1/1
# View pod events
kubectl describe pod demo-app-xxx -n demo-app
# Should show successful Liveness and Readiness probes
# Test probe failure
POD=$(kubectl get pod -n demo-app -l app=demo-app -o jsonpath='{.items[0].metadata.name}')
kubectl exec -n demo-app $POD -- curl -X POST localhost:3000/health/toggle
# Watch pod restart
kubectl get pods -n demo-app -w
Load Balancing Test
# Make multiple requests
for i in {1..10}; do
curl -s $SERVICE_URL | jq -r .hostname
done
# Should show different pod hostnames (load balancing working)
Scaling Test
# Scale up
kubectl scale deployment demo-app -n demo-app --replicas=5
# Watch scaling
kubectl get pods -n demo-app -w
# Scale down
kubectl scale deployment demo-app -n demo-app --replicas=2
Advanced Features
Horizontal Pod Autoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: demo-app
namespace: demo-app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: demo-app
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Ingress (for HTTP routing)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo-app
namespace: demo-app
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: demo.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: demo-app
port:
number: 80
Best Practices
✅ Do's
- Use namespaces: Organize resources
- Set resource limits: Prevent resource hogging
- Add health checks: Enable self-healing
- Use ConfigMaps: Externalize configuration
- Label everything: Enable selection and filtering
- Use rolling updates: Zero-downtime deployments
❌ Don'ts
- Don't run as root: Security risk
- Don't use :latest tag: Unpredictable updates
- Don't skip health checks: Miss failures
- Don't hardcode config: Use ConfigMaps
- Don't forget resource limits: Risk cluster stability
Links
- Kubernetes Documentation
- Kubernetes Deployments
- Kubernetes Services
- Configure Liveness/Readiness Probes
- Minikube
Share Your Success
Deployed your first app to Kubernetes? Celebrate!
Tag @thedevopsdaily on X with:
- Screenshot of running pods
- Application URL/response
- Number of replicas
- What you learned
Use hashtags: #AdventOfDevOps #Kubernetes #CloudNative #Day8
Ready to complete this challenge?
Mark this challenge as complete once you've finished the task. We'll track your progress!
Completed this challenge? Share your success!
Tag @thedevopsdaily on X (Twitter) and share your learning journey with the community!
We earn commissions when you shop through the links below.
DigitalOcean
Cloud infrastructure for developers
Simple, reliable cloud computing designed for developers
DevDojo
Developer community & tools
Join a community of developers sharing knowledge and tools
Acronis
The most secure backup
Acronis: the most secure backup solution for your data
Pluralsight
Technology skills platform
Expert-led courses in software development, IT ops, data, and cybersecurity
Want to support DevOps Daily and reach thousands of developers?
Become a SponsorFound an issue?