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!
These amazing companies help us create free, high-quality DevOps content for the community
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
Want to support DevOps Daily and reach thousands of developers?
Become a SponsorFound an issue?