From 5371bdce3bc25fcdeebb23b46e7d425de744c6e4 Mon Sep 17 00:00:00 2001 From: zwitschi Date: Tue, 28 Apr 2026 10:20:58 +0200 Subject: [PATCH] Refactor application ports to 12000 for backend and 12001 for frontend; update documentation and configuration files accordingly Co-authored-by: Copilot --- README.md | 2 +- backend/Dockerfile | 22 +++ backend/app/main.py | 2 +- docker-compose.yml | 60 ++++++++ docs/7-deployment-view.md | 84 +++++++++-- docs/deployment/coolify.md | 36 ++++- docs/deployment/docker-compose.md | 243 ++++++++++++++++++++++++++++++ frontend/Dockerfile | 21 +++ frontend/app/static/app.js | 2 +- nginx/coolify.conf | 2 +- nginx/docker-compose.conf | 53 +++++++ 11 files changed, 506 insertions(+), 21 deletions(-) create mode 100644 backend/Dockerfile create mode 100644 docker-compose.yml create mode 100644 docs/deployment/docker-compose.md create mode 100644 frontend/Dockerfile create mode 100644 nginx/docker-compose.conf diff --git a/README.md b/README.md index a55f474..5db4478 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ uvicorn app.main:app --reload --port 12000 ```bash cd frontend -flask --app app.main run --port 5000 +flask --app app.main run --port 12001 ``` ### Running tests diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..922daab --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,22 @@ +FROM python:3.12-slim + +WORKDIR /app + +# Install system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements and install Python dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY . . + +# Expose port +EXPOSE 12000 + +# Run the application +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "12000"] diff --git a/backend/app/main.py b/backend/app/main.py index 2094998..44c2d8b 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -30,7 +30,7 @@ app = FastAPI( app.add_middleware( CORSMiddleware, - allow_origins=[os.getenv("CORS_ORIGINS", "http://localhost:5000")], + allow_origins=[os.getenv("CORS_ORIGINS", "http://localhost:12001")], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..fc78dbf --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,60 @@ +version: '3.8' + +services: + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: ai-backend + ports: + - "12000:12000" + environment: + - OPENROUTER_API_KEY=${OPENROUTER_API_KEY} + - JWT_SECRET=${JWT_SECRET} + - APP_URL=${APP_URL:-http://localhost} + - APP_NAME=${APP_NAME:-AI Allucanget} + - CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:12001} + volumes: + - ./data:/app/data + networks: + - app-network + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:12000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + container_name: ai-frontend + ports: + - "12001:12001" + environment: + - FLASK_SECRET_KEY=${FLASK_SECRET_KEY} + - BACKEND_URL=${BACKEND_URL:-http://backend:12000} + depends_on: + backend: + condition: service_healthy + networks: + - app-network + + nginx: + image: nginx:alpine + container_name: ai-nginx + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/docker-compose.conf:/etc/nginx/conf.d/default.conf:ro + depends_on: + - backend + - frontend + networks: + - app-network + +networks: + app-network: + driver: bridge diff --git a/docs/7-deployment-view.md b/docs/7-deployment-view.md index fe44c61..5acd00e 100644 --- a/docs/7-deployment-view.md +++ b/docs/7-deployment-view.md @@ -8,13 +8,13 @@ Describes: ## Infrastructure Level 1 ```text -┌─────────────────────────────────────────────┐ -│ Host / VM │ -│ ┌─────────────┐ ┌─────────────────────┐ │ -│ │ frontend │ │ backend │ │ -│ │ (Flask) │ │ (FastAPI) │ │ -│ │ :5000 │ │ :12000 │ │ -│ └──────┬──────┘ └──────────┬──────────┘ │ +┌────────────────────────────────────────────┐ +│ Host / VM │ +│ ┌─────────────┐ ┌────────────────────┐ │ +│ │ frontend │ │ backend │ │ +│ │ (Flask) │ │ (FastAPI) │ │ +│ │ :12001 │ │ :12000 │ │ +│ └──────┬──────┘ └─────────┬──────────┘ │ │ │ │ │ │ └────────┬──────────┘ │ │ │ │ @@ -22,7 +22,7 @@ Describes: │ │ db (DuckDB) │ │ │ │ data/app.db │ │ │ └────────────────┘ │ -└─────────────────────────────────────────────┘ +└────────────────────────────────────────────┘ ``` **Motivation:** All three components run on a single VM (or as Docker containers) for simplicity and low operational overhead. @@ -33,12 +33,74 @@ Describes: | Building Block | Container / Process | Port | | --------------- | ---------------------------- | ----- | -| Flask frontend | `frontend` | 5000 | +| Flask frontend | `frontend` | 12001 | | FastAPI backend | `backend` | 12000 | | DuckDB | File on host (`data/app.db`) | — | ## Infrastructure Level 2 -### Docker Compose (alternative) +### Docker Compose (Recommended for Development & Production) -All three services can be run with `docker compose up`. The `backend` mounts the `data/` volume for DuckDB persistence. +All services are containerized and orchestrated with `docker compose`: + +```text +┌─────────────────────────────────────────────────────────────┐ +│ Docker Host / VM │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Docker Network: app-network (bridge) │ │ +│ │ ┌──────────────────────────────────────────────┐ │ │ +│ │ │ Backend Container (FastAPI) │ │ │ +│ │ │ - Port: 12000 │ │ │ +│ │ │ - Service Name: backend │ │ │ +│ │ │ - Volume Mount: /app/data ← host/data/ │ │ │ +│ │ ├──────────────────────────────────────────────┤ │ │ +│ │ │ Frontend Container (Flask) │ │ │ +│ │ │ - Port: 12001 │ │ │ +│ │ │ - Service Name: frontend │ │ │ +│ │ │ - Depends on: backend (health check) │ │ │ +│ │ ├──────────────────────────────────────────────┤ │ │ +│ │ │ Nginx Container (Reverse Proxy) │ │ │ +│ │ │ - Port: 80 (HTTP), 443 (HTTPS) │ │ │ +│ │ │ - Config: nginx/docker-compose.conf │ │ │ +│ │ │ - Routes: /api/* → backend:12000 │ │ │ +│ │ │ / → frontend:12001 │ │ │ +│ │ └──────────────────────────────────────────────┘ │ │ +│ └──────────────────────────────────────────────────────┘ │ +│ ▲ │ +│ Host Port Bindings │ +│ 80:80, 443:443, 12000:12000, 12001:12001 │ +└─────────────────────────────────────────────────────────────┘ + │ + Users / Internet +``` + +**Deployment Steps:** + +1. Ensure Docker and Docker Compose are installed +2. Create `.env` with required environment variables +3. Run: `docker compose up --build` +4. Access via browser at `http://localhost:12001` or through Nginx at `http://localhost:80` + +**Benefits:** + +- **Consistency**: Same containerized environment across development, testing, and production +- **Simplicity**: Single command to start entire stack +- **Portability**: Run on any system with Docker installed +- **Persistence**: DuckDB data survives container restarts via volume mounts +- **Networking**: Service names enable automatic DNS resolution (backend:12000, frontend:12001) +- **Observability**: Easy logging with `docker compose logs` + +**See**: [Docker Compose Deployment Guide](./deployment/docker-compose.md) for detailed instructions. + +### Coolify (Alternative for Existing Deployments) + +If using Coolify instead of Docker Compose: + +1. **Backend Service**: Nixpacks build with base directory `/backend`, port 12000 +2. **Frontend Service**: Nixpacks build with base directory `/frontend`, port 12001 +3. **Reverse Proxy**: Coolify's built-in proxy or custom Nginx config +4. **Data Persistence**: Volume mount at `/app/data` for DuckDB + +**Note**: Both services must be on the same Coolify server or cluster for service-to-service communication via `http://localhost:12000` or service DNS names (depending on Coolify's networking setup). + +**See**: [Coolify Deployment Guide](./deployment/coolify.md) for detailed instructions. diff --git a/docs/deployment/coolify.md b/docs/deployment/coolify.md index 032c9b2..9b8d5eb 100644 --- a/docs/deployment/coolify.md +++ b/docs/deployment/coolify.md @@ -2,6 +2,30 @@ This guide covers deploying `ai.allucanget.biz` using [Coolify](https://coolify.io) from the repository `https://git.allucanget.biz/allucanget/ai.allucanget.biz.git`. +## Deployment Options + +The application supports two deployment approaches: + +### Option 1: Docker Compose (Recommended) + +- **Best for**: Local development, consistent environments, simplified deployment +- **See**: [Docker Compose Deployment Guide](./docker-compose.md) +- **Key benefits**: + - Same containerized environment locally and in production + - Built-in service orchestration and networking + - Easy volume management for data persistence + - Better debugging and log viewing + +### Option 2: Coolify with Nixpacks (Alternative) + +- **Best for**: Existing Coolify installations, serverless deployments +- **Key differences**: + - Uses Coolify's Nixpacks build system instead of Docker + - Requires separate services setup in Coolify UI + - Manual environment variable configuration per service + +This guide covers **Option 2 (Nixpacks)**. For **Option 1 (Docker Compose)**, see the [Docker Compose Deployment Guide](./docker-compose.md). + ## Architecture Overview The application consists of two Python services: @@ -9,12 +33,12 @@ The application consists of two Python services: | Service | Framework | Port | Description | | -------- | ----------------- | ----- | ------------------------------------------ | | Backend | FastAPI + uvicorn | 12000 | REST API, auth, AI generation, DuckDB | -| Frontend | Flask + gunicorn | 5000 | SSR web UI, session auth, proxy to backend | +| Frontend | Flask + gunicorn | 12001 | SSR web UI, session auth, proxy to backend | Coolify's built-in reverse proxy routes traffic: - `/api/*` → Backend (port 12000) -- `/` → Frontend (port 5000) +- `/` → Frontend (port 12001) ## Prerequisites @@ -58,10 +82,10 @@ Add these as **Runtime** environment variables in Coolify: 3. Choose the `main` branch 4. Set **Build Pack** to `nixpacks` 5. **CRITICAL: Set Base Directory to `/frontend`** — this tells Nixpacks to look in the `frontend/` subdirectory for `requirements.txt` and the Python application -6. Set **Ports Exposed** to `5000` +6. Set **Ports Exposed** to `12001` 7. Set **Start Command** to: ```txt - gunicorn app.main:app --bind 0.0.0.0:5000 --workers 2 --timeout 120 + gunicorn app.main:app --bind 0.0.0.0:12001 --workers 2 --timeout 120 ``` 8. Click **Create Resource** @@ -91,7 +115,7 @@ Coolify provides a built-in reverse proxy. Configure routing rules: ### Frontend Proxy Rules - **Domain**: `ai.allucanget.biz` -- **Port**: `5000` +- **Port**: `12001` - **Path**: `/` → forward to frontend ### Nginx Configuration (Optional) @@ -113,7 +137,7 @@ location /api/ { # Frontend proxy location / { - proxy_pass http://frontend:5000; + proxy_pass http://frontend:12001; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/docs/deployment/docker-compose.md b/docs/deployment/docker-compose.md new file mode 100644 index 0000000..6e41bf4 --- /dev/null +++ b/docs/deployment/docker-compose.md @@ -0,0 +1,243 @@ +# Docker Compose Deployment Guide + +This guide explains how to deploy the AI application locally using Docker Compose. + +## Overview + +Docker Compose provides a convenient way to: + +- **Develop locally** with the same containerized environment used in production +- **Manage services** as a single stack (backend, frontend, and Nginx) +- **Persist data** using volume mounts +- **Enable service-to-service communication** using Docker's internal networking + +## Prerequisites + +- Docker Desktop installed and running +- Docker Compose v2.0+ (included with Docker Desktop) +- A `.env` file with required environment variables (see [Configuration](#configuration)) + +## Quick Start + +1. **Clone the repository** and navigate to the project root: + +```bash +cd ai.allucanget.biz +``` + +2. **Configure environment variables** in `.env`: + +```bash +# Copy the example if you don't have a .env file +cp .env.example .env + +# Edit .env and fill in required values +# - OPENROUTER_API_KEY: Your openrouter.ai API key +# - JWT_SECRET: A random secret for signing JWT tokens +# - FLASK_SECRET_KEY: A random secret for Flask session cookies +``` + +3. **Build and start the services**: + +```bash +docker compose up --build +``` + +4. **Access the application**: + - Frontend UI: http://localhost:12001 + - Backend API: http://localhost:12000 + - API Health: http://localhost:12000/health + - Nginx Proxy: http://localhost:80 (routes to frontend) or http://localhost:80/api (routes to backend) + +5. **View logs**: + +```bash +# All services +docker compose logs -f + +# Specific service +docker compose logs -f backend +docker compose logs -f frontend +``` + +6. **Stop the services**: + +```bash +docker compose down +``` + +## Configuration + +### Environment Variables + +The `.env` file in the project root is automatically loaded by Docker Compose. Required variables: + +| Variable | Purpose | Example | +| -------------------- | ------------------------------------------ | ---------------------------------------------------------------------------- | +| `OPENROUTER_API_KEY` | API key for AI generation | `sk-or-v1-...` | +| `JWT_SECRET` | Secret for JWT token signing | (random hex string) | +| `FLASK_SECRET_KEY` | Secret for Flask session cookies | (random hex string) | +| `BACKEND_URL` | Internal URL for frontend to reach backend | `http://backend:12000` (Docker) or `http://localhost:12000` (local dev) | +| `CORS_ORIGINS` | Allowed CORS origins for backend | `http://localhost:12001` (local) or `https://ai.allucanget.biz` (production) | +| `APP_URL` | Public URL of the backend | `http://localhost` or `https://ai.allucanget.biz` | +| `APP_NAME` | Application name | `AI Allucanget` | + +### Volume Mounts + +The `docker-compose.yml` defines the following volume: + +- `./data:/app/data` — Persists the DuckDB database (`app.db`) between container restarts + +## Architecture + +``` +┌─────────────────────────────────────────────────────────┐ +│ Docker Network: app-network (internal bridge) │ +├─────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────────┐ ┌──────────────────────────┐ │ +│ │ Frontend │ │ Backend │ │ +│ │ Port 12001 │ │ Port 12000 │ │ +│ │ Flask + Gunicorn│◄──►│ FastAPI + Uvicorn │ │ +│ │ (Service name: │ │ (Service name: backend) │ │ +│ │ frontend) │ │ │ │ +│ └──────────────────┘ │ ┌────────────────────┐ │ │ +│ ▲ │ │ DuckDB Database │ │ │ +│ │ │ │ /app/data/app.db │ │ │ +│ ┌────▼──────────────┐ │ └────────────────────┘ │ │ +│ │ Nginx (Port 80) │ └──────────────────────────┘ │ +│ │ Reverse Proxy │ │ +│ └───────────────────┘ │ +│ ▲ │ +│ │ (bind mount) │ +│ Host:80, 443 │ +└─────────────────────────────────────────────────────────┘ + │ + Host OS +``` + +## Common Tasks + +### View service status + +```bash +docker compose ps +``` + +Output example: + +``` +NAME COMMAND SERVICE STATUS PORTS +ai-backend "uvicorn app.main:a…" backend Up 2m 0.0.0.0:12000->12000/tcp +ai-frontend "gunicorn app.main:…" frontend Up 2m 0.0.0.0:12001->12001/tcp +ai-nginx "nginx -g daemon of…" nginx Up 2m 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp +``` + +### Rebuild images + +```bash +# Rebuild all services +docker compose build + +# Rebuild a specific service +docker compose build backend +docker compose build frontend +``` + +### Access container shell + +```bash +# Backend shell +docker compose exec backend bash + +# Frontend shell +docker compose exec frontend bash +``` + +### View service logs + +```bash +# Tail all logs +docker compose logs -f + +# Follow only backend logs +docker compose logs -f backend + +# Last 100 lines of frontend logs +docker compose logs --tail 100 frontend +``` + +### Clean up + +```bash +# Stop containers but keep volumes and images +docker compose stop + +# Stop and remove containers +docker compose down + +# Stop containers and remove volumes (WARNING: deletes database!) +docker compose down -v + +# Remove images as well +docker compose down --rmi all +``` + +## Troubleshooting + +### "Docker daemon is not running" + +**Error**: `error during connect: This error may indicate the client is not properly configured` + +**Solution**: Start Docker Desktop (Windows/Mac) or start the Docker daemon (Linux). + +### "Port 12000 or 12001 already in use" + +**Error**: `Error response from daemon: bind: address already in use` + +**Solution**: Either stop the other application using that port, or modify the ports in `docker-compose.yml`: + +```yaml +ports: + - "13000:12000" # Maps host:13000 to container:12000 + - "13001:12001" # Maps host:13001 to container:12001 +``` + +### Frontend cannot reach backend + +**Error**: Connection refused or 502 Bad Gateway + +**Ensure**: + +1. Backend service is running: `docker compose ps` +2. `BACKEND_URL` in `.env` is set to `http://backend:12000` (not `localhost`) +3. Services are on the same Docker network: `docker compose ps` shows them in the same `docker-compose.yml` + +### Database file not persisting + +**Issue**: Data is lost after `docker compose down` + +**Solution**: The volume mount `./data:/app/data` should persist data automatically. Check that: + +1. The `data/` directory exists on your host +2. The volume is mounted correctly in `docker-compose.yml` +3. DuckDB has write permissions to the `data/` directory + +## Performance Tips + +- **Development**: Use `docker compose up` for real-time logs and quick iterations +- **Background**: Use `docker compose up -d` to run services detached +- **Rebuilds**: Use `docker compose up --build` only when dependencies or Dockerfiles change +- **Caching**: Docker caches layers; unchanged steps build faster + +## Next Steps + +- **Deployment**: See [Coolify Deployment](./coolify.md) for production deployment +- **Testing**: See [Testing Guide](../TESTING.md) for running tests +- **Architecture**: See [Deployment View](../7-deployment-view.md) for system architecture + +## Additional Resources + +- [Docker Compose Documentation](https://docs.docker.com/compose/) +- [Docker Networking Guide](https://docs.docker.com/network/) +- [Best Practices for Writing Dockerfiles](https://docs.docker.com/develop/dev-best-practices/dockerfile_best-practices/) diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..dca9983 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,21 @@ +FROM python:3.12-slim + +WORKDIR /app + +# Install system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements and install Python dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY . . + +# Expose port +EXPOSE 12001 + +# Run the application +CMD ["gunicorn", "app.main:app", "--bind", "0.0.0.0:12001", "--workers", "2", "--timeout", "120"] diff --git a/frontend/app/static/app.js b/frontend/app/static/app.js index 9d9d8e5..c23bcb3 100644 --- a/frontend/app/static/app.js +++ b/frontend/app/static/app.js @@ -79,6 +79,6 @@ document.addEventListener("DOMContentLoaded", () => { } catch (e) { console.error("Video polling error:", e); } - }, 5000); + }, 12001); } }); diff --git a/nginx/coolify.conf b/nginx/coolify.conf index ff16b7f..41eb7db 100644 --- a/nginx/coolify.conf +++ b/nginx/coolify.conf @@ -8,7 +8,7 @@ upstream backend { # Frontend proxy upstream frontend { - server 127.0.0.1:5000; + server 127.0.0.1:12001; } server { diff --git a/nginx/docker-compose.conf b/nginx/docker-compose.conf new file mode 100644 index 0000000..fbc0a86 --- /dev/null +++ b/nginx/docker-compose.conf @@ -0,0 +1,53 @@ +# Nginx reverse proxy configuration for Docker Compose deployment +# Place this in docker-compose.yml volume mount to /etc/nginx/conf.d/default.conf + +# Backend API proxy - use Docker service name instead of localhost +upstream backend { + server backend:12000; +} + +# Frontend proxy - use Docker service name instead of localhost +upstream frontend { + server frontend:12001; +} + +server { + listen 80; + server_name _; + + # Backend API proxy + location /api/ { + proxy_pass http://backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket support (if needed in future) + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + # Frontend proxy + location / { + proxy_pass http://frontend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Static files caching + location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + proxy_pass http://frontend; + expires 30d; + add_header Cache-Control "public, immutable"; + } + } + + # Health check endpoint + location /health { + proxy_pass http://backend; + proxy_set_header Host $host; + } +}