# Looking - Production Deployment Guide > **Complete guide to deploying the Looking application to production** This document covers building Docker images, configuring Traefik reverse proxy, setting up SSL certificates with Let's Encrypt, managing environment variables, and backup/restore procedures. --- ## 📋 Table of Contents - [Overview](#overview) - [Prerequisites](#prerequisites) - [Architecture](#architecture) - [Pre-Deployment Checklist](#pre-deployment-checklist) - [Building Docker Images](#building-docker-images) - [Environment Variables](#environment-variables) - [Deployment](#deployment) - [Traefik Configuration](#traefik-configuration) - [SSL/TLS Certificates](#ssltls-certificates) - [Domain Configuration](#domain-configuration) - [Database Management](#database-management) - [Backup and Restore](#backup-and-restore) - [Monitoring and Logs](#monitoring-and-logs) - [Rollback Procedures](#rollback-procedures) - [Troubleshooting](#troubleshooting) --- ## 🎯 Overview The production deployment uses **Docker Compose** with: - **Traefik** - Reverse proxy and load balancer - **Let's Encrypt** - Automatic SSL certificate management - **Docker Volumes** - Persistent storage for database and images - **Custom Networks** - Isolated backend and external traffic separation **Production Domains**: - **Frontend**: https://pfosi.mifi.dev - **Backend API**: https://api.pfosi.mifi.dev - **Database Admin**: https://mongo.pfosi.mifi.dev --- ## ✅ Prerequisites ### Server Requirements - **OS**: Linux (Ubuntu 20.04+ or Debian 11+ recommended) - **CPU**: 2+ cores - **RAM**: 4GB+ (8GB recommended) - **Disk**: 20GB+ free space - **Docker**: 20.10+ with Docker Compose v2 - **Network**: Public IP address - **Ports**: 80 (HTTP), 443 (HTTPS) open to internet ### Domain Setup 1. **Register domains**: - `pfosi.mifi.dev` (frontend) - `api.pfosi.mifi.dev` (backend) - `mongo.pfosi.mifi.dev` (admin panel) 2. **DNS Configuration**: - Point all domains to your server's public IP - Use A records: ``` pfosi.mifi.dev A api.pfosi.mifi.dev A mongo.pfosi.mifi.dev A ``` 3. **Wait for DNS propagation** (can take up to 24-48 hours, usually minutes) ```bash dig pfosi.mifi.dev dig api.pfosi.mifi.dev ``` ### Software Requirements ```bash # Install Docker and Docker Compose curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh sudo usermod -aG docker $USER # Verify installation docker --version docker compose version ``` ### Traefik Setup This deployment assumes you have **Traefik already running** with: - External network: `marina-net` - Let's Encrypt configured - Ports 80/443 exposed If you don't have Traefik, see [Traefik Configuration](#traefik-configuration) section. --- ## 🏗️ Architecture ``` ┌───────────────────────────────────────────────────────────────┐ │ Internet │ └────────────────────────────┬──────────────────────────────────┘ │ Port 80/443 │ ┌────────────────────────────▼──────────────────────────────────┐ │ Traefik (Reverse Proxy) │ │ - SSL Termination (Let's Encrypt) │ │ - HTTP → HTTPS Redirect │ │ - Domain Routing │ │ - CORS Headers │ └──────┬─────────────────┬────────────────────┬─────────────────┘ │ │ │ │ marina-net │ marina-net │ marina-net │ (external) │ (external) │ (external) │ │ │ ┌──────▼─────────┐ ┌────▼──────────┐ ┌─────▼──────────────┐ │ Frontend │ │ Backend │ │ Mongo Express │ │ (Nginx) │ │ (Express) │ │ (Web UI) │ │ │ │ │ │ │ │ Port: 80 │ │ Port: 3069 │ │ Port: 8081 │ │ (internal) │ │ (internal) │ │ (internal) │ │ │ │ │ │ │ │ Domain: │ │ Domain: │ │ Domain: │ │ pfosi. │ │ api.pfosi. │ │ mongo.pfosi. │ │ mifi.dev │ │ mifi.dev │ │ mifi.dev │ └────────────────┘ └────┬──────────┘ └─────┬──────────────┘ │ │ │ backend-net │ backend-net │ (internal) │ (internal) │ │ ┌────▼────────────────────▼────┐ │ MongoDB 4.4 │ │ │ │ Port: 27017 (internal) │ │ Database: urge │ │ │ │ Volume: mongo_data │ │ (persistent storage) │ └─────────────────────────────┘ │ ┌─────────▼──────────┐ │ api_images volume │ │ (profile/message │ │ image storage) │ └────────────────────┘ ``` ### Network Topology | Network | Type | Purpose | | --------------- | -------- | --------------------------------------------------------- | | **marina-net** | External | Traefik-to-services communication, public internet access | | **backend-net** | Internal | Services-to-MongoDB communication, isolated from internet | --- ## 📝 Pre-Deployment Checklist ### 1. Update Seed Data ⚠️ **CRITICAL**: Database is wiped and reseeded on each deployment. **Edit profile data**: ```bash nano backend/data/profiles.json ``` **Add images**: ```bash cp new-profile.jpg backend/src/images/profile/ ``` **Commit changes**: ```bash git add backend/data/profiles.json backend/src/images/ git commit -m "Update profile data for deployment" git push ``` --- ### 2. Set Environment Variables Create `.env` file in project root: ```bash cp backend/.env.example .env nano .env ``` **Required variables** (see [Environment Variables](#environment-variables) section): - JWT_SECRET - GOOGLE_MAPS_API_KEY - MAIL_HOST, MAIL_PORT, MAIL_USER, MAIL_PASS - MONGO_ROOT_USER, MONGO_ROOT_PASSWORD - MONGO_EXPRESS_USER, MONGO_EXPRESS_PASSWORD --- ### 3. Update Docker Compose **Edit [docker-compose.yml](docker-compose.yml)**: Update image registry URLs: ```yaml image: git.mifi.dev:12023/mifi/pfosi-looking-frontend:latest # Change to your registry ``` Update domains in Traefik labels: ```yaml - "traefik.http.routers.looking-frontend.rule=Host(`pfosi.mifi.dev`)" # Change to your domain ``` --- ### 4. Test Locally ```bash # Build images npm run docker:build # Test services docker-compose up -d # Verify services curl http://localhost:80 # Frontend curl http://localhost:3069/profiles # Backend ``` --- ## 🐳 Building Docker Images ### Frontend Dockerfile **File**: `app/Dockerfile` (must be created) ```dockerfile # Stage 1: Build FROM node:14-bullseye AS builder WORKDIR /app COPY package*.json ./ RUN npm install --legacy-peer-deps COPY . . RUN npm run build # Stage 2: Production FROM nginx:alpine COPY --from=builder /app/www /usr/share/nginx/html COPY nginx.conf /etc/nginx/nginx.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ``` **Create `app/nginx.conf`**: ```nginx server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /assets { expires 1y; add_header Cache-Control "public, immutable"; } } ``` --- ### Backend Dockerfile **File**: `backend/Dockerfile` ```dockerfile FROM node:14-bullseye WORKDIR /app COPY package*.json ./ RUN npm install --production COPY . . EXPOSE 3069 CMD ["node", "src/bin/www"] ``` --- ### Build Commands **Build both images**: ```bash npm run docker:build ``` **Or build individually**: ```bash # Frontend docker build -t pfosi-looking-frontend:latest ./app # Backend docker build -t pfosi-looking-backend:latest ./backend ``` --- ### Push to Registry If using a private registry: ```bash # Tag images docker tag pfosi-looking-frontend:latest git.mifi.dev:12023/mifi/pfosi-looking-frontend:latest docker tag pfosi-looking-backend:latest git.mifi.dev:12023/mifi/pfosi-looking-backend:latest # Login to registry docker login git.mifi.dev:12023 # Push images docker push git.mifi.dev:12023/mifi/pfosi-looking-frontend:latest docker push git.mifi.dev:12023/mifi/pfosi-looking-backend:latest ``` --- ## 🔐 Environment Variables ### Production .env File **Location**: Project root `.env` (NOT committed to Git) ```bash # JWT Secret (generate with: openssl rand -base64 32) JWT_SECRET=your-production-secret-min-32-chars-long # Google Maps API GOOGLE_MAPS_API_KEY=AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # SMTP Configuration MAIL_HOST=smtp.gmail.com MAIL_PORT=587 MAIL_USER=support@example.com MAIL_PASS=app-specific-password-here # MongoDB Credentials MONGO_ROOT_USER=admin MONGO_ROOT_PASSWORD=strong-password-here # Mongo Express Credentials MONGO_EXPRESS_USER=admin MONGO_EXPRESS_PASSWORD=different-strong-password ``` ### Using Docker Secrets (Recommended) For enhanced security, use Docker secrets: ```bash # Create secrets echo "strong-password" | docker secret create mongo_root_password - echo "jwt-secret" | docker secret create jwt_secret - # Update docker-compose.yml secrets: mongo_root_password: external: true jwt_secret: external: true services: looking-backend: secrets: - jwt_secret environment: - JWT_SECRET_FILE=/run/secrets/jwt_secret ``` --- ## 🚀 Deployment ### Deploy Services ```bash # From project root npm run deploy # Or manually docker-compose up -d ``` --- ### Verify Deployment ```bash # Check container status docker-compose ps # Should show: # - looking-frontend (healthy) # - looking-backend (healthy) # - looking-mongo (healthy) # - looking-mongo-express (healthy) # View logs docker-compose logs -f looking-backend docker-compose logs -f looking-frontend # Test endpoints curl https://pfosi.mifi.dev curl https://api.pfosi.mifi.dev/profiles ``` --- ### Initial Database Seed Database automatically seeds on first MongoDB startup: ```bash # View MongoDB logs to confirm seeding docker logs looking-mongo # Or manually seed docker exec -it looking-backend npm run seed ``` --- ## 🔀 Traefik Configuration If you don't have Traefik running, here's a basic setup. ### Create Traefik docker-compose.yml **File**: `~/traefik/docker-compose.yml` ```yaml version: "3.8" services: traefik: image: traefik:v2.10 container_name: traefik restart: unless-stopped command: - "--api.dashboard=true" - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--certificatesresolvers.letsencrypt.acme.email=admin@yourdomain.com" - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true" - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" ports: - "80:80" - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - traefik-letsencrypt:/letsencrypt networks: - marina-net labels: - "traefik.enable=true" - "traefik.http.routers.traefik.rule=Host(`traefik.yourdomain.com`)" - "traefik.http.routers.traefik.service=api@internal" - "traefik.http.routers.traefik.tls.certresolver=letsencrypt" - "traefik.http.middlewares.redirect-https.redirectscheme.scheme=https" - "traefik.http.middlewares.redirect-https.redirectscheme.permanent=true" - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)" - "traefik.http.routers.http-catchall.entrypoints=web" - "traefik.http.routers.http-catchall.middlewares=redirect-https" volumes: traefik-letsencrypt: networks: marina-net: external: true ``` ### Start Traefik ```bash cd ~/traefik docker network create marina-net docker-compose up -d ``` --- ## 🔒 SSL/TLS Certificates ### Let's Encrypt Configuration **In docker-compose.yml Traefik labels**: ```yaml labels: - "traefik.http.routers.looking-frontend.tls=true" - "traefik.http.routers.looking-frontend.tls.certresolver=letsencrypt" ``` **Certificate storage**: `/letsencrypt/acme.json` in Traefik container **Renewal**: Automatic (Traefik handles renewals) --- ### Certificate Troubleshooting **View certificate info**: ```bash docker exec traefik cat /letsencrypt/acme.json | jq ``` **Force certificate renewal**: ```bash # Delete acme.json and restart Traefik docker exec traefik rm /letsencrypt/acme.json docker restart traefik ``` **Let's Encrypt rate limits**: - 50 certificates per domain per week - Use staging server for testing: ```yaml - "--certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory" ``` --- ## 🌐 Domain Configuration ### Traefik Labels **Frontend**: ```yaml labels: - "traefik.http.routers.looking-frontend.rule=Host(`pfosi.mifi.dev`)" - "traefik.http.services.looking-frontend.loadbalancer.server.port=80" ``` **Backend**: ```yaml labels: - "traefik.http.routers.looking-backend.rule=Host(`api.pfosi.mifi.dev`)" - "traefik.http.services.looking-backend.loadbalancer.server.port=3069" ``` **Mongo Express**: ```yaml labels: - "traefik.http.routers.mongo-express.rule=Host(`mongo.pfosi.mifi.dev`)" - "traefik.http.services.mongo-express.loadbalancer.server.port=8081" ``` --- ### CORS Configuration **Backend CORS middleware** (already in docker-compose.yml): ```yaml labels: - "traefik.http.routers.looking-backend.middlewares=api-cors" - "traefik.http.middlewares.api-cors.headers.accesscontrolallowmethods=GET,OPTIONS,PUT,POST,DELETE,PATCH" - "traefik.http.middlewares.api-cors.headers.accesscontrolalloworiginlist=https://pfosi.mifi.dev" - "traefik.http.middlewares.api-cors.headers.accesscontrolallowcredentials=true" - "traefik.http.middlewares.api-cors.headers.accesscontrolallowheaders=Origin,X-Requested-With,Content-Type,Accept,Authorization,Cache-Control" ``` **Key settings**: - **Origin**: `https://pfosi.mifi.dev` (frontend domain only) - **Credentials**: Enabled (for JWT cookies if used) - **Methods**: Standard REST verbs + OPTIONS for preflight --- ## 💾 Database Management ### Current Workflow (Seed-Based) ⚠️ **Database is wiped and reseeded on each deployment** **Steps**: 1. Edit `backend/data/profiles.json` 2. Commit changes to Git 3. Deploy (database drops and reloads) **No user-generated content is preserved.** --- ### Future Interactive Workflow **When the app becomes interactive** (user-generated content): 1. **Disable auto-seeding**: - Remove seed data mount from docker-compose.yml - Don't run `npm run seed` after initial deployment 2. **Implement backups** (see [Backup and Restore](#backup-and-restore)) 3. **Add backup cron job**: ```bash # /etc/cron.d/mongodb-backup 0 2 * * * /usr/local/bin/backup-mongodb.sh ``` 4. **Test restore procedure** regularly --- ## 🔄 Backup and Restore ### Current State: Not Required With seed-based workflow: - All data is in `backend/data/profiles.json` (version controlled) - No user-generated content - Redeployment = fresh database **No backups needed currently.** --- ### Future: When Interactive **If app becomes interactive** with user-generated content, implement these procedures: --- ### Backup Procedure **Create backup script**: `/usr/local/bin/backup-mongodb.sh` ```bash #!/bin/bash set -e BACKUP_DIR="/backups/mongodb" DATE=$(date +%Y%m%d_%H%M%S) BACKUP_FILE="$BACKUP_DIR/urge_$DATE.gz" # Create backup directory mkdir -p "$BACKUP_DIR" # Dump database docker exec looking-mongo mongodump \ --db=urge \ --archive | gzip > "$BACKUP_FILE" # Keep last 30 days of backups find "$BACKUP_DIR" -name "urge_*.gz" -mtime +30 -delete echo "Backup completed: $BACKUP_FILE" ``` **Make executable**: ```bash chmod +x /usr/local/bin/backup-mongodb.sh ``` --- ### Restore Procedure **Restore from backup**: ```bash # List backups ls -lh /backups/mongodb/ # Restore specific backup gunzip < /backups/mongodb/urge_20240115_020000.gz | \ docker exec -i looking-mongo mongorestore --archive --db=urge ``` --- ### Volume Snapshots **Backup Docker volume directly**: ```bash # Stop services (optional, for consistency) docker-compose stop looking-backend # Create volume backup docker run --rm \ -v pfosi-looking-monorepo_mongo_data:/data \ -v /backups:/backup \ alpine tar czf /backup/mongo_data_$(date +%Y%m%d).tar.gz /data # Restart services docker-compose start looking-backend ``` **Restore volume**: ```bash # Stop all services docker-compose down # Restore volume docker run --rm \ -v pfosi-looking-monorepo_mongo_data:/data \ -v /backups:/backup \ alpine sh -c "rm -rf /data/* && tar xzf /backup/mongo_data_20240115.tar.gz -C /" # Start services docker-compose up -d ``` --- ### Backup Image Uploads **Backup `api_images` volume**: ```bash docker run --rm \ -v pfosi-looking-monorepo_api_images:/data \ -v /backups:/backup \ alpine tar czf /backup/api_images_$(date +%Y%m%d).tar.gz /data ``` **Restore**: ```bash docker run --rm \ -v pfosi-looking-monorepo_api_images:/data \ -v /backups:/backup \ alpine sh -c "rm -rf /data/* && tar xzf /backup/api_images_20240115.tar.gz -C /" ``` --- ## 📊 Monitoring and Logs ### View Logs **All services**: ```bash docker-compose logs -f ``` **Specific service**: ```bash docker-compose logs -f looking-backend docker-compose logs -f looking-frontend docker-compose logs -f looking-mongo ``` **With timestamps**: ```bash docker-compose logs -f --timestamps looking-backend ``` --- ### Container Status ```bash # Check running containers docker-compose ps # Resource usage docker stats ``` --- ### MongoDB Logs ```bash # View MongoDB logs docker logs looking-mongo # Follow logs docker logs -f looking-mongo # Search logs docker logs looking-mongo 2>&1 | grep ERROR ``` --- ### Application Logs **Backend** (Winston logger): - Console output via `docker-compose logs` - File logs: `backend/logs/` (if configured) **Nginx** (Frontend): ```bash docker exec looking-frontend cat /var/log/nginx/access.log docker exec looking-frontend cat /var/log/nginx/error.log ``` --- ### Health Checks **MongoDB**: ```bash docker exec looking-mongo mongo --eval "db.adminCommand('ping')" ``` **Backend API**: ```bash curl https://api.pfosi.mifi.dev/profiles ``` **Frontend**: ```bash curl https://pfosi.mifi.dev ``` --- ## ⏪ Rollback Procedures ### Quick Rollback **1. Identify previous image version**: ```bash docker images | grep pfosi-looking ``` **2. Update docker-compose.yml**: ```yaml image: git.mifi.dev:12023/mifi/pfosi-looking-backend:previous-tag ``` **3. Redeploy**: ```bash docker-compose up -d looking-backend ``` --- ### Full Rollback with Database **1. Stop services**: ```bash docker-compose down ``` **2. Restore database backup** (if needed): ```bash gunzip < /backups/mongodb/urge_previous.gz | \ docker exec -i looking-mongo mongorestore --archive --db=urge ``` **3. Deploy previous version**: ```bash git checkout npm run deploy ``` --- ### Emergency Rollback **Restore from Git**: ```bash # Clone fresh copy git clone PfosiLooking-monorepo-backup cd PfosiLooking-monorepo-backup git checkout # Deploy npm run docker:build npm run deploy ``` --- ## 🔧 Troubleshooting ### Services Won't Start **Check logs**: ```bash docker-compose logs ``` **Verify network exists**: ```bash docker network ls | grep marina-net # Create if missing docker network create marina-net ``` **Check disk space**: ```bash df -h docker system df ``` --- ### SSL Certificate Issues **Domain not resolving**: ```bash dig pfosi.mifi.dev nslookup pfosi.mifi.dev ``` **Certificate not issued**: ```bash # Check Traefik logs docker logs traefik | grep -i acme # Verify domain is reachable curl -I http://pfosi.mifi.dev ``` **Rate limit errors**: - Use Let's Encrypt staging server for testing - Wait 1 week if hit production limit --- ### Database Connection Errors **Backend can't reach MongoDB**: ```bash # Verify MongoDB is on backend-net docker network inspect pfosi-looking-monorepo_backend-net # Test connection from backend docker exec looking-backend nc -zv mongo 27017 ``` **Wrong credentials**: ```bash # Check environment variables docker exec looking-backend env | grep MONGO ``` --- ### CORS Errors **Browser shows CORS error**: 1. **Verify Traefik middleware**: ```bash docker exec traefik cat /etc/traefik/dynamic.conf ``` 2. **Check allowed origin**: ```yaml accesscontrolalloworiginlist=https://pfosi.mifi.dev # Must match frontend domain exactly ``` 3. **Test with curl**: ```bash curl -H "Origin: https://pfosi.mifi.dev" \ -H "Access-Control-Request-Method: POST" \ -H "Access-Control-Request-Headers: Content-Type" \ -X OPTIONS https://api.pfosi.mifi.dev/profiles ``` --- ### Image Upload Failures **Volume permissions**: ```bash # Check volume exists docker volume ls | grep api_images # Inspect volume docker volume inspect pfosi-looking-monorepo_api_images ``` **Directory writable**: ```bash docker exec looking-backend ls -la /app/src/images docker exec looking-backend touch /app/src/images/test ``` --- ## 📚 Related Documentation - **[Root README](README.md)** - Project overview - **[Backend README](backend/README.md)** - API server setup - **[Frontend README](app/README.md)** - Ionic app setup - **[DevContainer Guide](.devcontainer/README.md)** - Development environment - **[API Reference](backend/API.md)** - REST API endpoints - **[Database Schema](backend/SCHEMA.md)** - MongoDB collections --- **Need Help?** Check individual component documentation or [open an issue](your-repository-issues-url) for deployment-specific problems.