This commit is contained in:
Nihal Ahmed 2026-05-14 07:35:50 +00:00
commit 18a369f5ad
22 changed files with 577 additions and 0 deletions

10
Dockerfile Normal file
View File

@ -0,0 +1,10 @@
FROM python:3.10
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "5000"]

189
README.md Normal file
View File

@ -0,0 +1,189 @@
# Notes Application on Kubernetes
## 📌 Description
This project is a simple full-stack **Notes Application** deployed on Kubernetes.
It allows users to create and view notes through a web interface. The application demonstrates a complete **3-tier architecture**:
* Frontend (User Interface)
* Backend (API Server)
* Database (PostgreSQL)
The application is containerized using Docker and deployed using Kubernetes.
---
## 🧱 Application Architecture
The system consists of three main components:
### 1. Frontend
* Built using HTML and JavaScript
* Served via Nginx
* Provides UI for adding and viewing notes
* Communicates with backend using HTTP requests
### 2. Backend
* Built using FastAPI (Python)
* Provides REST API endpoints:
* `GET /notes`
* `POST /notes`
* Handles business logic and database interaction
### 3. Database
* PostgreSQL database
* Stores notes persistently
* Managed using Kubernetes StatefulSet
---
## 📦 Containers Used
| Container | Description |
| --------- | --------------------------------------------- |
| frontend | Nginx container serving static frontend files |
| backend | FastAPI application handling API requests |
| postgres | PostgreSQL database for storing notes |
---
## ☸️ Kubernetes Objects
| Object | Description |
| --------------------------- | -------------------------------------------- |
| Namespace | `notes-app` isolates application resources |
| Deployment | Manages frontend and backend pods |
| StatefulSet | Manages PostgreSQL with persistent storage |
| Service | Enables communication between components |
| PersistentVolume (PV) | Provides storage for database |
| PersistentVolumeClaim (PVC) | Requests storage for PostgreSQL |
---
## 🌐 Networking
* Communication is handled using Kubernetes **Services**
* Backend is accessible via:
```
http://backend-service
```
* PostgreSQL is accessible via:
```
postgres
```
* Frontend is exposed externally using **NodePort**
---
## 💾 Storage
* PostgreSQL uses persistent storage via:
* PersistentVolume (PV)
* PersistentVolumeClaim (PVC)
* Ensures data is retained even if pods restart
---
## ⚙️ Container Configuration
### Backend Configuration
```text
DATABASE_URL=postgresql://user:password@postgres:5432/notesdb
```
### Frontend Configuration
```text
const API_URL = "http://backend-service";
```
---
## 🚀 How to Run the Application
### 1. Prepare the Application
```bash
./scripts/prepare-app.sh
```
Builds Docker images and prepares environment.
---
### 2. Start the Application
```bash
./scripts/start-app.sh
```
Deploys all Kubernetes resources.
---
### 3. Access the Application
#### Option 1: NodePort
```
http://localhost:30007
```
#### Option 2: Port Forward (recommended for Docker Desktop)
```bash
kubectl port-forward service/frontend-service 8080:80 -n notes-app
```
Then open:
```
http://localhost:8080
```
---
### 4. Stop the Application
```bash
./scripts/stop-app.sh
```
Deletes all Kubernetes resources.
---
## 🧪 Functionality
* Users can add notes via UI
* Notes are stored in PostgreSQL
* Data persists across restarts
---
## 📝 Notes
* Docker images are hosted on Docker Hub
* Kubernetes cluster used: Docker Desktop Kubernetes
* Application demonstrates real-world container orchestration
---
## 📚 References
* OpenAI. (2026). ChatGPT (GPT-5.5). https://chat.openai.com
Used for guidance on Kubernetes deployment, debugging, and structuring the application.
---
## 👨‍💻 Author
Nihal Ahmed

BIN
configmap.yaml Normal file

Binary file not shown.

12
crud.py Normal file
View File

@ -0,0 +1,12 @@
from sqlalchemy.orm import Session
import models, schemas
def create_note(db: Session, note: schemas.NoteCreate):
db_note = models.Note(content=note.content)
db.add(db_note)
db.commit()
db.refresh(db_note)
return db_note
def get_notes(db: Session):
return db.query(models.Note).all()

10
database.py Normal file
View File

@ -0,0 +1,10 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base
import os
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://user:password@postgres:5432/notesdb")
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(bind=engine)
Base = declarative_base()

44
deployment.yaml Normal file
View File

@ -0,0 +1,44 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: notes-app
spec:
replicas: 1
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: nihal0314/backend:latest # ✅ FIXED
env:
- name: DATABASE_URL
vvalue: postgresql://user:password@postgres:5432/notesdb
ports:
- containerPort: 5000
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: notes-app
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: nihal0314/frontend:latest # ✅ FIXED
ports:
- containerPort: 80

145
index.html Normal file
View File

@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Notes App</title>
<style>
body {
font-family: Arial;
background: #f4f4f4;
display: flex;
justify-content: center;
margin-top: 60px;
}
.container {
background: white;
padding: 25px;
width: 420px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
h1 {
text-align: center;
}
.input-box {
display: flex;
gap: 10px;
}
input {
flex: 1;
padding: 10px;
}
button {
padding: 10px;
background: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
ul {
margin-top: 20px;
padding: 0;
list-style: none;
}
li {
background: #e9ecef;
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
}
.status {
margin-top: 10px;
color: green;
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<h1>Notes App</h1>
<div class="input-box">
<input type="text" id="noteInput" placeholder="Enter your note">
<button onclick="addNote()">Add</button>
</div>
<div id="status" class="status"></div>
<ul id="notesList"></ul>
</div>
<script>
const API_URL = "http://127.0.0.1:5000";
async function fetchNotes() {
try {
const res = await fetch(API_URL + "/notes");
const data = await res.json();
const list = document.getElementById("notesList");
list.innerHTML = "";
if (data.length === 0) {
list.innerHTML = "<li>No notes yet</li>";
return;
}
data.forEach(note => {
const li = document.createElement("li");
li.textContent = "ID: " + note.id + " | " + note.content;
list.appendChild(li);
});
} catch (err) {
console.error(err);
}
}
async function addNote() {
const input = document.getElementById("noteInput");
const status = document.getElementById("status");
if (!input.value.trim()) return;
try {
await fetch(API_URL + "/notes", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
content: input.value
})
});
status.textContent = "Saved to database ✅";
input.value = "";
fetchNotes();
} catch (err) {
console.error(err);
status.textContent = "Error saving ❌";
}
}
// Load notes on page load
fetchNotes();
</script>
</body>
</html>

4
init.sql Normal file
View File

@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS notes (
id SERIAL PRIMARY KEY,
content TEXT
);

36
main.py Normal file
View File

@ -0,0 +1,36 @@
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
import models
from database import engine, SessionLocal
import crud, schemas
from fastapi.middleware.cors import CORSMiddleware
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/")
def root():
return {"message": "API is running"}
@app.post("/notes")
def create(note: schemas.NoteCreate, db: Session = Depends(get_db)):
return crud.create_note(db, note)
@app.get("/notes")
def read(db: Session = Depends(get_db)):
return crud.get_notes(db)

8
models.py Normal file
View File

@ -0,0 +1,8 @@
from sqlalchemy import Column, Integer, String
from database import Base
class Note(Base):
__tablename__ = "notes"
id = Column(Integer, primary_key=True, index=True)
content = Column(String, index=True)

4
namespace.yaml Normal file
View File

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: notes-app

8
nginx.conf Normal file
View File

@ -0,0 +1,8 @@
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html;
}
}

4
package.json Normal file
View File

@ -0,0 +1,4 @@
{
"name": "notes-frontend",
"version": "1.0.0"
}

3
prepare-app.sh Normal file
View File

@ -0,0 +1,3 @@
#!/bin/bash
docker-compose build

6
requirements.txt Normal file
View File

@ -0,0 +1,6 @@
fastapi
uvicorn
psycopg2-binary
sqlalchemy
pydantic

11
schemas.py Normal file
View File

@ -0,0 +1,11 @@
from pydantic import BaseModel
class NoteCreate(BaseModel):
content: str
class NoteResponse(BaseModel):
id: int
content: str
class Config:
from_attributes = True

BIN
secret.yaml Normal file

Binary file not shown.

25
service.yaml Normal file
View File

@ -0,0 +1,25 @@
apiVersion: v1
kind: Service
metadata:
name: backend-service
namespace: notes-app
spec:
selector:
app: backend
ports:
- port: 80
targetPort: 5000
---
apiVersion: v1
kind: Service
metadata:
name: frontend-service
namespace: notes-app
spec:
type: NodePort
selector:
app: frontend
ports:
- port: 80
targetPort: 80
nodePort: 30007

3
start-app.sh Normal file
View File

@ -0,0 +1,3 @@
#!/bin/bash
docker-compose up -d

51
statefulset.yaml Normal file
View File

@ -0,0 +1,51 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: notes-app
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:14
env:
- name: POSTGRES_USER
value: user
- name: POSTGRES_PASSWORD
value: password
- name: POSTGRES_DB
value: notesdb
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: postgres-storage
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: notes-app
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432

3
stop-app.sh Normal file
View File

@ -0,0 +1,3 @@
#!/bin/bash
docker-compose down

1
vite.config.js Normal file
View File

@ -0,0 +1 @@
export default {}