From 74a4e3ede8833f96316c3f7a8bad9bdf52413cb5 Mon Sep 17 00:00:00 2001 From: James Pattinson Date: Sun, 26 Apr 2026 09:43:02 +0000 Subject: [PATCH] Container refactoring Co-authored-by: Copilot --- .env.example | 6 ++ .gitignore | 1 + QUICKSTART.md | 60 ++++++------- README.md | 109 +++++++++++++----------- docker-compose.yml | 81 ++++++++++++------ docker/gateway/Dockerfile | 8 ++ docker/gateway/docker-entrypoint-dev.sh | 22 +++++ docker/gateway/nginx.dev.conf | 79 +++++++++++++++++ frontend/vite.config.ts | 2 +- 9 files changed, 259 insertions(+), 109 deletions(-) create mode 100644 docker/gateway/Dockerfile create mode 100644 docker/gateway/docker-entrypoint-dev.sh create mode 100644 docker/gateway/nginx.dev.conf diff --git a/.env.example b/.env.example index 9a8d786..5fe840a 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,12 @@ APP_VERSION="1.0.0" DEBUG=True ENVIRONMENT=development +# Gateway host port +APP_PORT=8050 +APP_TLS_PORT=8443 +DEV_CERT_CN=localhost +DEV_CERT_SANS=DNS:localhost,IP:127.0.0.1,IP:::1 + # API Settings API_V1_PREFIX=/api/v1 SECRET_KEY=your-secret-key-change-this-in-production diff --git a/.gitignore b/.gitignore index a4fdcab..60b54fb 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ Thumbs.db # Docker docker-compose.override.yml +docker/certs/ # Uploads uploads/ diff --git a/QUICKSTART.md b/QUICKSTART.md index f2fdf36..46eb821 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -4,23 +4,28 @@ ```bash # Start all services -docker-compose up -d +docker compose up -d # Watch the logs until services are ready -docker-compose logs -f +docker compose logs -f ``` Wait until you see "Application startup complete", then press Ctrl+C. **Access the API**: -- API: http://localhost:8000 -- Docs: http://localhost:8000/docs +- API: http://localhost:8050/api/v1 +- Docs: http://localhost:8050/docs + +Set `APP_PORT` in `.env` / `.env.example` to change `8050`. +For Square payment form testing, use HTTPS at `https://localhost:8443`. +Set `APP_TLS_PORT` in `.env` / `.env.example` to change `8443`. +TLS certs are auto-generated by the gateway container on first start. ## Testing the API ### 1. Register a new user ```bash -curl -X POST "http://localhost:8000/api/v1/auth/register" \ +curl -X POST "http://localhost:8050/api/v1/auth/register" \ -H "Content-Type: application/json" \ -d '{ "email": "test@example.com", @@ -32,7 +37,7 @@ curl -X POST "http://localhost:8000/api/v1/auth/register" \ ### 2. Login ```bash -curl -X POST "http://localhost:8000/api/v1/auth/login-json" \ +curl -X POST "http://localhost:8050/api/v1/auth/login-json" \ -H "Content-Type: application/json" \ -d '{ "email": "test@example.com", @@ -44,45 +49,41 @@ Save the `access_token` from the response. ### 3. Get your profile ```bash -curl -X GET "http://localhost:8000/api/v1/users/me" \ +curl -X GET "http://localhost:8050/api/v1/users/me" \ -H "Authorization: Bearer YOUR_TOKEN_HERE" ``` ### 4. List membership tiers ```bash -curl -X GET "http://localhost:8000/api/v1/tiers/" +curl -X GET "http://localhost:8050/api/v1/tiers/" ``` ## Docker Compose Commands ```bash # Start services -docker-compose up -d +docker compose up -d # Stop services -docker-compose down +docker compose down # View logs (all services) -docker-compose logs -f +docker compose logs -f # View logs (specific service) -docker-compose logs -f backend -docker-compose logs -f mysql +docker compose logs -f backend # Restart services -docker-compose restart +docker compose restart # Rebuild after code changes -docker-compose up -d --build +docker compose up -d --build # Check status -docker-compose ps +docker compose ps -# Access MySQL CLI (using environment variables) -docker exec -it membership_mysql mysql -u "${DATABASE_USER}" -p"${DATABASE_PASSWORD}" "${DATABASE_NAME}" - -# Create database backup -docker exec membership_mysql mysqldump -u "${DATABASE_USER}" -p"${DATABASE_PASSWORD}" "${DATABASE_NAME}" > backup_$(date +%Y%m%d_%H%M%S).sql +# Tail gateway logs +docker compose logs -f gateway ``` ## Default Admin Access @@ -111,33 +112,28 @@ docker exec membership_mysql mysqldump -u "${DATABASE_USER}" -p"${DATABASE_PASSW ### Check service status ```bash -docker-compose ps +docker compose ps ``` ### View all logs ```bash -docker-compose logs -f +docker compose logs -f ``` ### View backend logs only ```bash -docker-compose logs -f backend -``` - -### View MySQL logs only -```bash -docker-compose logs -f mysql +docker compose logs -f backend ``` ### Restart everything ```bash -docker-compose restart +docker compose restart ``` ### Clean start (removes all data) ```bash -docker-compose down -v -docker-compose up -d +docker compose down -v +docker compose up -d ``` ## Next Steps diff --git a/README.md b/README.md index 9089123..0da27fb 100644 --- a/README.md +++ b/README.md @@ -73,41 +73,42 @@ membership/ - SMTP2GO API key (for email notifications) - Database password (if desired) -3. **Start the services in your preferred mode**: +3. **Start the services**: ```bash - # For development (with hot reloading) - docker-compose --profile dev up -d - - # For production (optimized static files) - docker-compose --profile prod up -d + # Development mode (gateway + Vite hot reload) + docker compose up -d + + # Production static frontend mode + docker compose --profile prod up -d frontend-prod backend ``` 4. **Wait for services to be ready** (about 30 seconds for MySQL initialization): ```bash - docker-compose logs -f + docker compose logs -f ``` Press Ctrl+C when you see "Application startup complete" 5. **Access the application**: - - Frontend: http://localhost:3500 (development) or http://localhost:8080 (production) - - API: http://localhost:8000 - - API Documentation: http://localhost:8000/docs + - Frontend (HTTP): http://localhost:8050 + - Frontend (HTTPS for Square): https://localhost:8443 + - API: http://localhost:8050/api/v1 + - API Documentation: http://localhost:8050/docs + - TLS certs are generated automatically by the gateway container on first start ## Frontend Development vs Production -Choose your deployment mode by using the appropriate docker-compose file: - ### Development Mode (Vite) ```bash -docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d +docker compose up -d ``` -- Frontend served by Vite dev server on port 3500 +- Single entry point on port 8050 +- Nginx gateway routes `/api/*` to backend and all other traffic to Vite - Hot reloading and development features -- Access at: http://localhost:3500 +- Access at: http://localhost:8050 ### Production Mode (Nginx) ```bash -docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d +docker compose --profile prod up -d frontend-prod backend ``` - Frontend served by Nginx on port 8050 - Optimized static files, production-ready @@ -120,15 +121,30 @@ Set your preferred defaults in `.env`: # Deployment mode (for reference only) MODE=dev +# Single host entrypoint port +APP_PORT=8050 + +# HTTPS entrypoint port for self-signed TLS in dev +APP_TLS_PORT=8443 +DEV_CERT_CN=localhost +DEV_CERT_SANS=DNS:localhost,IP:127.0.0.1,IP:::1 + # Frontend allowed hosts (comma-separated) VITE_ALLOWED_HOSTS=sasaprod,localhost,members.sasalliance.org ``` +If you access via a LAN hostname or IP, add it to `VITE_ALLOWED_HOSTS`. +If you change `APP_PORT`, use that port instead of `8050` in URLs. +If you change `APP_TLS_PORT`, use that port for HTTPS URLs. +If you change hostnames, update `DEV_CERT_CN` and `DEV_CERT_SANS` accordingly. + ### Stopping Services ```bash # Stop all services -docker compose -f docker-compose.yml -f docker-compose.dev.yml down -docker compose -f docker-compose.yml -f docker-compose.prod.yml down +docker compose down + +# Stop production profile services +docker compose --profile prod down ``` ## Default Credentials @@ -182,29 +198,29 @@ docker compose -f docker-compose.yml -f docker-compose.prod.yml down ```bash # Start all services -docker-compose up -d +docker compose up -d # View logs (all services) -docker-compose logs -f +docker compose logs -f # View logs (specific service) -docker-compose logs -f backend -docker-compose logs -f mysql +docker compose logs -f backend +docker compose logs -f gateway # Stop services -docker-compose down +docker compose down # Stop and remove volumes (clean slate) -docker-compose down -v +docker compose down -v # Restart services -docker-compose restart +docker compose restart # Rebuild after code changes -docker-compose up -d --build +docker compose up -d --build # Check service status -docker-compose ps +docker compose ps ``` ### Database Operations @@ -242,7 +258,7 @@ sudo docker compose exec backend alembic history ## API Testing -You can use the interactive API documentation at http://localhost:8000/docs to test endpoints: +You can use the interactive API documentation at http://localhost:8050/docs to test endpoints: 1. Register a new user 2. Login to get access token @@ -253,7 +269,7 @@ Or use curl: ```bash # Register -curl -X POST "http://localhost:8000/api/v1/auth/register" \ +curl -X POST "http://localhost:8050/api/v1/auth/register" \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", @@ -263,7 +279,7 @@ curl -X POST "http://localhost:8000/api/v1/auth/register" \ }' # Login -curl -X POST "http://localhost:8000/api/v1/auth/login-json" \ +curl -X POST "http://localhost:8050/api/v1/auth/login-json" \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", @@ -271,7 +287,7 @@ curl -X POST "http://localhost:8000/api/v1/auth/login-json" \ }' # Get profile (replace TOKEN with actual token) -curl -X GET "http://localhost:8000/api/v1/users/me" \ +curl -X GET "http://localhost:8050/api/v1/users/me" \ -H "Authorization: Bearer TOKEN" ``` @@ -308,45 +324,42 @@ The system comes with three pre-configured tiers: ### Services not starting ```bash # Check status of all services -docker-compose ps +docker compose ps # View all logs -docker-compose logs +docker compose logs # View specific service logs -docker-compose logs mysql -docker-compose logs backend +docker compose logs gateway +docker compose logs backend # Restart all services -docker-compose restart +docker compose restart # Full restart with rebuild -docker-compose down -docker-compose up -d --build +docker compose down +docker compose up -d --build ``` ### Database connection issues ```bash -# Check if MySQL is healthy -docker-compose ps +# Check backend connectivity and status +docker compose ps -# View MySQL logs for errors -docker-compose logs mysql - -# Wait for MySQL to be fully ready (may take 30 seconds on first start) -docker-compose logs -f mysql +# View backend logs for DB errors +docker compose logs backend ``` ### Clean slate restart ```bash # Stop everything and remove volumes -docker-compose down -v +docker compose down -v # Start fresh -docker-compose up -d +docker compose up -d # Wait for initialization -docker-compose logs -f +docker compose logs -f ``` ## Next Steps diff --git a/docker-compose.yml b/docker-compose.yml index 50ee333..1f01bb8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -42,8 +42,8 @@ services: - ACCESS_TOKEN_EXPIRE_MINUTES=${ACCESS_TOKEN_EXPIRE_MINUTES} extra_hosts: - "host.docker.internal:host-gateway" - ports: - - "6000:8000" # Only expose backend API to host + expose: + - "8000" volumes: - ./backend/app:/app/app - ./backend/alembic:/app/alembic @@ -55,35 +55,60 @@ services: # mysql: # condition: service_healthy - # frontend: - # build: - # context: ./frontend - # dockerfile: Dockerfile - # target: development - # restart: unless-stopped - # environment: - # - VITE_HOST_CHECK=false - # - VITE_ALLOWED_HOSTS=${VITE_ALLOWED_HOSTS} - # ports: - # - "8050:3000" # Expose frontend to host - # volumes: - # - ./frontend/src:/app/src - # - ./frontend/public:/app/public - # - ./frontend/vite.config.ts:/app/vite.config.ts - # depends_on: - # - backend + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + target: development + restart: unless-stopped + env_file: + - .env + environment: + - VITE_ALLOWED_HOSTS=${VITE_ALLOWED_HOSTS} + expose: + - "3000" + volumes: + - ./frontend/src:/app/src + - ./frontend/public:/app/public + - ./frontend/vite.config.ts:/app/vite.config.ts + - ./frontend/index.html:/app/index.html + depends_on: + - backend + + gateway: + build: + context: ./docker/gateway + dockerfile: Dockerfile + restart: unless-stopped + env_file: + - .env + environment: + - DEV_CERT_CN=${DEV_CERT_CN:-localhost} + - DEV_CERT_SANS=${DEV_CERT_SANS:-DNS:localhost,IP:127.0.0.1,IP:::1} + ports: + - "${APP_PORT:-8050}:80" + - "${APP_TLS_PORT:-8443}:443" + volumes: + - ./docker/gateway/nginx.dev.conf:/etc/nginx/conf.d/default.conf:ro + - gateway_certs:/etc/nginx/certs + depends_on: + - backend + - frontend frontend-prod: - build: - context: ./frontend - dockerfile: Dockerfile - target: production - restart: unless-stopped - ports: - - "8050:80" # Nginx default port - depends_on: - - backend + build: + context: ./frontend + dockerfile: Dockerfile + target: production + restart: unless-stopped + ports: + - "${APP_PORT:-8050}:80" # Nginx default port + depends_on: + - backend + profiles: + - prod volumes: # mysql_data: uploads_data: + gateway_certs: diff --git a/docker/gateway/Dockerfile b/docker/gateway/Dockerfile new file mode 100644 index 0000000..31393df --- /dev/null +++ b/docker/gateway/Dockerfile @@ -0,0 +1,8 @@ +FROM nginx:alpine + +RUN apk add --no-cache openssl + +COPY docker-entrypoint-dev.sh /usr/local/bin/docker-entrypoint-dev.sh +RUN chmod +x /usr/local/bin/docker-entrypoint-dev.sh + +CMD ["/usr/local/bin/docker-entrypoint-dev.sh"] diff --git a/docker/gateway/docker-entrypoint-dev.sh b/docker/gateway/docker-entrypoint-dev.sh new file mode 100644 index 0000000..8b96cb7 --- /dev/null +++ b/docker/gateway/docker-entrypoint-dev.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env sh +set -eu + +CERT_DIR="/etc/nginx/certs" +CERT_FILE="$CERT_DIR/dev.crt" +KEY_FILE="$CERT_DIR/dev.key" +CERT_CN="${DEV_CERT_CN:-localhost}" +CERT_SANS="${DEV_CERT_SANS:-DNS:localhost,IP:127.0.0.1,IP:::1}" + +mkdir -p "$CERT_DIR" + +if [ ! -f "$CERT_FILE" ] || [ ! -f "$KEY_FILE" ]; then + echo "Generating self-signed TLS certificate for CN=$CERT_CN" + openssl req -x509 -nodes -newkey rsa:2048 \ + -keyout "$KEY_FILE" \ + -out "$CERT_FILE" \ + -days 365 \ + -subj "/C=GB/ST=Dev/L=Dev/O=SASA/OU=Membership/CN=$CERT_CN" \ + -addext "subjectAltName=$CERT_SANS" +fi + +exec nginx -g 'daemon off;' diff --git a/docker/gateway/nginx.dev.conf b/docker/gateway/nginx.dev.conf new file mode 100644 index 0000000..032f5cf --- /dev/null +++ b/docker/gateway/nginx.dev.conf @@ -0,0 +1,79 @@ +server { + listen 80; + server_name _; + + # Keep HTTP available in dev, but use HTTPS for Square Web Payments. + location /api/ { + proxy_pass http://backend:8000; + proxy_http_version 1.1; + 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; + } + + location ~ ^/(docs|redoc|openapi.json)$ { + proxy_pass http://backend:8000; + proxy_http_version 1.1; + 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; + } + + location / { + proxy_pass http://frontend:3000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + 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; + } +} + +server { + listen 443 ssl; + server_name _; + + ssl_certificate /etc/nginx/certs/dev.crt; + ssl_certificate_key /etc/nginx/certs/dev.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_prefer_server_ciphers off; + + # API routes to backend service + location /api/ { + proxy_pass http://backend:8000; + proxy_http_version 1.1; + 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; + } + + # FastAPI docs and schema for local development + location ~ ^/(docs|redoc|openapi.json)$ { + proxy_pass http://backend:8000; + proxy_http_version 1.1; + 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; + } + + # All other requests route to Vite dev server + location / { + proxy_pass http://frontend:3000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + 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; + } +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 43f7a67..5a3e994 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -7,7 +7,7 @@ export default defineConfig({ host: true, port: 3000, strictPort: true, - allowedHosts: process.env.VITE_ALLOWED_HOSTS ? process.env.VITE_ALLOWED_HOSTS.split(',') : ['sasaprod', 'localhost'], + allowedHosts: process.env.VITE_ALLOWED_HOSTS ? process.env.VITE_ALLOWED_HOSTS.split(',') : true, watch: { usePolling: true },