Production Deployment
This guide covers best practices and security considerations for deploying App CR to production environments.
Pre-Deployment Checklist
Before deploying to production, ensure you’ve completed these critical steps:
Application Configuration
Security Best Practices
1. Database Security
Never use the default Docker Compose credentials in production! The development credentials (user_admin/password123) are not secure and should only be used locally.
Production Database Requirements:
Use a managed database service (AWS RDS, DigitalOcean, Railway, etc.)
Generate strong passwords (20+ characters, mixed case, numbers, symbols)
Restrict database access to your application’s IP addresses
Enable SSL/TLS for database connections
Regular automated backups with point-in-time recovery
Example Secure DATABASE_URL:
DATABASE_URL = "postgresql://prod_user_a9f2:Xk9 $mP2 #vL8qR5*[email protected] :5432/app_cr_prod?sslmode=require"
2. JWT Secret Security
Generate a cryptographically secure JWT secret:
# Generate a 256-bit secret
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
Store your JWT_SECRET in your hosting platform’s environment variable manager, never in code or version control.
3. Password Hashing
The application uses bcryptjs for password hashing. Ensure this is configured properly:
const bcrypt = require ( 'bcryptjs' );
// When creating users, hash passwords
const hashedPassword = await bcrypt . hash ( password , 10 );
// When verifying, compare hashes
const isValid = await bcrypt . compare ( password , hashedPassword );
4. CORS Configuration
Restrict CORS to your frontend domain only:
const cors = require ( 'cors' );
// Development (allow all)
app . use ( cors ());
// Production (restrict to your domain)
app . use ( cors ({
origin: 'https://your-frontend-domain.com' ,
credentials: true ,
optionsSuccessStatus: 200
}));
5. Environment Variables
Never hardcode sensitive values. Use environment variables:
// ❌ Bad - hardcoded
const TOKEN = 'my-secret-key' ;
// ✅ Good - from environment
const TOKEN = process . env . JWT_SECRET ;
if ( ! TOKEN ) {
throw new Error ( 'JWT_SECRET environment variable is required' );
}
Database Migration Strategy
Running Migrations in Production
Always backup your database before running migrations in production!
Backup Database
Create a backup before any schema changes: # If using managed service, use their backup feature
# Or manually backup:
pg_dump -U user -d database > backup_ $( date +%Y%m%d_%H%M%S ) .sql
Test Migrations
Test migrations in a staging environment first: npx prisma migrate deploy --preview-feature
Deploy Migrations
Deploy migrations to production: cd backend
npx prisma migrate deploy
Verify Database
Verify the schema is correct: npx prisma db pull
npx prisma generate
Railway
Railway provides easy deployment for Node.js and PostgreSQL.
Install Railway CLI
npm install -g @railway/cli
railway login
Add PostgreSQL
Railway automatically sets the DATABASE_URL variable.
Configure Environment Variables
railway variables set JWT_SECRET=your-secret
railway variables set PORT= 3000
Heroku
Install Heroku CLI
npm install -g heroku
heroku login
Create Application
heroku create app-cr-backend
Add PostgreSQL
heroku addons:create heroku-postgresql:mini
Set Environment Variables
heroku config:set JWT_SECRET=your-secret
heroku config:set NODE_ENV=production
AWS/DigitalOcean/VPS
For custom VPS deployments:
Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
Clone Repository
git clone your-repo-url
cd your-repo
Set Environment Variables
Create /etc/systemd/system/app-cr.service or use .env file
Start with Docker Compose
docker-compose -f docker-compose.prod.yml up -d
Monitoring and Logging
Application Logging
Add structured logging to your application:
const winston = require ( 'winston' );
const logger = winston . createLogger ({
level: 'info' ,
format: winston . format . json (),
transports: [
new winston . transports . File ({ filename: 'error.log' , level: 'error' }),
new winston . transports . File ({ filename: 'combined.log' })
]
});
// Use logger instead of console.log
logger . info ( 'Server started' , { port: PORT });
logger . error ( 'Database error' , { error: error . message });
Health Check Endpoint
Add a health check endpoint for monitoring:
app . get ( '/health' , async ( req , res ) => {
try {
// Check database connection
await prisma . $queryRaw `SELECT 1` ;
res . status ( 200 ). json ({
status: 'healthy' ,
timestamp: new Date (). toISOString (),
uptime: process . uptime ()
});
} catch ( error ) {
res . status ( 503 ). json ({
status: 'unhealthy' ,
error: error . message
});
}
});
Monitoring Services
Consider using:
Sentry - Error tracking and monitoring
LogRocket - Session replay and logging
Datadog - Application performance monitoring
New Relic - Full-stack observability
1. Database Connection Pooling
Configure Prisma connection pooling:
const prisma = new PrismaClient ({
datasources: {
db: {
url: process . env . DATABASE_URL + '?connection_limit=10&pool_timeout=20'
}
}
});
2. Enable Compression
const compression = require ( 'compression' );
app . use ( compression ());
3. Rate Limiting
Protect against abuse:
const rateLimit = require ( 'express-rate-limit' );
const limiter = rateLimit ({
windowMs: 15 * 60 * 1000 , // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app . use ( '/api/' , limiter );
4. Caching
Implement caching for frequently accessed data:
const redis = require ( 'redis' );
const client = redis . createClient ({
url: process . env . REDIS_URL
});
// Cache database queries
const getCachedData = async ( key ) => {
const cached = await client . get ( key );
if ( cached ) return JSON . parse ( cached );
const data = await prisma . user . findMany ();
await client . setEx ( key , 3600 , JSON . stringify ( data ));
return data ;
};
Backup Strategy
Automated Database Backups
Most managed database services provide automated backups. Enable this feature!
Manual Backup Script
#!/bin/bash
DATE = $( date +%Y%m%d_%H%M%S )
BACKUP_DIR = "/backups"
DB_NAME = "app_cr_prod"
# Create backup
pg_dump $DATABASE_URL > $BACKUP_DIR /backup_ $DATE .sql
# Compress
gzip $BACKUP_DIR /backup_ $DATE .sql
# Upload to S3 (optional)
aws s3 cp $BACKUP_DIR /backup_ $DATE .sql.gz s3://your-bucket/backups/
# Delete old backups (keep last 7 days)
find $BACKUP_DIR -name "backup_*.sql.gz" -mtime +7 -delete
SSL/TLS Configuration
Always use HTTPS in production:
Using Reverse Proxy (Nginx)
server {
listen 443 ssl http2;
server_name api.yourdomain.com;
ssl_certificate /etc/ssl/certs/cert.pem;
ssl_certificate_key /etc/ssl/private/key.pem;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1 ;
proxy_set_header Upgrade $ http_upgrade ;
proxy_set_header Connection 'upgrade' ;
proxy_set_header Host $ host ;
proxy_cache_bypass $ http_upgrade ;
}
}
Rollback Strategy
Prepare for potential issues:
Keep previous version available : Don’t delete old deployments immediately
Database migrations : Create down migrations for schema changes
Feature flags : Use feature flags to disable new features quickly
Blue-Green deployment : Maintain two production environments
Production Environment Variables
Complete list of production environment variables:
# Required
DATABASE_URL = "postgresql://user:password@host:5432/database?sslmode=require"
JWT_SECRET = "your-256-bit-secret"
PORT = 3000
NODE_ENV = production
# Optional (Recommended)
CORS_ORIGIN = "https://your-frontend.com"
LOG_LEVEL = info
RATE_LIMIT_MAX = 100
RATE_LIMIT_WINDOW_MS = 900000
# Monitoring (Optional)
SENTRY_DSN = "https://your-sentry-dsn"
REDIS_URL = "redis://user:password@host:6379"
Next Steps
Docker Setup Review Docker configuration options
Environment Variables Configure your environment variables