Compare commits

...

No commits in common. "main" and "master" have entirely different histories.
main ... master

32 changed files with 167 additions and 688 deletions

58
README.md Normal file
View File

@ -0,0 +1,58 @@
# Docker Web Application
## Prerequisites
- Docker installed on the system.
## Application Description
This is a simple Flask web application running inside a Docker container. The app accepts user input via a form and displays the submitted data.
## Docker Setup
1. **Preparation**
Run the following command to set up the environment:
```bash
./prepare-app.sh
```
2. **Start the Application**
Start the application with:
```bash
./start-app.sh
```
The app will be available at: [http://localhost:5000](http://localhost:5000)
3. **Stop the Application**
To stop the application:
```bash
./stop-app.sh
```
4. **Remove the Application**
To remove all Docker containers, networks, and volumes:
```bash
./remove-app.sh
```
## Working Example
1. Prepare the application:
```bash
./prepare-app.sh
```
2. Start the app:
```bash
./start-app.sh
```
3. Visit [http://localhost:5000](http://localhost:5000) in a browser.
4. Stop the app:
```bash
./stop-app.sh
```
5. Remove the app:
```bash
./remove-app.sh
```
## Conclusion
This application demonstrates Docker containerization of a Flask app with basic form handling.

18
docker-compose.yml Normal file
View File

@ -0,0 +1,18 @@
version: '3.8'
services:
flask-app:
build: .
ports:
- "5000:5000"
networks:
- flask-network
restart: unless-stopped
# You can add other services here, like a database if required
networks:
flask-network:
driver: bridge
volumes:
flask-data:
driver: local

21
dockerfile Normal file
View File

@ -0,0 +1,21 @@
# Use an official Python runtime as a parent image
FROM python:3.8-slim
# Set the working directory inside the container
WORKDIR /app
# Copy the current directory contents into the container at /app
COPY . /app
# Install Flask
RUN pip install --no-cache-dir flask
# Set the environment variable for Flask
ENV FLASK_APP=testapp.py
ENV FLASK_RUN_HOST=0.0.0.0
# Expose port 5000 for the Flask app
EXPOSE 5000
# Run the Flask application
CMD ["flask", "run", "--host=0.0.0.0", "--port=5000"]

11
prepare-app.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/bash
# Create a Docker network
echo "Creating Docker network..."
docker network create flask-network
# Create a named volume for persistent storage (for example, for database)
echo "Creating Docker volume..."
docker volume create flask-data
echo "Preparation complete!"

15
remove-app.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
# Remove the Flask app container
echo "Removing Flask app container..."
docker rm flask-app
# Remove Docker network
echo "Removing Docker network..."
docker network rm flask-network
# Remove Docker volume
echo "Removing Docker volume..."
docker volume rm flask-data
echo "App removed successfully!"

9
start-app.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
# Start the Flask app container
echo "Starting Flask app container..."
docker run -d --restart unless-stopped --name flask-app --network flask-network -p 5000:5000 flask-app
# Optionally, start any additional services (like a database) here if needed
echo "App is running at http://localhost:5000"

9
stop-app.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
# Stop the Flask app container
echo "Stopping Flask app container..."
docker stop flask-app
# Optionally, stop other services like the database here
echo "App stopped successfully!"

26
testapp.py Normal file
View File

@ -0,0 +1,26 @@
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def home():
if request.method == 'POST':
college_name = request.form['college_name']
student_name = request.form['student_name']
course_name = request.form['course_name']
year_of_study = request.form['year_of_study']
return f"College: {college_name}, Student: {student_name}, Course: {course_name}, Year: {year_of_study}"
return '''
<form method="POST">
College Name: <input type="text" name="college_name"><br>
Student Name: <input type="text" name="student_name"><br>
Course Name: <input type="text" name="course_name"><br>
Year of Study: <input type="text" name="year_of_study"><br>
<input type="submit" value="Submit">
</form>
'''
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=5000) # This allows the app to run inside the container

View File

@ -1,9 +0,0 @@
FROM python:3.9-slim
WORKDIR /app
COPY app/ .
RUN pip install --no-cache-dir -r requirements.txt
CMD ["python", "main.py"]

View File

@ -1,32 +0,0 @@
# Flask + PostgreSQL Kubernetes Deployment
This project deploys a Flask web application with a PostgreSQL backend using Kubernetes.
## 🛠 Structure
- `app/`: Flask source code (`main.py`, `requirements.txt`)
- `k8s/`: Kubernetes manifests for deployments, services, namespace, etc.
- `Dockerfile`: Builds the Flask app image
- `prepare-app.sh`: Builds Docker image and prepares volumes
- `start-app.sh`: Applies all Kubernetes objects
- `stop-app.sh`: Deletes all Kubernetes resources
- `statefulset.yaml`: Defines StatefulSet, PV, and PVC
## 🚀 Steps to Deploy
1. **Prepare the application:**
```bash
./prepare-app.sh
hafzal03@LAPTOP-ELUS3HGM:~/mypro/z2$ kubectl get pods -n webapp-namespace
NAME READY STATUS RESTARTS AGE
flask-app-6b844bf6-cq9t6 1/1 Running 0 8m37s
postgres-644fc4c86d-l9h4f 1/1 Running 0 14m
hafzal03@LAPTOP-ELUS3HGM:~/mypro/z2$ minikube service flask-service -n webapp-namespace
minikube service flask-service -n webapp-namespace
kubectl get pods -n webapp-namespace
kubectl rollout restart deployment flask-app -n webapp-namespace

View File

@ -1,81 +0,0 @@
from flask import Flask, request, jsonify, render_template_string
import psycopg2
import os
app = Flask(__name__)
def get_db_connection():
return psycopg2.connect(
host=os.environ.get("POSTGRES_HOST"),
database=os.environ.get("POSTGRES_DB"),
user=os.environ.get("POSTGRES_USER"),
password=os.environ.get("POSTGRES_PASSWORD"),
port=os.environ.get("POSTGRES_PORT", 5432),
)
def init_db():
conn = get_db_connection()
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS students (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
room_number TEXT NOT NULL,
faculty TEXT NOT NULL
);
""")
conn.commit()
cur.close()
conn.close()
@app.route("/")
def index():
return "<h1>Welcome!</h1><p>Go to /add to add a student, /students to view all.</p>"
# HTML Form + POST Submission
@app.route("/add", methods=["GET", "POST"])
def add_student():
if request.method == "POST":
name = request.form.get("name")
room_number = request.form.get("room_number")
faculty = request.form.get("faculty")
if not name or not room_number or not faculty:
return "All fields are required!", 400
conn = get_db_connection()
cur = conn.cursor()
cur.execute("INSERT INTO students (name, room_number, faculty) VALUES (%s, %s, %s)", (name, room_number, faculty))
conn.commit()
cur.close()
conn.close()
return "<p>Student added successfully!</p><a href='/add'>Add another</a>"
# HTML form
return render_template_string("""
<h2>Add Student</h2>
<form method="post">
Name: <input name="name"><br>
Room Number: <input name="room_number"><br>
Faculty: <input name="faculty"><br>
<input type="submit" value="Add Student">
</form>
""")
# View all students
@app.route("/students", methods=["GET"])
def get_students():
conn = get_db_connection()
cur = conn.cursor()
cur.execute("SELECT id, name, room_number, faculty FROM students")
rows = cur.fetchall()
cur.close()
conn.close()
return jsonify([
{"id": row[0], "name": row[1], "room_number": row[2], "faculty": row[3]} for row in rows
])
if __name__ == "__main__":
init_db()
app.run(host="0.0.0.0", port=8000)

View File

@ -1,5 +0,0 @@
Flask==2.0.1
Werkzeug==2.0.3
psycopg2-binary==2.9.1
python-dotenv==0.19.0
Jinja2==3.0.3

View File

@ -1,32 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: flask-app
namespace: webapp-namespace
spec:
replicas: 1
selector:
matchLabels:
app: flask
template:
metadata:
labels:
app: flask
spec:
containers:
- name: flask
image: flask-app:latest
imagePullPolicy: Never
ports:
- containerPort: 8000
env:
- name: POSTGRES_HOST
value: postgres
- name: POSTGRES_DB
value: mydatabase
- name: POSTGRES_USER
value: myuser
- name: POSTGRES_PASSWORD
value: mypassword
- name: POSTGRES_PORT
value: "5432"

View File

@ -1,12 +0,0 @@
apiVersion: batch/v1
kind: Job
metadata:
name: migrate
spec:
template:
spec:
containers:
- name: migrator
image: myapp:latest
command: ["python", "manage.py", "migrate"]
restartPolicy: Never

View File

@ -1,4 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: webapp-namespace

View File

@ -1,57 +0,0 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: webapp-namespace
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: webapp-namespace
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:13
env:
- name: POSTGRES_DB
value: mydatabase
- name: POSTGRES_USER
value: myuser
- name: POSTGRES_PASSWORD
value: mypassword
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: webapp-namespace
spec:
selector:
app: postgres
ports:
- port: 5432

View File

@ -1,13 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: flask-service
namespace: webapp-namespace
spec:
selector:
app: flask
type: NodePort
ports:
- port: 8000
targetPort: 8000
nodePort: 30080 # <-- Changed this

View File

@ -1,10 +0,0 @@
#!/bin/bash
echo "Building Docker image for Flask app..."
docker build -t flask-app:latest .
echo "Creating Minikube image cache (if using Minikube)..."
eval $(minikube docker-env)
docker build -t flask-app:latest .
echo "Preparation complete."

View File

@ -1,18 +0,0 @@
#!/bin/bash
echo "Creating Namespace..."
kubectl apply -f k8s/namespace.yaml
echo "Creating Persistent Volumes and StatefulSet..."
kubectl apply -f k8s/statefulset.yaml -n webapp-namespace
echo "Deploying PostgreSQL..."
kubectl apply -f k8s/postgres-deployment.yaml -n webapp-namespace
echo "Deploying Flask App..."
kubectl apply -f k8s/deployment.yaml -n webapp-namespace
echo "Creating Service..."
kubectl apply -f k8s/service.yaml -n webapp-namespace
echo "All resources have been applied."

View File

@ -1,18 +0,0 @@
#!/bin/bash
echo "Deleting Flask App Deployment..."
kubectl delete -f k8s/deployment.yaml -n webapp-namespace
echo "Deleting Flask Service..."
kubectl delete -f k8s/service.yaml -n webapp-namespace
echo "Deleting PostgreSQL Deployment and PVC..."
kubectl delete -f k8s/postgres-deployment.yaml -n webapp-namespace
echo "Deleting StatefulSet and PVs..."
kubectl delete -f k8s/statefulset.yaml -n webapp-namespace
echo "Deleting Namespace..."
kubectl delete -f k8s/namespace.yaml
echo "All resources have been deleted."

View File

@ -1,9 +0,0 @@
FROM python:3.9-slim
WORKDIR /app
COPY app/ .
RUN pip install --no-cache-dir -r requirements.txt
CMD ["python", "main.py"]

View File

@ -1,138 +0,0 @@
# Flask + PostgreSQL Kubernetes WebApp
A lightweight full-stack web application powered by **Flask** and **PostgreSQL**, containerized with **Docker**, and deployed on **Kubernetes (Minikube)** using custom `Namespace`, `Deployment`, `StatefulSet`, and `Services`. Designed to demonstrate a production-like cloud-native architecture.
---
## Project Structure
```bash
z2/
├── app/
│ ├── main.py # Flask application logic
│ └── requirements.txt # Python dependencies
├── k8s/
│ ├── namespace.yaml # Custom namespace
│ ├── deployment.yaml # Flask app deployment
│ ├── postgres-deployment.yaml # PostgreSQL StatefulSet + PVC + Service
│ ├── migrate-job.yaml # Migration job (optional)
│ ├── service.yaml # Flask NodePort Service
├── Dockerfile # For building Flask app image
├── prepare-app.sh # Script: Build & tag Docker image
├── start-app.sh # Script: Apply all Kubernetes configs
├── stop-app.sh # Script: Delete all resources
└── README.md # Project documentation
```
---
## How It Works
1. **Prepare the Environment**
```bash
./prepare-app.sh
```
- Builds and tags Docker image locally
- Prepares persistent volume (PVC) for PostgreSQL
2. **Deploy the Application**
```bash
./start-app.sh
```
- Creates namespace, StatefulSet, Deployments, Services
3. **Access the Web App**
```bash
minikube service flask-service -n webapp-namespace
```
- Opens the app in your browser (NodePort)
4. **Stop and Clean Everything**
```bash
./stop-app.sh
```
- Deletes all created Kubernetes resources
---
## Technologies Used
| Tool | Purpose |
|----------------|------------------------------|
| Flask | Web framework (Python) |
| PostgreSQL | Relational database |
| Docker | Containerization |
| Kubernetes | Container orchestration |
| Minikube | Local Kubernetes environment |
---
## Health Checks
To ensure resilience and uptime, Kubernetes probes can be added:
```yaml
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
```
Add a `/health` route to your `main.py`:
```python
@app.route('/health')
def health():
return "OK", 200
```
---
## Troubleshooting
| Problem | Solution |
|-------------------------------------|--------------------------------------------------------------------------|
| ❌ `ImagePullBackOff` or `ErrImagePull` | Make sure image is built locally with `docker build -t flask-app .` and you're using `imagePullPolicy: Never` |
| ❌ App not accessible | Check service type is `NodePort`, port 30080 is exposed, and run `minikube service flask-service -n webapp-namespace` |
| ❌ Database errors | Ensure PostgreSQL pod is running (`kubectl get pods -n webapp-namespace`), check PVC and env vars |
| ❌ "command not found: kubectl" | Install `kubectl` and make sure its configured with `minikube` context |
| ❌ Connection refused errors | Check that `POSTGRES_HOST` matches the service name (`postgres`) and app waits until DB is ready |
---
## Future Enhancements
- Use Helm for templated deployments
- Add database migrations (Alembic / Flask-Migrate)
- CI/CD integration with GitLab or GitHub Actions
- Add Redis or caching layer
- Auto-scaling and HorizontalPodAutoscaler
## Author
**Hafzal Ahamed Hasan Mohamed**
TUKE, Faculty of Electrical Engineering and Informatics
Git: [git.kemt.fei.tuke.sk/hh304ug/zkt25](https://git.kemt.fei.tuke.sk/hh304ug/zkt25)
hafzal03@LAPTOP-ELUS3HGM:~/mypro/z2$ kubectl get pods -n webapp-namespace
NAME READY STATUS RESTARTS AGE
flask-app-6b844bf6-cq9t6 1/1 Running 0 8m37s
postgres-644fc4c86d-l9h4f 1/1 Running 0 14m
hafzal03@LAPTOP-ELUS3HGM:~/mypro/z2$ minikube service flask-service -n webapp-namespace
minikube service flask-service -n webapp-namespace
kubectl get pods -n webapp-namespace
kubectl rollout restart deployment flask-app -n webapp-namespace

View File

@ -1,81 +0,0 @@
from flask import Flask, request, jsonify, render_template_string
import psycopg2
import os
app = Flask(__name__)
def get_db_connection():
return psycopg2.connect(
host=os.environ.get("POSTGRES_HOST"),
database=os.environ.get("POSTGRES_DB"),
user=os.environ.get("POSTGRES_USER"),
password=os.environ.get("POSTGRES_PASSWORD"),
port=os.environ.get("POSTGRES_PORT", 5432),
)
def init_db():
conn = get_db_connection()
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS students (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
room_number TEXT NOT NULL,
faculty TEXT NOT NULL
);
""")
conn.commit()
cur.close()
conn.close()
@app.route("/")
def index():
return "<h1>Welcome!</h1><p>Go to /add to add a student, /students to view all.</p>"
# HTML Form + POST Submission
@app.route("/add", methods=["GET", "POST"])
def add_student():
if request.method == "POST":
name = request.form.get("name")
room_number = request.form.get("room_number")
faculty = request.form.get("faculty")
if not name or not room_number or not faculty:
return "All fields are required!", 400
conn = get_db_connection()
cur = conn.cursor()
cur.execute("INSERT INTO students (name, room_number, faculty) VALUES (%s, %s, %s)", (name, room_number, faculty))
conn.commit()
cur.close()
conn.close()
return "<p>Student added successfully!</p><a href='/add'>Add another</a>"
# HTML form
return render_template_string("""
<h2>Add Student</h2>
<form method="post">
Name: <input name="name"><br>
Room Number: <input name="room_number"><br>
Faculty: <input name="faculty"><br>
<input type="submit" value="Add Student">
</form>
""")
# View all students
@app.route("/students", methods=["GET"])
def get_students():
conn = get_db_connection()
cur = conn.cursor()
cur.execute("SELECT id, name, room_number, faculty FROM students")
rows = cur.fetchall()
cur.close()
conn.close()
return jsonify([
{"id": row[0], "name": row[1], "room_number": row[2], "faculty": row[3]} for row in rows
])
if __name__ == "__main__":
init_db()
app.run(host="0.0.0.0", port=8000)

View File

@ -1,5 +0,0 @@
Flask==2.0.1
Werkzeug==2.0.3
psycopg2-binary==2.9.1
python-dotenv==0.19.0
Jinja2==3.0.3

View File

@ -1,32 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: flask-app
namespace: webapp-namespace
spec:
replicas: 1
selector:
matchLabels:
app: flask
template:
metadata:
labels:
app: flask
spec:
containers:
- name: flask
image: flask-app:latest
imagePullPolicy: Never
ports:
- containerPort: 8000
env:
- name: POSTGRES_HOST
value: postgres
- name: POSTGRES_DB
value: mydatabase
- name: POSTGRES_USER
value: myuser
- name: POSTGRES_PASSWORD
value: mypassword
- name: POSTGRES_PORT
value: "5432"

View File

@ -1,12 +0,0 @@
apiVersion: batch/v1
kind: Job
metadata:
name: migrate
spec:
template:
spec:
containers:
- name: migrator
image: myapp:latest
command: ["python", "manage.py", "migrate"]
restartPolicy: Never

View File

@ -1,4 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: webapp-namespace

View File

@ -1,57 +0,0 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: webapp-namespace
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: webapp-namespace
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:13
env:
- name: POSTGRES_DB
value: mydatabase
- name: POSTGRES_USER
value: myuser
- name: POSTGRES_PASSWORD
value: mypassword
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: webapp-namespace
spec:
selector:
app: postgres
ports:
- port: 5432

View File

@ -1,13 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: flask-service
namespace: webapp-namespace
spec:
selector:
app: flask
type: NodePort
ports:
- port: 8000
targetPort: 8000
nodePort: 30080 # <-- Changed this

View File

@ -1,10 +0,0 @@
#!/bin/bash
echo "Building Docker image for Flask app..."
docker build -t flask-app:latest .
echo "Creating Minikube image cache (if using Minikube)..."
eval $(minikube docker-env)
docker build -t flask-app:latest .
echo "Preparation complete."

View File

@ -1,18 +0,0 @@
#!/bin/bash
echo "Creating Namespace..."
kubectl apply -f k8s/namespace.yaml
echo "Creating Persistent Volumes and StatefulSet..."
kubectl apply -f k8s/statefulset.yaml -n webapp-namespace
echo "Deploying PostgreSQL..."
kubectl apply -f k8s/postgres-deployment.yaml -n webapp-namespace
echo "Deploying Flask App..."
kubectl apply -f k8s/deployment.yaml -n webapp-namespace
echo "Creating Service..."
kubectl apply -f k8s/service.yaml -n webapp-namespace
echo "All resources have been applied."

View File

@ -1,18 +0,0 @@
#!/bin/bash
echo "Deleting Flask App Deployment..."
kubectl delete -f k8s/deployment.yaml -n webapp-namespace
echo "Deleting Flask Service..."
kubectl delete -f k8s/service.yaml -n webapp-namespace
echo "Deleting PostgreSQL Deployment and PVC..."
kubectl delete -f k8s/postgres-deployment.yaml -n webapp-namespace
echo "Deleting StatefulSet and PVs..."
kubectl delete -f k8s/statefulset.yaml -n webapp-namespace
echo "Deleting Namespace..."
kubectl delete -f k8s/namespace.yaml
echo "All resources have been deleted."