Containerizing your App (Blood Pressure Tracker) with Docker & Kubernetes

Detail-oriented and dedicated Cloud/DevOps Engineer with experience in designing, deploying, and managing cloud infrastructure across Azure, AWS and GCP environments. Strong expertise in cybersecurity, system administration, and incident management. Proven history of success in IT support roles, with proficiency in Linux and Windows server administration, virtualisation, identity management, and Active Directory. Committed to enhancing security, optimising system performance, and ensuring the reliability of IT infrastructure.
As a developer who built a blood pressure tracking application, I aimed to simplify and enhance the deployment process for greater reliability. In this comprehensive guide, I'll walk you through containerizing your application with Docker and orchestrating it with Kubernetes.
What We'll Cover
Docker Basics: Containerizing your application
Multi-container Setup: Docker Compose with PostgreSQL
Kubernetes Fundamentals: Orchestration for production
Step-by-step Deployment: From local to cluster
Troubleshooting Common Issues
Prerequisites
Basic command line knowledge
My blood pressure tracker application code
Docker Desktop installed
Kubernetes cluster (Minikube, Docker Desktop, or cloud provider)
Part 1: Understanding Containerization
What is Docker?
Think of Docker as a standardized shipping container for software. Just like shipping containers revolutionized cargo transport, Docker containers revolutionize software deployment by packaging your application and all its dependencies into a single, portable unit.
Why Containerize? Consistency: "Works on my machine" becomes "Works everywhere"
Isolation: Applications don't interfere with each other
Portability: Move between development, testing, and production easily
Scalability: Run multiple instances effortlessly
Part 2: Containerizing with Docker
Step 1: Install Docker
Download Docker Desktop from docker.com and install it. Verify installation:
docker --version

Step 2: Create Your Dockerfile
Create Dockerfile in your project root with the correct configuration:
# Use Node.js 18 as base image
FROM node:18-alpine
# Set working directory
WORKDIR /app
# Copy package files first (better caching)
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy application files
COPY . .
# Set default environment variables
ENV DB_USER=postgres
ENV DB_HOST=localhost
ENV DB_NAME=blood_pressure_db
ENV DB_PASSWORD=postgres
ENV DB_PORT=5432
ENV JWT_SECRET=development_secret_key_change_in_production
ENV JWT_EXPIRE=7d
ENV PORT=3000
ENV NODE_ENV=production
# Expose application port
EXPOSE 3000
# Security: Run as non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
RUN chown -R nextjs:nodejs /app
USER nextjs
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000', (res) => { if (res.statusCode !== 200) process.exit(1) }) || process.exit(1)"
# Start application
CMD ["npm", "start"]
Step 3: Create .dockerignore
Create .dockerignore to exclude unnecessary files:
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.env.local
.env.production
.nyc_output
coverage
.nyc_output
.coverage
.vscode
*.log
Dockerfile
.dockerignore
.docker
Step 4: Build Your Docker Image
# Build the image with a tag
docker build -t blood-pressure-app .
# Verify the image was created
docker images | grep blood-pressure-app

Step 5: Run Your Container
# Run the container with port mapping
docker run -p 3000:3000 blood-pressure-app
# Visit http://localhost:3000 to see your app!
Part 3: Multi-Container Setup with Docker Compose
Your app needs both Node.js and PostgreSQL. Docker Compose manages multiple containers.
Create docker-compose.yml
Create docker-compose.yml with the correct configuration:
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DB_HOST=postgres
- DB_USER=bp_user
- DB_PASSWORD=bp_password
- DB_NAME=bp_database
- DB_PORT=5432
- JWT_SECRET=your_jwt_secret_here_change_this
- JWT_EXPIRE=7d
- PORT=3000
- NODE_ENV=production
depends_on:
- postgres
restart: unless-stopped
networks:
- bp-network
postgres:
image: postgres:15-alpine
environment:
- POSTGRES_USER=bp_user
- POSTGRES_PASSWORD=bp_password
- POSTGRES_DB=bp_database
volumes:
- postgres_/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
restart: unless-stopped
networks:
- bp-network
volumes:
postgres_
networks:
bp-network:
driver: bridge
Create Database Initialization Script
Create init.sql for database setup:
-- Create users table
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Create bp_readings table
CREATE TABLE IF NOT EXISTS bp_readings (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
systolic INTEGER NOT NULL,
diastolic INTEGER NOT NULL,
reading_date TIMESTAMP NOT NULL,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Create indexes for better performance
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_bp_readings_user_id ON bp_readings(user_id);
CREATE INDEX IF NOT EXISTS idx_bp_readings_date ON bp_readings(reading_date);
Run with Docker Compose
# Build and start all services
docker-compose up --build
# Run in background (detached mode)
docker-compose up -d --build
# View logs
docker-compose logs -f
# Stop all services
docker-compose down
Part 4: Introduction to Kubernetes
What is Kubernetes?
Kubernetes (often abbreviated as K8s) is an orchestration platform that automates the deployment, scaling, and management of containerized applications across clusters of hosts.
Think of it as a conductor for an orchestra - it manages multiple containers (musicians) to play in harmony.
Key Kubernetes Concepts
Pod: The smallest deployable unit (like a pod of whales)
Service: Network abstraction for accessing pods
Deployment: Manages pod replicas
ConfigMap: Stores configuration data
Secret: Stores sensitive data securely
Part 5: Kubernetes Setup for Beginners
Install Kubernetes Tools
For Docker Desktop users:
Open Docker Desktop
Go to Settings → Kubernetes
Check "Enable Kubernetes"
Click Apply & Restart
Start Your Local Cluster
# Start Minikube (if not using Docker Desktop)
minikube start
# Check cluster status
kubectl cluster-info

Part 6: Kubernetes Manifests
Create a k8s directory with these files:
Namespace (k8s/namespace.yaml)
apiVersion: v1
kind: Namespace
metadata:
name: blood-pressure-tracker
ConfigMap (k8s/configmap.yaml)
apiVersion: v1
kind: ConfigMap
metadata:
name: bp-app-config
namespace: blood-pressure-tracker
DB_HOST: "postgres-service"
DB_PORT: "5432"
DB_NAME: "bp_database"
JWT_EXPIRE: "7d"
PORT: "3000"
NODE_ENV: "production"
Secret (k8s/secrets.yaml)
apiVersion: v1
kind: Secret
metadata:
name: bp-app-secrets
namespace: blood-pressure-tracker
type: Opaque
# Base64 encoded values
# echo -n 'bp_user' | base64 = YnBfdXNlcg==
# echo -n 'bp_password' | base64 = YnBfcGFzc3dvcmQ=
# echo -n 'your_jwt_secret_here_change_this' | base64 = eW91cl9qd3Rfc2VjcmV0X2hlcmVfY2hhbmdlX3RoaXM=
DB_USER: YnBfdXNlcg==
DB_PASSWORD: YnBfcGFzc3dvcmQ=
JWT_SECRET: eW91cl9qd3Rfc2VjcmV0X2hlcmVfY2hhbmdlX3RoaXM=
PostgreSQL ConfigMap (k8s/postgres-configmap.yaml)
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-init-scripts
namespace: blood-pressure-tracker
init.sql: |
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS bp_readings (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
systolic INTEGER NOT NULL,
diastolic INTEGER NOT NULL,
reading_date TIMESTAMP NOT NULL,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_bp_readings_user_id ON bp_readings(user_id);
CREATE INDEX IF NOT EXISTS idx_bp_readings_date ON bp_readings(reading_date);
PostgreSQL Deployment (k8s/postgres-deployment.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres-deployment
namespace: blood-pressure-tracker
labels:
app: postgres
spec:
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: bp-app-secrets
key: DB_USER
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: bp-app-secrets
key: DB_PASSWORD
- name: POSTGRES_DB
value: "bp_database"
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
- name: init-scripts
mountPath: /docker-entrypoint-initdb.d
volumes:
- name: postgres-storage
emptyDir: {}
- name: init-scripts
configMap:
name: postgres-init-scripts
---
apiVersion: v1
kind: Service
metadata:
name: postgres-service
namespace: blood-pressure-tracker
spec:
selector:
app: postgres
ports:
- protocol: TCP
port: 5432
targetPort: 5432
type: ClusterIP
Application Deployment (k8s/app-deployment.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
name: bp-app-deployment
namespace: blood-pressure-tracker
labels:
app: bp-app
spec:
replicas: 2
selector:
matchLabels:
app: bp-app
template:
metadata:
labels:
app: bp-app
spec:
containers:
- name: bp-app
image: laoluafolami/blood-pressure-app:latest
imagePullPolicy: Always
ports:
- containerPort: 3000
envFrom:
- configMapRef:
name: bp-app-config
- secretRef:
name: bp-app-secrets
readinessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 15
periodSeconds: 20
---
apiVersion: v1
kind: Service
metadata:
name: bp-app-service
namespace: blood-pressure-tracker
spec:
selector:
app: bp-app
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer
Part 7: Deploying to Kubernetes
Step 1: Push Image to Docker Hub
# Login to Docker Hub
docker login
# Build your image
docker build -t blood-pressure-app .
# Tag with your Docker Hub username
docker tag blood-pressure-app:latest laoluafolami/blood-pressure-app:latest
# Push to Docker Hub
docker push laoluafolami/blood-pressure-app:latest
Step 2: Apply All Manifests
# Apply namespace first
kubectl apply -f k8s/namespace.yaml
# Apply ConfigMaps
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/postgres-configmap.yaml
# Apply Secrets
kubectl apply -f k8s/secrets.yaml
# Apply PostgreSQL
kubectl apply -f k8s/postgres-deployment.yaml
# Apply Application
kubectl apply -f k8s/app-deployment.yaml
Step 3: Monitor Your Deployment
# Check pods
kubectl get pods -n blood-pressure-tracker
# Check services
kubectl get services -n blood-pressure-tracker
# View logs
kubectl logs -n blood-pressure-tracker -l app=bp-app
# Access your app locally
kubectl port-forward -n blood-pressure-tracker service/bp-app-service 3000:80



Part 8: Checking Your Kubernetes Deployment
Complete Health Check Script
Create a simple script to check everything:
#!/bin/bash
echo "=== Checking Blood Pressure Tracker Deployment ==="
echo ""
echo "1. Checking namespace..."
kubectl get namespace blood-pressure-tracker
echo ""
echo "2. Checking pods..."
kubectl get pods -n blood-pressure-tracker
echo ""
echo "3. Checking services..."
kubectl get services -n blood-pressure-tracker
echo ""
echo "4. Checking deployments..."
kubectl get deployments -n blood-pressure-tracker
echo ""
echo "5. Checking app logs (last 10 lines)..."
kubectl logs -n blood-pressure-tracker -l app=bp-app --tail=10
echo ""
echo "6. Checking database logs (last 10 lines)..."
kubectl logs -n blood-pressure-tracker -l app=postgres --tail=10
echo ""
echo "=== Health Check Complete ==="
Access Your Application
# Port forward to access locally
kubectl port-forward -n blood-pressure-tracker service/bp-app-service 3000:80
# Then visit http://localhost:3000
Then visit http://localhost:3000





Part 9: Troubleshooting the issues I encountered
ImagePullBackOff Error
If you see this error:
Error from server (BadRequest): container "bp-app" in pod "bp-app-deployment-xxx" is waiting to start: trying and failing to pull image
Solution : Verify Docker Hub Image
# Make sure your image exists on Docker Hub
docker pull laoluafolami/blood-pressure-app:latest
Then update your deployment:
spec:
containers:
- name: bp-app
image: laoluafolami/blood-pressure-app:latest
imagePullSecrets:
- name: docker-config
Database Connection Issues
# Check if postgres service is running
kubectl get service postgres-service -n blood-pressure-tracker
# Test DNS resolution from app pod
kubectl exec -it -n blood-pressure-tracker deployment/bp-app-deployment -- nslookup postgres-service.blood-pressure-tracker.svc.cluster.local
# Check database tables
kubectl exec -it -n blood-pressure-tracker deployment/postgres-deployment -- psql -U bp_user -d bp_database -c "\dt"
Environment Variables Issues
# Check environment variables in pod
kubectl exec -it -n blood-pressure-tracker deployment/bp-app-deployment -- env | grep DB
# Check configmaps
kubectl get configmaps -n blood-pressure-tracker -o yaml
# Check secrets
kubectl get secrets -n blood-pressure-tracker -o yaml
Accessing Minikube Dashboard
minikube dashboard







Benefits of Containerization & Orchestration
Docker Benefits:
Consistent environments
Easy dependency management
Simplified deployment
Better resource utilization
Kubernetes Benefits:
Automatic scaling
Self-healing capabilities
Load balancing
Rolling updates
Multi-cloud deployment
Common Commands Cheat Sheet
Docker
# Build image
docker build -t myapp .
# Run container
docker run -p 3000:3000 myapp
# List containers
docker ps
# Stop container
docker stop <container-id>
Docker Compose
# Start services
docker-compose up
# Start in background
docker-compose up -d
# Stop services
docker-compose down
# View logs
docker-compose logs
Kubernetes
# Apply manifest
kubectl apply -f manifest.yaml
# Get pods
kubectl get pods
# Get services
kubectl get services
# View logs
kubectl logs <pod-name>
# Delete resources
kubectl delete -f manifest.yaml
Conclusion
Containerizing your application with Docker and orchestrating it with Kubernetes transforms how you deploy and manage software. What once required complex server configurations can now be accomplished with simple, portable containers.
The blood pressure tracker application, once a simple local project, can now be deployed anywhere with consistent behavior. Whether you're running it on your local machine, in a data center, or in the cloud, containers ensure your application works the same way everywhere.
Start with Docker for local development, then move to Kubernetes when you're ready for production-grade orchestration. The journey from a simple app to a containerized, orchestrated system is both empowering and essential for modern software development.
You can find the complete source code on my GitHub repository
Additional Resources
Docker Documentation: docs.docker.com
Kubernetes Documentation: kubernetes.io/docs
Docker Hub: hub.docker.com
Minikube: minikube.sigs.k8s.io
Feel free to like and share.



