This guide provides step-by-step instructions for deploying the Grocery List application to production.
Before deploying to production, ensure you have:
- Production database (PostgreSQL) provisioned
- Domain names configured (frontend and API)
- SSL/TLS certificates set up
- Email service provider account (SendGrid, AWS SES, etc.)
- Hosting platform account configured
Generate all required secrets using OpenSSL:
# Generate JWT Access Secret
openssl rand -base64 64
# Generate JWT Refresh Secret (use different output)
openssl rand -base64 64
# Generate Zero Auth Secret
openssl rand -base64 64# Install web-push CLI globally
npm install -g web-push
# Generate VAPID keys
web-push generate-vapid-keysSave both the public and private keys. You'll need both for configuration.
- Copy
.env.productionfile (already created) - Replace all
TODO-REPLACE-WITH-*placeholders with actual values - Replace all
REPLACE_ME_*secrets with generated values from Step 1 and 2 - Never commit this file to git (already in .gitignore)
# Database
DATABASE_URL=postgresql://user:password@host:5432/database?sslmode=require
# JWT Secrets (use generated values from Step 1)
JWT_ACCESS_SECRET=<your-generated-access-secret>
JWT_REFRESH_SECRET=<your-generated-refresh-secret>
# Zero Cache
ZERO_AUTH_SECRET=<your-generated-zero-secret>
ZERO_REPLICA_FILE=/var/lib/grocery-app/zero-replica.db # Persistent storage!
# VAPID (use generated values from Step 2)
VAPID_PUBLIC_KEY=<your-vapid-public-key>
VAPID_PRIVATE_KEY=<your-vapid-private-key>
VAPID_SUBJECT=mailto:admin@yourdomain.com
# URLs
VITE_API_URL=https://api.yourdomain.com
VITE_ZERO_SERVER=https://sync.yourdomain.com
CORS_ORIGIN=https://yourdomain.com
FRONTEND_URL=https://yourdomain.com# Environment
NODE_ENV=production
# Security
BCRYPT_ROUNDS=12
TRUST_PROXY=true
# Features
VITE_AUTH_ENABLED=true# Connect to your PostgreSQL server
psql -h your-db-host -U postgres
# Create database and user
CREATE DATABASE grocery_db_production;
CREATE USER grocery_prod WITH ENCRYPTED PASSWORD 'your-secure-password';
GRANT ALL PRIVILEGES ON DATABASE grocery_db_production TO grocery_prod;# Set DATABASE_URL environment variable
export DATABASE_URL="postgresql://grocery_prod:password@host:5432/grocery_db_production?sslmode=require"
# Run migrations
npm run migrate# Connect and verify tables
psql "$DATABASE_URL"
# List tables
\dt
# Expected tables:
# - users
# - refresh_tokens
# - failed_login_attempts
# - push_subscriptions
# - (zero-cache tables)- Sign up at https://sendgrid.com
- Create API key with "Mail Send" permissions
- Verify sender email address
- Update code in
server/utils/email.tsto use SendGrid:
import sgMail from '@sendgrid/mail';
sgMail.setApiKey(process.env.EMAIL_API_KEY!);
export async function sendEmail(options: EmailOptions): Promise<boolean> {
try {
await sgMail.send({
from: process.env.EMAIL_FROM!,
to: options.to,
subject: options.subject,
text: options.text,
html: options.html,
});
return true;
} catch (error) {
console.error('Email send failed:', error);
return false;
}
}- Enable AWS SES in your AWS account
- Verify domain and sender email
- Get AWS credentials (Access Key ID and Secret)
- Install AWS SDK:
npm install @aws-sdk/client-ses - Update
server/utils/email.tswith SES implementation
Before going live, verify:
- All secrets are securely generated (64+ characters)
- JWT secrets are different from each other
- Database password is strong and unique
- SSL/TLS certificates are valid and not expiring soon
- CORS is restricted to production domains only
- Rate limiting is configured appropriately
- TRUST_PROXY is set to true if behind proxy
- Debug logging is disabled (DEBUG_DB=false)
- NODE_ENV is set to "production"
- Default/development passwords are changed
- .env.production is in .gitignore
- Secrets are stored in secrets manager (not just env file)
# Set environment variables
heroku config:set NODE_ENV=production
heroku config:set DATABASE_URL="your-database-url"
heroku config:set JWT_ACCESS_SECRET="your-secret"
# ... (set all other variables)
# Deploy
git push heroku main
# Run migrations
heroku run npm run migrate- Create new app from GitHub repository
- Configure environment variables in dashboard
- Set build and run commands:
- Build:
npm run build - Run:
npm start
- Build:
- Configure health check endpoint:
/api/health - Deploy
# Initialize EB
eb init
# Create environment
eb create production-env
# Set environment variables
eb setenv NODE_ENV=production DATABASE_URL="your-url" JWT_ACCESS_SECRET="your-secret"
# Deploy
eb deploy# Build Docker image
docker build -t grocery-list:latest .
# Push to registry
docker tag grocery-list:latest your-registry/grocery-list:latest
docker push your-registry/grocery-list:latest
# Deploy to Kubernetes
kubectl apply -f k8s/production/
# Set secrets
kubectl create secret generic grocery-secrets \
--from-literal=jwt-access-secret='your-secret' \
--from-literal=jwt-refresh-secret='your-secret' \
--from-literal=database-url='your-url'The zero-cache server requires special attention:
Ensure ZERO_REPLICA_FILE points to persistent storage:
# NOT THIS (temporary storage):
ZERO_REPLICA_FILE=/tmp/zero-replica.db
# USE THIS (persistent storage):
ZERO_REPLICA_FILE=/var/lib/grocery-app/zero-replica.db# Create directory with proper permissions
sudo mkdir -p /var/lib/grocery-app
sudo chown appuser:appuser /var/lib/grocery-app
sudo chmod 755 /var/lib/grocery-app# Set up daily backups of zero-replica.db
0 2 * * * /usr/local/bin/backup-zero-replica.shThe application should expose a health check endpoint:
// server/index.ts
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});- Uptime Monitoring: UptimeRobot, Pingdom
- Error Tracking: Sentry, Rollbar
- APM: New Relic, Datadog
- Logs: Loggly, Papertrail, CloudWatch
- API response times
- Error rates (4xx, 5xx)
- Database connection pool usage
- Memory and CPU usage
- Rate limit violations
- Failed authentication attempts
- SSL certificate expiration (30 days warning)
# Test API endpoints
curl https://api.yourdomain.com/api/health
# Test authentication
curl -X POST https://api.yourdomain.com/api/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"Test123!","name":"Test User"}'Use tools like Apache Bench, Artillery, or k6:
# Install Artillery
npm install -g artillery
# Run load test
artillery quick --count 100 --num 10 https://api.yourdomain.com/api/health# Check for security vulnerabilities
npm audit
# SSL/TLS test
curl -I https://api.yourdomain.com
# Check headers
curl -I https://api.yourdomain.com/api/health | grep -i "strict-transport-security"- Frontend loads correctly
- API responds to health checks
- Authentication works (register, login, refresh)
- Database connections are stable
- Zero-cache sync is working
- Push notifications work
- Email sending works
- SSL certificates are valid
- CORS is properly configured
Watch logs for the first 24 hours:
# Heroku
heroku logs --tail
# Docker/K8s
kubectl logs -f deployment/grocery-list
# DigitalOcean
doctl apps logs your-app-id --followConfigure alerts for:
- Application errors (error rate > 5%)
- High response times (p95 > 1s)
- Database connection failures
- SSL certificate expiration (< 30 days)
- Memory usage (> 80%)
- Failed authentication spike
If issues occur, rollback immediately:
heroku rollbackdoctl apps deployment list your-app-id
doctl apps deployment rollback your-app-id deployment-idkubectl rollout undo deployment/grocery-list# Test connection
psql "$DATABASE_URL"
# Check SSL mode
echo $DATABASE_URL | grep sslmode
# Verify firewall/security groups- Verify JWT_ACCESS_SECRET is set correctly
- Check token expiration settings
- Ensure system clocks are synchronized
- Verify CORS_ORIGIN includes your frontend domain
- Check for trailing slashes in URLs
- Ensure protocol (https://) matches
- Check TRUST_PROXY setting if behind proxy
- Verify IP extraction is working correctly
- Consider using Redis for multi-instance deployments
- Daily: Monitor error logs and metrics
- Weekly: Review security alerts, check SSL expiration
- Monthly: Rotate secrets (if policy requires), review access logs
- Quarterly: Security audit, dependency updates, load testing
# Check for updates
npm outdated
# Update dependencies
npm update
# Check for security vulnerabilities
npm audit fixWhen rotating secrets:
- Generate new secrets
- Update in secrets manager
- Deploy new configuration
- Monitor for issues
- Invalidate old secrets after verification
For production support:
- Check application logs first
- Review monitoring dashboards
- Consult this deployment guide
- Check GitHub issues
- Contact team lead for critical issues
- PostgreSQL Production Checklist
- Node.js Production Best Practices
- OWASP Security Guidelines
- JWT Best Practices
Last Updated: 2025-10-26 Version: 1.0