Production URLs
The current production deployment:- Frontend: https://app.mcd.ppg.br
- API: https://li.mcd.ppg.br
- API Documentation: https://li.mcd.ppg.br/docs
Architecture Overview
Macondo Link Manager uses a split deployment strategy:- Frontend (Next.js): Deployed on Vercel with custom domain
- Backend (Fastify): Deployed on Railway with custom domain
- Database (PostgreSQL): Managed database on Railway
Deployment Options
Vercel + Railway
Current production setup - Recommended for most use cases
Docker Deployment
Self-hosted option using Docker containers
Vercel + Railway (Recommended)
This is the current production setup used by Macondo.Prerequisites
- Vercel account
- Railway account
- Custom domain (optional but recommended)
- Google OAuth credentials configured for production URLs
Deploy Backend to Railway
DATABASE_URL connection stringapiDATABASE_URL=${{Postgres.DATABASE_URL}}
BASE_URL=https://li.mcd.ppg.br
GOOGLE_CLIENT_ID=your_production_client_id
GOOGLE_CLIENT_SECRET=your_production_client_secret
JWT_SECRET=your_production_jwt_secret
FRONTEND_URL=https://app.mcd.ppg.br
NODE_ENV=production
NODE_VERSION=20
PORT=3333
DATABASE_URL will be automatically populated by Railway when you reference the PostgreSQL service.# Install Railway CLI
npm i -g @railway/cli
# Login
railway login
# Link to your project
railway link
# Run migrations
railway run npx prisma migrate deploy
Deploy Frontend to Vercel
- Framework: Next.js
- Root Directory:
web - Build Command:
npm run build - Output Directory:
.next
Update OAuth Redirect URIs
In Google Cloud Console:- Go to APIs & Services → Credentials
- Edit your OAuth 2.0 Client ID
- Add authorized redirect URI:
- Add authorized JavaScript origins:
Docker Deployment
For self-hosted deployments using Docker.Production Dockerfile
The API includes a multi-stage production Dockerfile:Deploy with Docker Compose
services:
postgres:
image: postgres:15-alpine
container_name: macondo_links_db_prod
restart: always
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- macondo-network
api:
build:
context: ./api
dockerfile: Dockerfile
container_name: macondo_links_api_prod
restart: always
ports:
- "3333:3333"
environment:
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
BASE_URL: ${BASE_URL}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
JWT_SECRET: ${JWT_SECRET}
FRONTEND_URL: ${FRONTEND_URL}
NODE_ENV: production
PORT: 3333
depends_on:
- postgres
networks:
- macondo-network
web:
build:
context: ./web
dockerfile: Dockerfile
container_name: macondo_links_web_prod
restart: always
ports:
- "3000:3000"
environment:
NEXT_PUBLIC_API_URL: ${BASE_URL}
depends_on:
- api
networks:
- macondo-network
volumes:
postgres_data:
networks:
macondo-network:
driver: bridge
POSTGRES_DB=macondo_links_prod
POSTGRES_USER=postgres
POSTGRES_PASSWORD=strong_production_password
BASE_URL=https://li.mcd.ppg.br
FRONTEND_URL=https://app.mcd.ppg.br
GOOGLE_CLIENT_ID=production_client_id
GOOGLE_CLIENT_SECRET=production_client_secret
JWT_SECRET=strong_random_jwt_secret
NODE_ENV=production
# Build images
docker-compose -f docker-compose.prod.yml build
# Start services
docker-compose -f docker-compose.prod.yml up -d
# Run database migrations
docker-compose -f docker-compose.prod.yml exec api npx prisma migrate deploy
# Check status
docker-compose -f docker-compose.prod.yml ps
# Nginx configuration
server {
listen 443 ssl http2;
server_name li.mcd.ppg.br;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:3333;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 443 ssl http2;
server_name app.mcd.ppg.br;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Database Migrations in Production
Railway
Run migrations using Railway CLI:Docker
Run migrations in the API container:Manual
For direct database access:Environment Variables
Critical Production Variables
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | ✅ | PostgreSQL connection string |
GOOGLE_CLIENT_ID | ✅ | Google OAuth Client ID |
GOOGLE_CLIENT_SECRET | ✅ | Google OAuth Secret |
JWT_SECRET | ✅ | JWT signing secret (must be strong) |
BASE_URL | ✅ | Public API URL (e.g., https://li.mcd.ppg.br) |
FRONTEND_URL | ✅ | Public frontend URL (e.g., https://app.mcd.ppg.br) |
NODE_ENV | ✅ | Must be production |
PORT | ✅ | API port (default: 3333) |
MAXMIND_LICENSE_KEY | ⚠️ | Optional: for GeoIP data |
Health Checks
Monitor your deployment with health checks:Set up monitoring
Recommended monitoring tools:- Railway: Built-in metrics and logs
- Vercel: Analytics and deployment logs
- UptimeRobot: External uptime monitoring
- Sentry: Error tracking (to be implemented)
Domain Configuration
DNS Records
For custom domains, configure these DNS records: Frontend (Vercel):SSL Certificates
Both Vercel and Railway automatically provision and renew SSL certificates using Let’s Encrypt. For self-hosted deployments, use:- Certbot for Let’s Encrypt certificates
- Caddy for automatic HTTPS
Rollback Strategy
Vercel
Vercel keeps all previous deployments:- Go to Deployments
- Find the stable version
- Click ⋯ → Promote to Production
Railway
Railway maintains deployment history:- Go to Deployments
- Select a previous deployment
- Click Redeploy
Docker
Tag your images for easy rollback:Database Backups
Railway Automated Backups
Railway Pro plan includes automated daily backups.Manual Backup
Create manual backups:Automated Backup Script
Performance Optimization
Backend
- Enable connection pooling in Prisma
- Use Redis for caching (future enhancement)
- Optimize database queries with indexes
- Enable compression in Fastify
Frontend
- Enable Next.js static optimization
- Use Vercel Edge Network for fast global delivery
- Implement proper caching headers
- Optimize images with Next.js Image component
Database
- Create indexes on frequently queried fields
- Use connection pooling (PgBouncer)
- Monitor slow queries
- Regular VACUUM and ANALYZE
Security Checklist
- Strong
JWT_SECRET(32+ characters, random) - HTTPS enabled on all domains
- Google OAuth restricted to corporate domain
- Environment variables not committed to Git
- Database backups configured
- Health check endpoint monitored
- CORS configured correctly
- Rate limiting enabled (future)
- SQL injection protection (Prisma ORM)
- XSS protection (Next.js built-in)
Troubleshooting
API returns 502 Bad Gateway
API returns 502 Bad Gateway
Check Railway logs:Common causes:
- Database connection failure
- Migration not run
- Environment variables missing
- Application crash on startup
CORS errors in browser
CORS errors in browser
Verify
FRONTEND_URL is set correctly in backend environment variables and matches your frontend domain exactly.OAuth redirect fails in production
OAuth redirect fails in production
Check:
- Google OAuth redirect URI matches production URL
BASE_URLandFRONTEND_URLare correct- Domain is properly configured with SSL
- Corporate domain restriction is set correctly
Database migration fails
Database migration fails
Before running migrations:
Next Steps
After deployment:- Set up monitoring and alerting
- Configure automated backups
- Review Database Management for ongoing maintenance
- Plan for scaling based on usage metrics
- Consider implementing CDN for static assets
