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

23 KiB

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

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:


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)

    dig pfosi.mifi.dev
    dig api.pfosi.mifi.dev
    

Software Requirements

# 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 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:

nano backend/data/profiles.json

Add images:

cp new-profile.jpg backend/src/images/profile/

Commit changes:

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:

cp backend/.env.example .env
nano .env

Required variables (see 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:

Update image registry URLs:

image: git.mifi.dev:12023/mifi/pfosi-looking-frontend:latest
# Change to your registry

Update domains in Traefik labels:

- "traefik.http.routers.looking-frontend.rule=Host(`pfosi.mifi.dev`)"
# Change to your domain

4. Test Locally

# 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)

# 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:

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

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:

npm run docker:build

Or build individually:

# 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:

# 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)

# 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

For enhanced security, use Docker secrets:

# 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

# From project root
npm run deploy

# Or manually
docker-compose up -d

Verify Deployment

# 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:

# 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

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

cd ~/traefik
docker network create marina-net
docker-compose up -d

🔒 SSL/TLS Certificates

Let's Encrypt Configuration

In docker-compose.yml Traefik labels:

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:

docker exec traefik cat /letsencrypt/acme.json | jq

Force certificate renewal:

# 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:
    - "--certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
    

🌐 Domain Configuration

Traefik Labels

Frontend:

labels:
  - "traefik.http.routers.looking-frontend.rule=Host(`pfosi.mifi.dev`)"
  - "traefik.http.services.looking-frontend.loadbalancer.server.port=80"

Backend:

labels:
  - "traefik.http.routers.looking-backend.rule=Host(`api.pfosi.mifi.dev`)"
  - "traefik.http.services.looking-backend.loadbalancer.server.port=3069"

Mongo Express:

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):

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)

  3. Add backup cron job:

    # /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

#!/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:

chmod +x /usr/local/bin/backup-mongodb.sh

Restore Procedure

Restore from backup:

# 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:

# 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:

# 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:

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:

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:

docker-compose logs -f

Specific service:

docker-compose logs -f looking-backend
docker-compose logs -f looking-frontend
docker-compose logs -f looking-mongo

With timestamps:

docker-compose logs -f --timestamps looking-backend

Container Status

# Check running containers
docker-compose ps

# Resource usage
docker stats

MongoDB Logs

# 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):

docker exec looking-frontend cat /var/log/nginx/access.log
docker exec looking-frontend cat /var/log/nginx/error.log

Health Checks

MongoDB:

docker exec looking-mongo mongo --eval "db.adminCommand('ping')"

Backend API:

curl https://api.pfosi.mifi.dev/profiles

Frontend:

curl https://pfosi.mifi.dev

Rollback Procedures

Quick Rollback

1. Identify previous image version:

docker images | grep pfosi-looking

2. Update docker-compose.yml:

image: git.mifi.dev:12023/mifi/pfosi-looking-backend:previous-tag

3. Redeploy:

docker-compose up -d looking-backend

Full Rollback with Database

1. Stop services:

docker-compose down

2. Restore database backup (if needed):

gunzip < /backups/mongodb/urge_previous.gz | \
  docker exec -i looking-mongo mongorestore --archive --db=urge

3. Deploy previous version:

git checkout <previous-commit>
npm run deploy

Emergency Rollback

Restore from Git:

# 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:

docker-compose logs

Verify network exists:

docker network ls | grep marina-net

# Create if missing
docker network create marina-net

Check disk space:

df -h
docker system df

SSL Certificate Issues

Domain not resolving:

dig pfosi.mifi.dev
nslookup pfosi.mifi.dev

Certificate not issued:

# 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:

# 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:

# Check environment variables
docker exec looking-backend env | grep MONGO

CORS Errors

Browser shows CORS error:

  1. Verify Traefik middleware:

    docker exec traefik cat /etc/traefik/dynamic.conf
    
  2. Check allowed origin:

    accesscontrolalloworiginlist=https://pfosi.mifi.dev
    # Must match frontend domain exactly
    
  3. Test with curl:

    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:

# Check volume exists
docker volume ls | grep api_images

# Inspect volume
docker volume inspect pfosi-looking-monorepo_api_images

Directory writable:

docker exec looking-backend ls -la /app/src/images
docker exec looking-backend touch /app/src/images/test


Need Help? Check individual component documentation or open an issue for deployment-specific problems.