This project demonstrates a complete Kubernetes-based application running on Minikube. It includes a frontend, a Flask backend API, and a PostgreSQL database, all deployed with Kubernetes manifests.
The goal of this project is to practice real DevOps concepts in a small but complete setup:
- Containerizing applications with Docker
- Deploying multi-tier services on Kubernetes
- Managing configuration with
ConfigMapandSecret - Using persistent storage with
PersistentVolumeClaim - Exposing services with
Service,Ingress, andminikube tunnel - Debugging real deployment issues
project/
|-- backend/
| |-- app.py
| |-- Dockerfile
| `-- requirements.txt
|-- frontend/
| |-- app.js
| |-- Dockerfile
| `-- index.html
`-- k8s/
|-- backend.yaml
|-- configmap.yaml
|-- frontend.yaml
|-- ingress.yaml
|-- namespace.yaml
|-- postgres.yaml
`-- secret.yaml
We separate the application code from the Kubernetes configuration so the project stays clean, easier to explain, and easier to maintain.
Browser -> Ingress -> Frontend -> Backend -> PostgreSQL
Flow:
- The user opens the frontend in the browser.
- The frontend sends API requests to the backend.
- The backend processes the request and connects to PostgreSQL.
- PostgreSQL stores and returns the task data.
This structure gives several practical benefits:
frontend/is isolated from backend logic, so UI changes do not affect database code.backend/contains API and database logic in one place, making debugging easier.k8s/keeps infrastructure as code, so deployments are repeatable and version-controlled.Secretkeeps database credentials out of application code.ConfigMapkeeps runtime configuration flexible.PersistentVolumeClaimkeeps PostgreSQL data even if the pod restarts.Ingressgives one clean entry point instead of exposing multiple ports manually.
- Kubernetes
- Minikube
- Docker
- Flask
- Gunicorn
- PostgreSQL
- Nginx
- HTML / JavaScript
This is the backend API built with Flask.
It does the following:
- Connects to PostgreSQL
- Creates the
taskstable if it does not exist - Exposes health and readiness endpoints
- Exposes task APIs for listing, creating, and toggling tasks
Important note:
- A real issue happened earlier when the table creation logic was only inside the main block.
- Gunicorn does not run the app the same way as the Flask development server.
- Because of that, the table initialization code did not run and the backend returned database errors.
- The fix was to ensure
init_db()runs when the module is loaded.
Defines the Python dependencies:
flaskpsycopg2-binarygunicorn
Builds the backend container by:
- Starting from
python:3.11-slim - Installing Python dependencies
- Copying the application code
- Running the app with Gunicorn on port
5000
This is the user interface for the task app. It provides:
- A title
- A task input box
- A button to add tasks
- A list that displays saved tasks
This file uses fetch() to call the backend API.
It does the following:
- Loads all tasks from
/api/tasks - Creates new tasks with
POST /api/tasks - Toggles tasks with
PATCH /api/tasks/:id/toggle
This builds the frontend container using nginx:alpine and serves the static files on port 80.
The k8s/ folder contains YAML files that define the desired state of the system.
Creates the namespace task-app to isolate all project resources.
Benefit:
- Keeps the application separated from other cluster workloads
Stores PostgreSQL credentials securely:
POSTGRES_DBPOSTGRES_USERPOSTGRES_PASSWORD
Benefit:
- Sensitive values are not hardcoded into the app source
Stores backend configuration such as:
DB_HOSTDB_PORT
Benefit:
- Environment-specific values can be changed without rebuilding images
Creates the PostgreSQL database system and includes:
PersistentVolumeClaimDeploymentService
Benefit:
- The PVC keeps database data persistent
- The Service gives stable internal access using
postgres-service
Deploys the Flask backend and includes:
DeploymentServiceConfigMapreferencesSecretreferences- Readiness and liveness probes
Benefit:
- Kubernetes can check whether the backend is healthy and ready
Deploys the Nginx frontend and exposes it internally through frontend-service.
Defines external HTTP routing:
http://task.local/-> frontendhttp://task.local/api-> backend
Benefit:
- One domain can route traffic to multiple internal services
Before running this project, make sure you have:
- Docker installed
- Minikube installed
kubectlinstalled- PowerShell or a terminal that can run the commands below
minikube start --driver=docker --cpus=2 --memory=4096What it does:
- Starts a local Kubernetes cluster
- Uses Docker as the driver
- Allocates 2 CPUs and 4 GB memory
kubectl get nodesWhat it does:
- Verifies that the cluster is running and ready
minikube image build -t task-backend-lite:v1 ./backend
minikube image build -t task-frontend-lite:v1 ./frontendWhat it does:
- Builds the backend image directly inside Minikube
- Builds the frontend image directly inside Minikube
- Avoids pushing images to Docker Hub for local testing
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/secret.yaml
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/postgres.yaml
kubectl apply -f k8s/backend.yaml
kubectl apply -f k8s/frontend.yamlWhat it does:
- Creates all required Kubernetes resources step by step
- Deploys database first, then backend, then frontend
kubectl get pods -n task-app
kubectl get svc -n task-app
kubectl get pvc -n task-appWhat it does:
- Shows pod status
- Shows internal services
- Verifies persistent storage was created
Start the port-forward:
kubectl port-forward svc/backend-service 5000:5000 -n task-appIn another terminal, test the API:
curl http://localhost:5000/health
curl http://localhost:5000/ready
curl http://localhost:5000/api/tasksOptional create test:
curl.exe -X POST http://localhost:5000/api/tasks -H "Content-Type: application/json" -d "{\"title\":\"Learn Kubernetes\"}"What it does:
- Verifies the backend is healthy
- Verifies the backend can connect to PostgreSQL
- Verifies task APIs are working
Start the port-forward:
kubectl port-forward svc/frontend-service 8080:80 -n task-appThen open:
http://localhost:8080
What it does:
- Confirms the frontend is loading correctly before ingress is configured
minikube addons enable ingressWhat it does:
- Installs the NGINX Ingress controller in Minikube
kubectl get pods -n ingress-nginxWhat it does:
- Confirms that the ingress controller is running
kubectl apply -f k8s/ingress.yaml
kubectl get ingress -n task-appWhat it does:
- Creates the ingress routing rules
- Verifies the ingress exists
Add this entry to your hosts file:
127.0.0.1 task.local
On Windows, the hosts file is usually:
C:\Windows\System32\drivers\etc\hosts
What it does:
- Maps
task.localto your local machine
minikube tunnelWhat it does:
- Exposes Kubernetes services externally on your machine
- Allows ingress traffic to reach the cluster
Open:
http://task.local
Expected result:
- The frontend loads
- The frontend calls the backend through ingress
- The backend stores and retrieves tasks from PostgreSQL
Check all pods:
kubectl get pods -n task-appCheck backend logs:
kubectl logs -l app=backend -n task-appCheck PostgreSQL logs:
kubectl logs -l app=postgres -n task-appDescribe a pod:
kubectl describe pod <pod-name> -n task-appRestart a deployment:
kubectl rollout restart deployment/backend -n task-app
kubectl rollout restart deployment/frontend -n task-appOne real issue in this project was a backend 500 Internal Server Error.
Command used to investigate:
kubectl logs -l app=backend -n task-appThe issue found:
relation "tasks" does not exist
Reason:
- The database table creation logic did not run under Gunicorn in the earlier version
Fix:
- Ensure the initialization code runs when the application module is loaded