diff --git a/z2/Dockerfile b/z2/Dockerfile
new file mode 100644
index 0000000..3578b8f
--- /dev/null
+++ b/z2/Dockerfile
@@ -0,0 +1,12 @@
+FROM python:3.9-slim
+
+WORKDIR /app
+
+COPY requirements.txt .
+RUN pip install --no-cache-dir -r requirements.txt
+
+COPY app.py .
+
+EXPOSE 5000
+
+CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
\ No newline at end of file
diff --git a/z2/README.md b/z2/README.md
new file mode 100644
index 0000000..98530e0
--- /dev/null
+++ b/z2/README.md
@@ -0,0 +1,31 @@
+# Shopping List Web App on Kubernetes (Local)
+
+This project deploys a Flask-based shopping list app with PostgreSQL on a local Kubernetes cluster (Docker Desktop).
+
+## Prerequisites
+- Docker Desktop with Kubernetes enabled
+- kubectl configured for `docker-desktop` context
+
+## Files
+- `namespace.yaml`: Kubernetes namespace
+- `persistent-storage.yaml`: PVC for Postgres data
+- `postgres-deployment.yaml` / `postgres-service.yaml`: Postgres setup
+- `deployment.yaml` / `service.yaml`: Web app setup
+- `prepare-app.sh`: build Docker image
+- `start-app.sh`: apply Kubernetes resources
+- `stop-app.sh`: delete all resources
+
+## Usage
+1. Build the image:
+ ```bash
+ ./prepare-app.sh
+
+2. Deploy to Kubernetes:
+ ```bash
+ ./start-app.sh
+
+3. Access the app:
+Open http://localhost:80 in your browser
+
+4. Clean up :
+ ./stop-app.sh
\ No newline at end of file
diff --git a/z2/app.py b/z2/app.py
new file mode 100644
index 0000000..3b9df90
--- /dev/null
+++ b/z2/app.py
@@ -0,0 +1,112 @@
+from flask import Flask, request, redirect, url_for, render_template_string
+import os
+import psycopg2
+from psycopg2 import pool
+
+app = Flask(__name__)
+
+DB_HOST = os.getenv('DB_HOST', 'localhost')
+DB_PORT = os.getenv('DB_PORT', '5432')
+DB_NAME = os.getenv('DB_NAME', 'postgres')
+DB_USER = os.getenv('DB_USER', 'postgres')
+DB_PASS = os.getenv('DB_PASS', 'postgres')
+
+connection_pool = psycopg2.pool.SimpleConnectionPool(
+ 1, 10,
+ host=DB_HOST,
+ port=DB_PORT,
+ database=DB_NAME,
+ user=DB_USER,
+ password=DB_PASS
+)
+
+# Initialize database schema
+with connection_pool.getconn() as conn:
+ with conn.cursor() as cur:
+ cur.execute('''
+ CREATE TABLE IF NOT EXISTS shopping_items (
+ id SERIAL PRIMARY KEY,
+ item TEXT NOT NULL,
+ quantity INTEGER DEFAULT 1,
+ purchased BOOLEAN DEFAULT FALSE
+ );
+ ''')
+ conn.commit()
+
+HTML = """
+
+
+
+ Shopping List
+
+
+
+ Shopping List
+
+
+ {% for id, name, qty, done in items %}
+ -
+ {{ name }} ({{ qty }})
+ {% if not done %}
+
+ {% else %}
+
+ {% endif %}
+
+
+ {% endfor %}
+
+
+
+"""
+
+@app.route('/')
+def index():
+ conn = connection_pool.getconn()
+ with conn.cursor() as cur:
+ cur.execute("SELECT id, item, quantity, purchased FROM shopping_items ORDER BY purchased, id")
+ items = cur.fetchall()
+ connection_pool.putconn(conn)
+ return render_template_string(HTML, items=items)
+
+@app.route('/add', methods=['POST'])
+def add_item():
+ item = request.form['item']
+ qty = int(request.form.get('quantity', 1))
+ conn = connection_pool.getconn()
+ with conn.cursor() as cur:
+ cur.execute("INSERT INTO shopping_items (item, quantity) VALUES (%s, %s)", (item, qty))
+ conn.commit()
+ connection_pool.putconn(conn)
+ return redirect(url_for('index'))
+
+@app.route('/toggle/', methods=['POST'])
+def toggle_item(item_id):
+ conn = connection_pool.getconn()
+ with conn.cursor() as cur:
+ cur.execute("UPDATE shopping_items SET purchased=NOT purchased WHERE id=%s", (item_id,))
+ conn.commit()
+ connection_pool.putconn(conn)
+ return redirect(url_for('index'))
+
+@app.route('/delete/', methods=['POST'])
+def delete_item(item_id):
+ conn = connection_pool.getconn()
+ with conn.cursor() as cur:
+ cur.execute("DELETE FROM shopping_items WHERE id=%s", (item_id,))
+ conn.commit()
+ connection_pool.putconn(conn)
+ return redirect(url_for('index'))
+
+if __name__ == '__main__':
+ app.run(host='0.0.0.0', port=5000)
\ No newline at end of file
diff --git a/z2/deployment.yaml b/z2/deployment.yaml
new file mode 100644
index 0000000..991b621
--- /dev/null
+++ b/z2/deployment.yaml
@@ -0,0 +1,50 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: web
+ namespace: my-app
+spec:
+ replicas: 2
+ selector:
+ matchLabels:
+ app: web
+ template:
+ metadata:
+ labels:
+ app: web
+ spec:
+ initContainers:
+ - name: wait-for-postgres
+ image: busybox
+ command:
+ - sh
+ - -c
+ - |
+ until nc -z postgres 5432; do
+ echo "Waiting for PostgreSQL..."
+ sleep 2
+ done
+ containers:
+ - name: web
+ # use the LOCAL image you just built, not the one on Docker Hub
+ image: simple-web-app:latest
+ imagePullPolicy: IfNotPresent
+ ports:
+ - containerPort: 5000
+ env:
+ - name: DB_HOST
+ value: postgres
+ - name: DB_PORT
+ value: "5432"
+ - name: DB_NAME
+ value: postgres
+ - name: DB_USER
+ value: postgres
+ - name: DB_PASS
+ value: postgres
+ readinessProbe:
+ httpGet:
+ path: /
+ port: 5000
+ initialDelaySeconds: 15
+ periodSeconds: 10
diff --git a/z2/namespace.yaml b/z2/namespace.yaml
new file mode 100644
index 0000000..ea5c7ee
--- /dev/null
+++ b/z2/namespace.yaml
@@ -0,0 +1,4 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: my-app
\ No newline at end of file
diff --git a/z2/persistent-storage.yaml b/z2/persistent-storage.yaml
new file mode 100644
index 0000000..e670f82
--- /dev/null
+++ b/z2/persistent-storage.yaml
@@ -0,0 +1,12 @@
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: data-pvc
+ namespace: my-app
+spec:
+ accessModes:
+ - ReadWriteOnce
+ storageClassName: docker-desktop
+ resources:
+ requests:
+ storage: 1Gi
\ No newline at end of file
diff --git a/z2/postgres-deployment.yaml b/z2/postgres-deployment.yaml
new file mode 100644
index 0000000..7cefcf7
--- /dev/null
+++ b/z2/postgres-deployment.yaml
@@ -0,0 +1,33 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: postgres
+ namespace: my-app
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: postgres
+ template:
+ metadata:
+ labels:
+ app: postgres
+ spec:
+ containers:
+ - name: postgres
+ image: postgres:14
+ ports:
+ - containerPort: 5432
+ env:
+ - name: POSTGRES_USER
+ value: postgres
+ - name: POSTGRES_PASSWORD
+ value: postgres
+ - name: POSTGRES_DB
+ value: postgres
+ volumeMounts:
+ - name: postgres-storage
+ mountPath: /var/lib/postgresql/data
+ volumes:
+ - name: postgres-storage
+ emptyDir: {}
\ No newline at end of file
diff --git a/z2/postgres-service.yaml b/z2/postgres-service.yaml
new file mode 100644
index 0000000..38d775a
--- /dev/null
+++ b/z2/postgres-service.yaml
@@ -0,0 +1,12 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: postgres
+ namespace: my-app
+spec:
+ selector:
+ app: postgres
+ ports:
+ - port: 5432
+ targetPort: 5432
+ clusterIP: None
\ No newline at end of file
diff --git a/z2/prepare-app.sh b/z2/prepare-app.sh
new file mode 100644
index 0000000..fc2742b
--- /dev/null
+++ b/z2/prepare-app.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+# Prepare the environment: build Docker image
+
+set -e
+
+IMAGE_NAME="simple-web-app:latest"
+
+echo "Building Docker image ${IMAGE_NAME}..."
+docker build -t ${IMAGE_NAME} .
+
+echo "Preparation complete."
\ No newline at end of file
diff --git a/z2/requirements.txt b/z2/requirements.txt
new file mode 100644
index 0000000..9856f32
--- /dev/null
+++ b/z2/requirements.txt
@@ -0,0 +1,4 @@
+Flask>=2.0.0
+Werkzeug>=2.0.0
+gunicorn>=20.1.0
+psycopg2-binary>=2.9.3
\ No newline at end of file
diff --git a/z2/service.yaml b/z2/service.yaml
new file mode 100644
index 0000000..c2dd6db
--- /dev/null
+++ b/z2/service.yaml
@@ -0,0 +1,13 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: web
+ namespace: my-app
+spec:
+ type: NodePort
+ selector:
+ app: web
+ ports:
+ - port: 80
+ targetPort: 5000
+ nodePort: 30080
diff --git a/z2/start-app.sh b/z2/start-app.sh
new file mode 100644
index 0000000..2c23a1f
--- /dev/null
+++ b/z2/start-app.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+# Deploy the application to local Kubernetes
+
+set -e
+
+# Ensure kubectl uses the local Docker Desktop cluster
+kubectl config use-context docker-desktop
+
+NAMESPACE="my-app"
+
+echo "Creating namespace..."
+kubectl apply -f namespace.yaml
+
+# (optional) if you’ve switched to emptyDir for Postgres storage, skip PVC
+# echo "Creating PersistentVolumeClaim..."
+# kubectl apply -f persistent-storage.yaml
+
+echo "Deploying PostgreSQL..."
+kubectl apply -f postgres-deployment.yaml
+kubectl apply -f postgres-service.yaml
+
+echo "Waiting for PostgreSQL pod(s) to be ready..."
+kubectl wait --for=condition=ready pod -l app=postgres -n ${NAMESPACE} --timeout=120s
+
+echo "Deploying web application..."
+kubectl apply -f deployment.yaml
+kubectl apply -f service.yaml
+
+echo "Deployment complete. Access the app at http://localhost:30080"
diff --git a/z2/stop-app.sh b/z2/stop-app.sh
new file mode 100644
index 0000000..efcb4df
--- /dev/null
+++ b/z2/stop-app.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+# Remove the application from local Kubernetes
+
+set -e
+
+kubectl config use-context docker-desktop
+
+NAMESPACE="my-app"
+
+echo "Deleting web application..."
+kubectl delete -f service.yaml -n ${NAMESPACE} --ignore-not-found
+kubectl delete -f deployment.yaml -n ${NAMESPACE} --ignore-not-found
+
+echo "Deleting PostgreSQL..."
+kubectl delete -f postgres-service.yaml -n ${NAMESPACE} --ignore-not-found
+kubectl delete -f postgres-deployment.yaml -n ${NAMESPACE} --ignore-not-found
+
+echo "Deleting storage..."
+kubectl delete -f persistent-storage.yaml -n ${NAMESPACE} --ignore-not-found
+
+echo "Deleting namespace..."
+kubectl delete -f namespace.yaml --ignore-not-found
+
+echo "Cleanup complete."
\ No newline at end of file