Deployment Guide
Production Deployment Guide
This guide walks you through deploying The Nom Database to a production server.
Prerequisites
Server Requirements
- Ubuntu 22.04 LTS or later (recommended)
- Minimum 2GB RAM, 2 CPU cores
- 20GB+ storage
- Public IP address
- Domain name pointed to your server
Required Software
- Docker Engine 24.0+
- Docker Compose v2.0+
- Git
- OpenSSL (for generating secrets)
Step 1: Server Setup
Install Docker and Docker Compose
# Update package index
sudo apt update && sudo apt upgrade -y
# Install dependencies
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
# Add Docker's official GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# Add Docker repository
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# Add your user to docker group
sudo usermod -aG docker $USER
newgrp docker
# Verify installation
docker --version
docker compose versionConfigure Firewall
# Allow SSH, HTTP, and HTTPS
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw statusStep 2: Clone Repository
cd /opt
sudo mkdir -p nomdb
sudo chown $USER:$USER nomdb
cd nomdb
git clone https://github.com/obermarclp/the-nom-database.git .Step 3: Configure Environment
Generate Secrets
# Generate JWT secret key
JWT_SECRET=$(openssl rand -base64 64)
echo "JWT_SECRET_KEY=$JWT_SECRET"
# Generate strong database password
DB_PASSWORD=$(openssl rand -base64 32)
echo "DB_PASSWORD=$DB_PASSWORD"Create Production Environment File
cp .env.production.example .env.productionEdit .env.production with your values:
nano .env.productionRequired Configuration:
Database:
DB_PASSWORD: Use the generated password above- Keep
DB_USER=nomdbandDB_NAME=nomdb
JWT Secret:
JWT_SECRET_KEY: Use the generated JWT secret above
Google Maps API:
GOOGLE_MAPS_API_KEY: Your production Google Maps API key- Enable Places API in Google Cloud Console
OIDC/Authentik (if using):
OIDC_ISSUER_URL: Your Authentik issuer URLOIDC_CLIENT_ID: Client ID from AuthentikOIDC_CLIENT_SECRET: Client secret from AuthentikOIDC_REDIRECT_URL: https://yourdomain.com/api/auth/oidc/callback
Domain Configuration:
ALLOWED_ORIGINS: https://yourdomain.com,https://www.yourdomain.comVITE_API_URL: https://yourdomain.com
AWS S3 (optional):
- Configure if using S3 for photo storage
- Otherwise, leave blank for local storage
Step 4: SSL/TLS Certificate Setup
Option A: Let’s Encrypt with Certbot (Recommended)
# Install Certbot
sudo apt install -y certbot
# Stop any services using port 80
docker compose -f docker-compose.prod.yml down
# Generate certificate (replace with your domain)
sudo certbot certonly --standalone -d yourdomain.com -d www.yourdomain.com
# Certificates will be in /etc/letsencrypt/live/yourdomain.com/
# Copy to nginx directory
sudo mkdir -p nginx/ssl
sudo cp /etc/letsencrypt/live/yourdomain.com/fullchain.pem nginx/ssl/
sudo cp /etc/letsencrypt/live/yourdomain.com/privkey.pem nginx/ssl/
sudo chown -R $USER:$USER nginx/sslAuto-renewal setup:
# Add renewal cron job
echo "0 3 * * * root certbot renew --quiet --deploy-hook 'cp /etc/letsencrypt/live/yourdomain.com/fullchain.pem /opt/nomdb/nginx/ssl/ && cp /etc/letsencrypt/live/yourdomain.com/privkey.pem /opt/nomdb/nginx/ssl/ && docker compose -f /opt/nomdb/docker-compose.prod.yml restart nginx'" | sudo tee -a /etc/crontab > /dev/nullOption B: Self-Signed Certificate (Development Only)
sudo mkdir -p nginx/ssl
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout nginx/ssl/privkey.pem \
-out nginx/ssl/fullchain.pem \
-subj "/C=US/ST=State/L=City/O=Organization/CN=yourdomain.com"
sudo chown -R $USER:$USER nginx/sslUpdate Nginx Configuration
Edit nginx/nginx.conf and replace all instances of yourdomain.com with your actual domain:
sed -i 's/yourdomain\.com/your-actual-domain.com/g' nginx/nginx.confStep 5: DNS Configuration
Point your domain to your server’s IP address:
A Records:
yourdomain.com→your-server-ipwww.yourdomain.com→your-server-ip
Verify DNS propagation:
dig yourdomain.com +shortStep 6: Build and Deploy
Build Images
# Build with production environment
docker compose -f docker-compose.prod.yml buildStart Services
# Start in detached mode
docker compose -f docker-compose.prod.yml up -d
# View logs
docker compose -f docker-compose.prod.yml logs -f
# Check service status
docker compose -f docker-compose.prod.yml psVerify Deployment
# Check all services are healthy
docker compose -f docker-compose.prod.yml ps
# Test health endpoint
curl https://yourdomain.com/health
# Test API
curl https://yourdomain.com/api/health
# Check nginx logs
docker compose -f docker-compose.prod.yml logs nginx
# Check backend logs
docker compose -f docker-compose.prod.yml logs backendStep 7: Post-Deployment
Create Admin User
If using local authentication:
curl -X POST https://yourdomain.com/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "admin@yourdomain.com",
"username": "admin",
"password": "YourSecurePassword123!",
"full_name": "Admin User"
}'Then manually set the user as admin in the database:
docker compose -f docker-compose.prod.yml exec db psql -U nomdb -d nomdb -c \
"UPDATE users SET is_admin = true WHERE email = 'admin@yourdomain.com';"Setup Database Backups
Create a backup cron job:
# Create backup script (see scripts/backup.sh)
chmod +x scripts/backup.sh
# Add daily backup cron job at 2 AM
echo "0 2 * * * /opt/nomdb/scripts/backup.sh" | crontab -Step 8: Monitoring and Maintenance
View Logs
# All services
docker compose -f docker-compose.prod.yml logs -f
# Specific service
docker compose -f docker-compose.prod.yml logs -f backend
docker compose -f docker-compose.prod.yml logs -f frontend
docker compose -f docker-compose.prod.yml logs -f nginx
docker compose -f docker-compose.prod.yml logs -f dbRestart Services
# Restart all
docker compose -f docker-compose.prod.yml restart
# Restart specific service
docker compose -f docker-compose.prod.yml restart backendUpdate Application
# Pull latest changes
git pull origin main
# Rebuild and restart
docker compose -f docker-compose.prod.yml build
docker compose -f docker-compose.prod.yml up -d
# Remove old images
docker image prune -fDatabase Management
# Access PostgreSQL
docker compose -f docker-compose.prod.yml exec db psql -U nomdb -d nomdb
# Backup database manually
docker compose -f docker-compose.prod.yml exec db pg_dump -U nomdb nomdb > backup_$(date +%Y%m%d_%H%M%S).sql
# Restore database
docker compose -f docker-compose.prod.yml exec -T db psql -U nomdb nomdb < backup.sqlTroubleshooting
Services Not Starting
# Check logs for errors
docker compose -f docker-compose.prod.yml logs
# Check container status
docker compose -f docker-compose.prod.yml ps
# Restart services
docker compose -f docker-compose.prod.yml restartSSL Certificate Issues
# Verify certificate files exist
ls -la nginx/ssl/
# Check nginx configuration
docker compose -f docker-compose.prod.yml exec nginx nginx -t
# Check certificate validity
openssl x509 -in nginx/ssl/fullchain.pem -text -nooutDatabase Connection Issues
# Check database is running
docker compose -f docker-compose.prod.yml exec db pg_isready -U nomdb
# Test connection
docker compose -f docker-compose.prod.yml exec db psql -U nomdb -d nomdb -c "SELECT 1;"
# Check backend environment
docker compose -f docker-compose.prod.yml exec backend env | grep DATABASE_URL502 Bad Gateway
This usually means the backend is not running or not healthy:
# Check backend health
curl http://localhost:8080/api/health
# Check backend logs
docker compose -f docker-compose.prod.yml logs backend
# Restart backend
docker compose -f docker-compose.prod.yml restart backendSecurity Checklist
- Strong database password set
- JWT secret key generated with sufficient entropy
- SSL/TLS certificate installed and valid
- Firewall configured (only 80, 443, 22 open)
- Environment files (.env.production) not committed to git
- Google Maps API key restricted by domain/IP
- OIDC client secret kept secure
- Regular database backups configured
- Container restart policies enabled
- Log rotation configured
- Rate limiting enabled in nginx
- Security headers configured
- Database only accessible from localhost
- Backend only accessible through nginx
- Admin user password is strong
Performance Optimization
Enable Docker BuildKit
export DOCKER_BUILDKIT=1
export COMPOSE_DOCKER_CLI_BUILD=1Optimize Database
docker compose -f docker-compose.prod.yml exec db psql -U nomdb -d nomdb-- Create indexes for better performance
CREATE INDEX IF NOT EXISTS idx_restaurants_created_at ON restaurants(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ratings_restaurant_id ON ratings(restaurant_id);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_sessions_refresh_token ON sessions(refresh_token);
-- Vacuum and analyze
VACUUM ANALYZE;Monitor Resource Usage
# Container stats
docker stats
# Disk usage
docker system df
# Clean up
docker system prune -a --volumesBackup and Disaster Recovery
Automated Backups
The backup script (scripts/backup.sh) runs daily and:
- Creates PostgreSQL dump
- Compresses backup
- Rotates old backups (keeps last 30 days)
- Optionally uploads to S3
Manual Backup
./scripts/backup.shRestore from Backup
# Stop services
docker compose -f docker-compose.prod.yml down
# Restore database
docker compose -f docker-compose.prod.yml up -d db
docker compose -f docker-compose.prod.yml exec -T db psql -U nomdb nomdb < backups/backup_20250115_020000.sql
# Restart all services
docker compose -f docker-compose.prod.yml up -dScaling Considerations
Horizontal Scaling
To run multiple backend instances behind nginx:
# In docker-compose.prod.yml
backend:
# ... existing config
deploy:
replicas: 3Database Connection Pooling
Already configured in backend with pgx connection pool.
CDN Integration
Consider using CloudFlare or AWS CloudFront for:
- Static asset caching
- DDoS protection
- Geographic distribution
Support and Resources
- Project Repository: https://github.com/obermarclp/the-nom-database
- Documentation: See documentation and Authentication
- Issue Tracker: GitHub Issues
License
See LICENSE file in the repository.