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
- Prerequisites
- Architecture
- Pre-Deployment Checklist
- Building Docker Images
- Environment Variables
- Deployment
- Traefik Configuration
- SSL/TLS Certificates
- Domain Configuration
- Database Management
- Backup and Restore
- Monitoring and Logs
- Rollback Procedures
- 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
-
Register domains:
pfosi.mifi.dev(frontend)api.pfosi.mifi.dev(backend)mongo.pfosi.mifi.dev(admin panel)
-
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>
-
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
Using Docker Secrets (Recommended)
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:
- Edit
backend/data/profiles.json - Commit changes to Git
- Deploy (database drops and reloads)
No user-generated content is preserved.
Future Interactive Workflow
When the app becomes interactive (user-generated content):
-
Disable auto-seeding:
- Remove seed data mount from docker-compose.yml
- Don't run
npm run seedafter initial deployment
-
Implement backups (see Backup and Restore)
-
Add backup cron job:
# /etc/cron.d/mongodb-backup 0 2 * * * /usr/local/bin/backup-mongodb.sh -
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:
-
Verify Traefik middleware:
docker exec traefik cat /etc/traefik/dynamic.conf -
Check allowed origin:
accesscontrolalloworiginlist=https://pfosi.mifi.dev # Must match frontend domain exactly -
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
📚 Related Documentation
- Root README - Project overview
- Backend README - API server setup
- Frontend README - Ionic app setup
- DevContainer Guide - Development environment
- API Reference - REST API endpoints
- Database Schema - MongoDB collections
Need Help? Check individual component documentation or open an issue for deployment-specific problems.