| .. | ||
| api | ||
| db | ||
| frontend | ||
| configmap-secret.yaml | ||
| deployment.yaml | ||
| namespace.yaml | ||
| prepare-app.sh | ||
| README.md | ||
| remove-app.sh | ||
| service.yaml | ||
| start-app.sh | ||
| statefulset.yaml | ||
| stop-app.sh | ||
Task Manager — Kubernetes Deployment
A full-stack task management web application deployed on Kubernetes, extending the Docker Compose version (z1) with Kubernetes-native orchestration, scaling, and persistent storage.
Table of Contents
- Description
- Prerequisites
- Architecture
- Kubernetes Objects
- Containers Used
- Networking
- Persistent Volumes
- Container Configuration
- Quick Start
- Usage Instructions
- Viewing the Application
- Example Workflow
- File Structure
- Sources
- Use of Artificial Intelligence
Description
Task Manager is a full-stack web application for creating, managing, and tracking tasks. Users can:
- Create tasks with a title and optional description
- Mark tasks as completed or reopen them
- Delete tasks they no longer need
- Filter tasks by status (All / Active / Completed)
- View statistics including total, active, and completed task counts
- Manage the database via Adminer web interface
The application consists of a dark-themed Nginx frontend, a Node.js/Express REST API backend, PostgreSQL for persistent task storage, and Redis for API response caching — all orchestrated by Kubernetes.
Prerequisites
| Software | Minimum Version | Purpose |
|---|---|---|
| Linux | Any modern distribution | Host OS |
| Docker | 20.10+ | Build container images |
| kubectl | 1.25+ | Kubernetes CLI |
| Minikube or kind | Latest | Local Kubernetes cluster |
| bash | 4.0+ | Running management scripts |
Verify installation:
docker --version
kubectl version --client
minikube version # or: kind version
Start a local cluster (if not already running):
minikube start
# or
kind create cluster
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Namespace: taskmanager │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Kubernetes Cluster DNS │ │
│ │ │ │
│ │ NodePort :30080 NodePort :30081 │ │
│ │ │ │ │ │
│ │ ┌────▼──────────┐ ┌────────▼──────────┐ │ │
│ │ │ Service │ │ Service │ │ │
│ │ │ (frontend) │ │ (adminer) │ │ │
│ │ └────┬──────────┘ └────────┬──────────┘ │ │
│ │ │ │ │ │
│ │ ┌────▼──────────┐ ┌────────▼──────────┐ │ │
│ │ │ Deployment │ │ Deployment │ │ │
│ │ │ (Nginx ×1) │ │ (Adminer ×1) │ │ │
│ │ └────┬──────────┘ └────────┬──────────┘ │ │
│ │ │ /api/ proxy │ │ │
│ │ ┌────▼──────────┐ │ │ │
│ │ │ Service │ │ │ │
│ │ │ (api:3000) │ │ │ │
│ │ └────┬──────────┘ │ │ │
│ │ │ │ │ │
│ │ ┌────▼──────────┐ ┌────────▼──────────┐ │ │
│ │ │ Deployment │ │ StatefulSet │ │ │
│ │ │ (API ×2) │ │ (Postgres ×1) │ │ │
│ │ └───┬───────────┘ └────────┬──────────┘ │ │
│ │ │ │ PVC → PV │ │
│ │ ┌───▼──────────┐ │ /mnt/.../postgres │ │
│ │ │ StatefulSet │ │ │ │
│ │ │ (Redis ×1) │─────────────►│ │ │
│ │ └──────────────┘ │ │ │
│ │ PVC → PV │ │ │
│ │ /mnt/.../redis │ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Kubernetes Objects
| Object | Name | File | Description |
|---|---|---|---|
| Namespace | taskmanager |
namespace.yaml |
Isolates all objects from other cluster workloads |
| Secret | postgres-secret |
configmap-secret.yaml |
Stores base64-encoded DB username and password |
| ConfigMap | postgres-init |
configmap-secret.yaml |
Contains init.sql to create the tasks table on first run |
| PersistentVolume | postgres-pv |
statefulset.yaml |
1 Gi host-path volume for PostgreSQL data |
| PersistentVolumeClaim | postgres-pvc |
statefulset.yaml |
Claims postgres-pv for the postgres StatefulSet |
| PersistentVolume | redis-pv |
statefulset.yaml |
500 Mi host-path volume for Redis AOF data |
| PersistentVolumeClaim | redis-pvc |
statefulset.yaml |
Claims redis-pv for the redis StatefulSet |
| StatefulSet | postgres |
statefulset.yaml |
Manages the PostgreSQL 16 database pod with stable identity |
| StatefulSet | redis |
statefulset.yaml |
Manages the Redis 7 cache pod with stable identity |
| Deployment | taskmanager-api |
deployment.yaml |
Runs 2 replicas of the Node.js REST API |
| Deployment | taskmanager-frontend |
deployment.yaml |
Runs 1 replica of the Nginx frontend |
| Deployment | taskmanager-adminer |
deployment.yaml |
Runs 1 replica of Adminer (DB web UI) |
| Service | postgres |
service.yaml |
ClusterIP — internal access to PostgreSQL on port 5432 |
| Service | redis |
service.yaml |
ClusterIP — internal access to Redis on port 6379 |
| Service | taskmanager-api |
service.yaml |
ClusterIP — internal access to API on port 3000 |
| Service | taskmanager-frontend |
service.yaml |
NodePort — exposes UI at port 30080 |
| Service | taskmanager-adminer |
service.yaml |
NodePort — exposes Adminer at port 30081 |
Containers Used
1. taskmanager-frontend (Custom — Nginx)
- Image: Built from
frontend/Dockerfileusingnginx:alpine - Role: Serves the static HTML/CSS/JS UI and reverse-proxies
/api/requests totaskmanager-api:3000using Kubernetes DNS resolution - Port: 80 (internal), exposed at NodePort 30080
2. taskmanager-api (Custom — Node.js)
- Image: Built from
api/Dockerfileusingnode:20-alpine - Role: REST API providing full CRUD operations for tasks. Uses PostgreSQL for data and Redis for caching (30 s TTL). 2 replicas for availability
- Port: 3000 (ClusterIP)
3. postgres (Official — postgres:16-alpine)
- Role: Primary relational database. The
init.sqlConfigMap is mounted into/docker-entrypoint-initdb.d/so the schema and sample data are created automatically the first time - Port: 5432 (ClusterIP)
4. redis (Official — redis:7-alpine)
- Role: In-memory cache for task list API responses. Configured with append-only persistence (
--appendonly yes) so the cache survives pod restarts - Port: 6379 (ClusterIP)
5. adminer (Official — adminer:latest)
- Role: Web-based database management interface for browsing, querying, and managing the PostgreSQL database directly in a browser
- Port: 8080 (internal), exposed at NodePort 30081
Networking
Kubernetes uses a flat cluster network — every pod can reach every other pod by its Service DNS name inside the same namespace.
| Service Name | Type | Port | Accessible From |
|---|---|---|---|
postgres |
ClusterIP | 5432 | API pods only (internal) |
redis |
ClusterIP | 6379 | API pods only (internal) |
taskmanager-api |
ClusterIP | 3000 | Frontend pods (Nginx proxy) |
taskmanager-frontend |
NodePort | 80 → 30080 | External (browser) |
taskmanager-adminer |
NodePort | 8080 → 30081 | External (browser) |
DNS resolution example: Inside the cluster, Nginx resolves taskmanager-api to the API Service IP automatically through Kubernetes DNS (kube-dns). The database and cache are not reachable from outside the cluster.
Persistent Volumes
| Volume Name | Type | Capacity | Mount Path | Purpose |
|---|---|---|---|---|
postgres-pv |
hostPath | 1 Gi | /mnt/taskmanager/postgres |
PostgreSQL data directory |
redis-pv |
hostPath | 500 Mi | /mnt/taskmanager/redis |
Redis append-only file |
Data persistence: Stopping the application by scaling to zero (./stop-app.sh) or restarting pods does not delete the host directories or PVs. Only ./remove-app.sh deletes them.
Container Configuration
All containers are configured using environment variables, Secrets, and ConfigMaps:
- Secrets:
postgres-secretprovides the database username and password to both thepostgresStatefulSet andtaskmanager-apiDeployment, avoiding plain-text credentials in YAML files - ConfigMap:
postgres-initprovides theinit.sqlscript mounted as a file into the postgres container - Resource limits: Every container has
requestsandlimitsdefined to prevent resource starvation - Readiness/liveness probes: All containers have health checks so Kubernetes only routes traffic to healthy pods and automatically restarts crashed pods
- Restart policy: Kubernetes restarts failed pods by default (managed by the Deployment/StatefulSet controllers)
- StatefulSets are used for PostgreSQL and Redis because they need a stable network identity and persistent storage. The API and frontend use Deployments because they are stateless and benefit from rolling updates
Quick Start
# 1. Start a local Kubernetes cluster (if not already running)
minikube start
# 2. Prepare — build Docker images and create host directories
./prepare-app.sh
# 3. Deploy to Kubernetes
./start-app.sh
# 4. Open the app in your browser
# Task Manager UI: http://$(minikube ip):30080
# Adminer (DB UI): http://$(minikube ip):30081
For Minikube, get the node IP with:
minikube ip
Usage Instructions
Preparing the application
./prepare-app.sh
Builds the custom Docker images (taskmanager-api:latest, taskmanager-frontend:latest), creates the host-path directories used by the PersistentVolumes, and automatically loads the images into Minikube if it detects a running Minikube cluster (preventing ImagePullBackOff errors).
Starting the application
./start-app.sh
Applies all Kubernetes manifests in the correct dependency order and waits for StatefulSets and Deployments to be ready. Prints the access URLs at the end.
Stopping and removing the application
./stop-app.sh
Deletes all Kubernetes objects (Deployments, StatefulSets, Services, PVCs, Secrets, ConfigMap, Namespace) and the PersistentVolumes and host directories.
⚠️ All task data will be lost. Run ./prepare-app.sh then ./start-app.sh to start fresh.
Pausing without data loss
# Scale all workloads to 0 — objects and PVs remain, data is preserved
kubectl scale deployment --all --replicas=0 -n taskmanager
kubectl scale statefulset --all --replicas=0 -n taskmanager
# Resume
./start-app.sh
Viewing the Application
Task Manager (Main UI)
- URL:
http://$(minikube ip):30080 - Features: Create, complete, delete, and filter tasks
Adminer (Database Management)
- URL:
http://$(minikube ip):30081 - Login credentials:
- System:
PostgreSQL - Server:
postgres - Username:
taskuser - Password:
taskpass - Database:
taskmanager
- System:
Example Workflow
# Start a Minikube cluster
$ minikube start
# Prepare images and host directories
$ ./prepare-app.sh
=============================================
Preparing Task Manager for Kubernetes...
=============================================
[1/2] Building Docker images...
✓ taskmanager-api:latest built
✓ taskmanager-frontend:latest built
[2/2] Creating host directories for PersistentVolumes...
✓ /mnt/taskmanager/postgres created
✓ /mnt/taskmanager/redis created
=============================================
✓ Preparation complete!
Run ./start-app.sh to deploy to Kubernetes
=============================================
# Deploy the application
$ ./start-app.sh
=============================================
Starting Task Manager on Kubernetes...
=============================================
[1/6] Creating Namespace...
[2/6] Applying Secrets and ConfigMaps...
[3/6] Applying PersistentVolumes, PVCs and StatefulSets...
[4/6] Waiting for postgres StatefulSet to be Ready...
Waiting for redis StatefulSet to be Ready...
[5/6] Applying Deployments...
[6/6] Applying Services...
Waiting for API deployment to be ready...
=============================================
✓ Task Manager is running!
🌐 Task Manager: http://$(minikube ip):30080
🗄️ Adminer (DB): http://$(minikube ip):30081
=============================================
# Stop (data preserved)
$ ./stop-app.sh
✓ Application stopped. Data is preserved in PersistentVolumes.
# Start again — all tasks still there
$ ./start-app.sh
# Remove everything
$ ./remove-app.sh
✓ Application completely removed.
File Structure
z2/
├── namespace.yaml # Namespace: taskmanager
├── configmap-secret.yaml # Secret (DB credentials) + ConfigMap (init.sql)
├── statefulset.yaml # PV, PVC, StatefulSet for postgres and redis
├── deployment.yaml # Deployments for API, frontend, adminer
├── service.yaml # Services (ClusterIP + NodePort)
├── prepare-app.sh # Build images, create host directories
├── start-app.sh # Deploy all Kubernetes objects
├── stop-app.sh # Scale to zero (pause)
├── remove-app.sh # Delete everything
├── README.md # This documentation
├── api/
│ ├── Dockerfile # Node.js 20 Alpine image
│ ├── package.json # Node dependencies
│ ├── server.js # Express REST API
│ └── db.js # PostgreSQL connection pool
├── frontend/
│ ├── Dockerfile # Nginx Alpine image
│ ├── nginx.conf # Nginx config (proxies /api/ → taskmanager-api)
│ └── public/
│ ├── index.html # Task Manager SPA
│ ├── style.css # Dark theme CSS
│ └── app.js # Frontend JavaScript
└── db/
└── init.sql # Database schema + sample data
Sources
- Kubernetes Documentation — https://kubernetes.io/docs/
- kubectl Reference — https://kubernetes.io/docs/reference/kubectl/
- Kubernetes: StatefulSets — https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/
- Kubernetes: Persistent Volumes — https://kubernetes.io/docs/concepts/storage/persistent-volumes/
- Kubernetes: Secrets — https://kubernetes.io/docs/concepts/configuration/secret/
- Minikube Documentation — https://minikube.sigs.k8s.io/docs/
- PostgreSQL Docker Image — https://hub.docker.com/_/postgres
- Redis Docker Image — https://hub.docker.com/_/redis
- Adminer Docker Image — https://hub.docker.com/_/adminer
- Node.js Docker Image — https://hub.docker.com/_/node
- Nginx Docker Image — https://hub.docker.com/_/nginx
- Express.js Documentation — https://expressjs.com/
Use of Artificial Intelligence
Artificial intelligence tools such as ChatGPT and Claude were used as a support tool during development for understanding Kubernetes concepts, writing YAML manifests, and debugging configuration issues. All implementation, testing, and integration were performed independently.