commit 53fe5aab6dd46966b35fb895ceccbff9f7c5b41b Author: Pablo Pérez Arcas Date: Mon Mar 30 08:20:48 2026 +0000 Assignment 1 Docker diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1f10b13 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY app.py . + +EXPOSE 5000 + +CMD ["python", "app.py"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..78205ef --- /dev/null +++ b/README.md @@ -0,0 +1,143 @@ +# Assignment 1 – Docker Notes App + +## Overview + +This project is a simple **Notes Application** built using Docker. +It demonstrates how to run a multi-container application composed of: + +- Frontend (Nginx) +- Backend (Flask REST API) +- Database (PostgreSQL) + +The application allows users to: +- Add notes +- View saved notes +- Delete notes + +All data is stored persistently using a Docker volume. + +--- + +## Architecture + +The application consists of three services: + +### 1. Frontend +- Technology: Nginx +- Serves static HTML page +- Proxies API requests to backend +- Accessible via browser + +### 2. Backend +- Technology: Python (Flask) +- Provides REST API: + - `GET /api/notes` + - `POST /api/notes` + - `DELETE /api/notes/{id}` +- Connects to PostgreSQL database + +### 3. Database +- Technology: PostgreSQL 16 +- Stores notes data +- Uses persistent Docker volume + +--- + +## Networking + +All containers are connected via a Docker virtual network: + +- **Network name:** `zkt_net` + +This allows communication between: +- frontend → backend +- backend → database + +--- + +## Persistent Storage + +- **Volume name:** `zkt_db_data` +- Purpose: store PostgreSQL data +- Ensures data is not lost after stopping containers + +--- + +## Containers Used + +| Container Name | Description | +|----------------|------------------------| +| zkt_frontend | Nginx web server | +| zkt_backend | Flask API | +| zkt_db | PostgreSQL database | + +--- + +## Requirements + +- Docker +- Docker Compose (plugin) +- Linux / WSL / Docker Desktop +- Web browser + +--- + +## How to Run the Application + +### 1. Prepare the application + +```bash +./prepare-app.sh + +### 2. Start the application + +./start-app.sh + +### Then open your browser: + + http://localhost:8080 + + +### 3. Stop the application +./stop-app.sh + +### 4. Remove the application +./remove-app.sh + +## Testing the Application + +-Open the web interface +-Add a note +-Verify it appears in the list +-Stop and restart the app: +./stop-app.sh +./start-app.sh +-Refresh the page + +## If the note is still there → persistence works correctly + +## Key Concepts Demonstrated + +-Multi-container Docker application +-Docker Compose usage +-Container networking +-Persistent storage with volumes +-REST API communication +-Reverse proxy with Nginx + +## Use of Artificial Intelligence + +Artificial intelligence was used to: + +-Design application architecture +-Generate initial code (frontend, backend, Docker config) +-Explain Docker concepts +-Assist with debugging and troubleshooting +-Prepare documentation + +## Tool used: + +-ChatGPT +-Gemini + +## Pablo Pérez Arcas \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..4831f8b --- /dev/null +++ b/app.py @@ -0,0 +1,93 @@ +from flask import Flask, request, jsonify +from flask_cors import CORS +import os +import psycopg2 +import time + +app = Flask(__name__) +CORS(app) + +DB_HOST = os.getenv("DB_HOST", "db") +DB_NAME = os.getenv("DB_NAME", "notesdb") +DB_USER = os.getenv("DB_USER", "notesuser") +DB_PASSWORD = os.getenv("DB_PASSWORD", "notessecret") +DB_PORT = os.getenv("DB_PORT", "5432") + +def get_connection(): + return psycopg2.connect( + host=DB_HOST, + database=DB_NAME, + user=DB_USER, + password=DB_PASSWORD, + port=DB_PORT + ) + +def init_db(): + for _ in range(20): + try: + conn = get_connection() + cur = conn.cursor() + cur.execute(""" + CREATE TABLE IF NOT EXISTS notes ( + id SERIAL PRIMARY KEY, + content TEXT NOT NULL + ); + """) + conn.commit() + cur.close() + conn.close() + return + except Exception: + time.sleep(2) + raise Exception("Database not ready") + +@app.route("/api/health", methods=["GET"]) +def health(): + return jsonify({"status": "ok"}) + +@app.route("/api/notes", methods=["GET"]) +def get_notes(): + conn = get_connection() + cur = conn.cursor() + cur.execute("SELECT id, content FROM notes ORDER BY id DESC;") + rows = cur.fetchall() + cur.close() + conn.close() + return jsonify([{"id": r[0], "content": r[1]} for r in rows]) + +@app.route("/api/notes", methods=["POST"]) +def add_note(): + data = request.get_json() + content = data.get("content", "").strip() + + if not content: + return jsonify({"error": "Content is required"}), 400 + + conn = get_connection() + cur = conn.cursor() + cur.execute("INSERT INTO notes (content) VALUES (%s) RETURNING id;", (content,)) + note_id = cur.fetchone()[0] + conn.commit() + cur.close() + conn.close() + + return jsonify({"id": note_id, "content": content}), 201 + +@app.route("/api/notes/", methods=["DELETE"]) +def delete_note(note_id): + conn = get_connection() + cur = conn.cursor() + cur.execute("DELETE FROM notes WHERE id = %s RETURNING id;", (note_id,)) + deleted = cur.fetchone() + conn.commit() + cur.close() + conn.close() + + if deleted is None: + return jsonify({"error": "Note not found"}), 404 + + return jsonify({"message": "Note deleted successfully"}) + +if __name__ == "__main__": + init_db() + app.run(host="0.0.0.0", port=5000) \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..89be148 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,51 @@ +services: + frontend: + image: nginx:latest + container_name: zkt_frontend + ports: + - "8080:80" + volumes: + - ./frontend/index.html:/usr/share/nginx/html/index.html:ro + - ./frontend/nginx.conf:/etc/nginx/conf.d/default.conf:ro + depends_on: + - backend + networks: + - zkt_net + restart: on-failure + + backend: + build: ./backend + image: zkt-notes-backend + container_name: zkt_backend + environment: + DB_HOST: db + DB_NAME: notesdb + DB_USER: notesuser + DB_PASSWORD: notessecret + DB_PORT: "5432" + depends_on: + - db + networks: + - zkt_net + restart: on-failure + + db: + image: postgres:16 + container_name: zkt_db + environment: + POSTGRES_DB: notesdb + POSTGRES_USER: notesuser + POSTGRES_PASSWORD: notessecret + volumes: + - zkt_db_data:/var/lib/postgresql/data + networks: + - zkt_net + restart: on-failure + +networks: + zkt_net: + external: true + +volumes: + zkt_db_data: + external: true \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..0d9412a --- /dev/null +++ b/index.html @@ -0,0 +1,250 @@ + + + + + + Notes App + + + +
+

Notes App

+

Simple Docker project with Nginx, Flask and PostgreSQL.

+ +
+ + +
+ +
+ +

Saved notes

+
    + + +
    + + + + \ No newline at end of file diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..d029ada --- /dev/null +++ b/nginx.conf @@ -0,0 +1,16 @@ +server { + listen 80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://backend:5000/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } +} \ No newline at end of file diff --git a/prepare-app.sh b/prepare-app.sh new file mode 100644 index 0000000..e27f813 --- /dev/null +++ b/prepare-app.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e + +echo "Preparing app..." + +docker network create zkt_net 2>/dev/null || true +docker volume create zkt_db_data 2>/dev/null || true + +docker compose build + +echo "App prepared." \ No newline at end of file diff --git a/remove-app.sh b/remove-app.sh new file mode 100644 index 0000000..eff9b5d --- /dev/null +++ b/remove-app.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +echo "Removing app..." + +docker compose down +docker network rm zkt_net 2>/dev/null || true +docker volume rm zkt_db_data 2>/dev/null || true + +echo "Removed app." \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d5ba281 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +flask +psycopg2-binary +flask-cors diff --git a/start-app.sh b/start-app.sh new file mode 100644 index 0000000..00f7b8f --- /dev/null +++ b/start-app.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +echo "Running app..." + +docker compose up -d + +echo "The app is available at http://localhost:8080" \ No newline at end of file diff --git a/stop-app.sh b/stop-app.sh new file mode 100644 index 0000000..3c7c443 --- /dev/null +++ b/stop-app.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +echo "Stopping app..." + +docker compose stop + +echo "App stopped." \ No newline at end of file