Files
looking-monorepo/DEPLOYMENT.md
2025-12-28 13:52:25 -03:00

1079 lines
23 KiB
Markdown

# 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 <server-ip>
api.pfosi.mifi.dev A <server-ip>
mongo.pfosi.mifi.dev A <server-ip>
```
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 <previous-commit>
npm run deploy
```
---
### Emergency Rollback
**Restore from Git**:
```bash
# Clone fresh copy
git clone <repository-url> PfosiLooking-monorepo-backup
cd PfosiLooking-monorepo-backup
git checkout <known-good-commit>
# 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.