- Remove completed PRODUCTION_TODO_LIST.md - Consolidate deployment guides: keep comprehensive PRODUCTION_DEPLOYMENT_GUIDE.md, remove redundant PRODUCTION_DEPLOYMENT.md - Update all .env.example files to reflect current system: - Remove deprecated ADMIN_EMAIL/ADMIN_PASSWORD (now auto-generated) - Add proper documentation for all environment variables - Clarify that Umami config is optional (primary via Admin UI) - Add realistic examples for SMTP providers - Update ports to match actual defaults (3001) - Update PRODUCTION_DEPLOYMENT_GUIDE.md: - Document auto-generated admin credentials process - Add Traefik configuration section - Update security checklist with current features - Fix outdated environment variables - Add nginx proxy configuration details 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
9.0 KiB
Production Deployment Guide
This comprehensive guide addresses all production deployment scenarios and common issues.
Pre-Deployment Checklist
1. Environment Variables
Create a .env file with ALL required variables:
# CRITICAL - Must change these!
JWT_SECRET=<generate-with-openssl-rand-base64-32>
DB_PASSWORD=<strong-password>
# Application URLs (your actual domain)
ADMIN_URL=https://yourdomain.com
FRONTEND_URL=https://yourdomain.com
BACKEND_URL=https://yourdomain.com
# Database (PostgreSQL)
DATABASE_CLIENT=pg
DB_HOST=postgres # or external host
DB_PORT=5432
DB_USER=picpeak
DB_NAME=picpeak
# Email Configuration (required for notifications)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password # Use app-specific password
EMAIL_FROM=PicPeak <noreply@yourdomain.com>
# Port Configuration
PORT=3001
# Performance Tuning
DB_POOL_MIN=5
DB_POOL_MAX=25
NODE_ENV=production
LOG_LEVEL=info
# Optional: Umami Analytics (configured via Admin UI)
# UMAMI_URL=https://analytics.yourdomain.com
# UMAMI_WEBSITE_ID=your-website-id
2. Generate Secrets
# Generate JWT Secret (REQUIRED)
openssl rand -base64 32
# Generate Database Password
openssl rand -base64 24
Frontend Configuration
For production deployment behind a reverse proxy:
Frontend Environment
# frontend/.env.production
VITE_API_URL=/api # Uses relative path for reverse proxy
# Optional: Umami fallback (primary config via Admin UI)
# VITE_UMAMI_URL=https://analytics.yourdomain.com
# VITE_UMAMI_WEBSITE_ID=your-website-id
This ensures all API calls use the same domain/protocol as the frontend.
Nginx Proxy Configuration
The frontend nginx configuration already includes proper proxy settings for:
/api→ Backend API/photos→ Protected photo access/thumbnails→ Thumbnail images/uploads→ Public uploads (logos, favicons)
All static assets are served through the nginx proxy, inheriting authentication headers.
Deployment Steps
1. Initial Setup
# Clone repository
git clone https://github.com/the-luap/wedding-photo-sharing.git
cd wedding-photo-sharing
# Create required directories
mkdir -p storage/events/active storage/events/archived storage/thumbnails storage/uploads
mkdir -p data logs
mkdir -p certbot/conf certbot/www
# Set permissions (important!)
chmod -R 755 storage data logs
2. Fix Docker Volume Permissions
Create docker-compose.override.yml for local volume configuration:
version: '3.8'
services:
backend:
volumes:
- ./storage:/app/storage:delegated
- ./data:/app/data:delegated
- ./logs:/app/logs:delegated
user: "1001:1001" # nodejs user
db:
volumes:
- ./postgres-data:/var/lib/postgresql/data
3. Build and Deploy
# Build images
docker-compose -f docker-compose.prod.yml build
# Start services
docker-compose -f docker-compose.prod.yml up -d
# Check logs
docker-compose -f docker-compose.prod.yml logs -f backend
4. Initial Admin Setup
The admin user is automatically created during database migration:
# Run migrations (this creates admin user)
docker-compose -f docker-compose.prod.yml exec backend npm run migrate
# Admin credentials will be displayed in console and saved to ADMIN_CREDENTIALS.txt
# Example output:
# ========================================
# ✅ Admin user created successfully!
# ========================================
# Username: admin
# Password: SwiftEagle3847!
#
# ⚠️ IMPORTANT: Change password on first login
# ========================================
# Retrieve credentials if needed
docker-compose -f docker-compose.prod.yml exec backend cat ADMIN_CREDENTIALS.txt
Important: You MUST change the auto-generated password on first login.
5. Configure Email (if using database config)
- Login to admin panel: https://yourdomain.com/admin
- Go to Settings > Email Configuration
- Enter SMTP details
- Test email sending
Common Issues and Solutions
Issue 1: Migration Failures
Error: "relation already exists"
Solution: The safe migration runner handles this automatically. If issues persist:
# Reset migrations tracking
docker-compose -f docker-compose.prod.yml exec db psql -U picpeak -d picpeak
# In PostgreSQL:
DROP TABLE IF EXISTS migrations;
\q
# Re-run migrations
docker-compose -f docker-compose.prod.yml exec backend npm run migrate:safe
Issue 2: Permission Denied Errors
Error: "EACCES: permission denied"
Solution: Fix container permissions:
# Stop containers
docker-compose -f docker-compose.prod.yml down
# Fix permissions on host
sudo chown -R 1001:1001 storage data logs
# Restart
docker-compose -f docker-compose.prod.yml up -d
Issue 3: Database Connection Failed
Error: "no pg_hba.conf entry"
Solution: Already fixed in docker-compose.prod.yml with:
- SSL disabled for internal Docker network
- Proper authentication method (scram-sha-256)
Issue 4: Frontend Can't Connect to Backend
Error: CORS errors or connection refused
Solution: Ensure environment variables match:
- Backend:
FRONTEND_URLmust match your frontend URL - Frontend:
VITE_API_URLmust be set during build
Issue 5: Email Not Sending
Solution: Check email configuration:
# Check backend logs
docker-compose -f docker-compose.prod.yml logs backend | grep email
# Verify SMTP settings
# Gmail users: Use app password, not regular password
# Enable "Less secure app access" or use OAuth2
SSL/HTTPS Setup
Option 1: Using Traefik (Recommended)
Add these labels to your docker-compose override:
services:
frontend:
labels:
- "traefik.enable=true"
- "traefik.http.routers.picpeak.rule=Host(`yourdomain.com`)"
- "traefik.http.routers.picpeak.entrypoints=websecure"
- "traefik.http.routers.picpeak.tls.certresolver=letsencrypt"
- "traefik.http.services.picpeak.loadbalancer.server.port=80"
Option 2: Using Certbot
- Update
nginx/sites-enabled/defaultwith your domain - Run certbot:
# Initial certificate
docker-compose -f docker-compose.prod.yml run --rm certbot certonly \
--webroot --webroot-path=/var/www/certbot \
-d yourdomain.com -d www.yourdomain.com
# Auto-renewal is handled by the certbot container
Monitoring
Health Checks
# Backend health
curl http://localhost/api/health
# Database connection
docker-compose -f docker-compose.prod.yml exec backend \
psql -U picpeak -d picpeak -c "SELECT 1"
Logs
# All services
docker-compose -f docker-compose.prod.yml logs -f
# Specific service
docker-compose -f docker-compose.prod.yml logs -f backend
Backup and Restore
Backup
#!/bin/bash
# backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="./backups/$DATE"
mkdir -p $BACKUP_DIR
# Database
docker-compose -f docker-compose.prod.yml exec -T db \
pg_dump -U picpeak picpeak > $BACKUP_DIR/database.sql
# Files
tar -czf $BACKUP_DIR/storage.tar.gz storage/
echo "Backup completed: $BACKUP_DIR"
Restore
# Database
docker-compose -f docker-compose.prod.yml exec -T db \
psql -U picpeak picpeak < ./backups/20240713_120000/database.sql
# Files
tar -xzf ./backups/20240713_120000/storage.tar.gz
Production Best Practices
- Always use named volumes in production for better data persistence
- Set up monitoring with Prometheus/Grafana
- Enable backups with automated scripts
- Use a reverse proxy (Nginx) for SSL termination
- Implement rate limiting at the Nginx level
- Regular updates - Keep Docker images updated
- Log rotation - Configure log rotation for application logs
Troubleshooting Commands
# Check running containers
docker-compose -f docker-compose.prod.yml ps
# Restart a service
docker-compose -f docker-compose.prod.yml restart backend
# View real-time logs
docker-compose -f docker-compose.prod.yml logs -f --tail=100
# Execute commands in container
docker-compose -f docker-compose.prod.yml exec backend sh
# Database shell
docker-compose -f docker-compose.prod.yml exec db psql -U picpeak
# Clean restart
docker-compose -f docker-compose.prod.yml down
docker-compose -f docker-compose.prod.yml up -d
Security Checklist
- Strong JWT_SECRET (min 32 chars)
- Strong database password
- Admin password changed from auto-generated one
- SSL/HTTPS enabled
- Firewall configured (only 80/443 open)
- Regular security updates
- Backup encryption
- Access logs monitored
- Rate limiting enabled (built-in)
- File upload restrictions configured
- Password complexity requirements configured (Admin > Settings)
- Session timeout configured (default 60 min)
- Umami analytics configured (if using)
- SMTP credentials secured with app-specific password
Support
For issues not covered here:
- Check application logs
- Review error messages carefully
- Ensure all environment variables are set
- Verify file permissions
- Check Docker daemon logs