Appearance
Environment Management
This document covers environment URLs, infrastructure details, configurations, and management practices.
Environment URLs
Development
- Branch:
develop - Web App: https://dev-app.jubiloop.ca
- API: https://dev-api.jubiloop.ca
- Marketing: https://dev.jubiloop.ca
- Infrastructure: Shared droplet with QA
QA
- Branch:
qa - Web App: https://qa-app.jubiloop.ca
- API: https://qa-api.jubiloop.ca
- Marketing: https://qa.jubiloop.ca
- Infrastructure: Shared droplet with Dev
Production
- Branch:
main - Web App: https://app.jubiloop.ca (Protected with Zero Trust temporarily)
- API: https://api.jubiloop.ca
- Marketing: https://jubiloop.ca (Public)
- Marketing (www): https://www.jubiloop.ca (Public)
- Infrastructure: Dedicated droplet
Infrastructure Costs
Monthly Breakdown
- DigitalOcean Droplets: $12 total
- Dev/QA shared: $6/month (s-1vcpu-1gb)
- Production: $6/month (s-1vcpu-1gb)
- DigitalOcean Managed PostgreSQL: ~$15/month (db-s-1vcpu-1gb, production only)
- Cloudflare: $0 (free tier)
- Domains: ~$2/month
- Total: ~$29/month
Droplet Specifications
- Size: 1 vCPU, 2GB RAM, 50GB SSD
- OS: Ubuntu 22.04 LTS
- Region: Toronto (tor1)
Service Locations
Backend (DigitalOcean)
- AdonisJS API
- Redis cache
- PostgreSQL (dev/qa only - local container)
- Caddy proxy
Frontend (Cloudflare Pages)
- React web app
- Next.js marketing site
Database
Development/QA
- Local PostgreSQL container
- Separate databases per environment
Production (DigitalOcean Managed PostgreSQL)
- DigitalOcean Managed PostgreSQL 16 (
db-s-1vcpu-1gb, region:tor1) - Automatic daily backups with 7-day retention
- PgBouncer connection pooling (transaction mode, pool size 14)
- Firewall rules restrict access to production droplet only
Environment Variables
All environment configuration is managed through env.deploy.yml:
yaml
# Common pattern for all environments
{ ENV }:
server:
PORT: 3333
DATABASE_URL: from_secrets # Becomes {ENV}_SERVER_DATABASE_URL
NODE_ENV: '{environment}'
webapp:
API_URL: 'https://{env}-api.jubiloop.ca'Environment-Specific Settings
Development:
- Debug logging enabled
- Relaxed rate limits (1000/hour)
- CORS allows localhost
- Verbose error messages
QA:
- Production-like settings
- Standard rate limits (500/hour)
- CORS restricted to QA domains
- Structured error logging
Production:
- Minimal logging
- Strict rate limits (100/hour)
- CORS limited to production domains
- Generic error messages
- DigitalOcean Managed PostgreSQL connection string
- Backups enabled on droplet
External Service Configuration
Payment Processing (Stripe)
| Environment | Mode | Webhook Endpoint |
|---|---|---|
| Development | Test mode | dev-api.jubiloop.ca/webhooks/stripe |
| QA | Test mode | qa-api.jubiloop.ca/webhooks/stripe |
| Production | Live mode | api.jubiloop.ca/webhooks/stripe |
Email Service
The platform uses a comprehensive Email Infrastructure with:
| Environment | Resend (Transactional) | Brevo (Marketing) |
|---|---|---|
| Development | Sandbox mode | Test lists |
| QA | Limited sending | Sandbox campaigns |
| Production | Full sending | Live campaigns |
File Storage (Cloudflare R2)
Not yet implemented
Cloudflare R2 is planned for application file storage (photos, documents), but no buckets, upload logic, or signed URL infrastructure exists in the codebase yet. R2 is currently used only for Terraform state storage (jubiloop-terraform-state bucket).
Database Management
Development & QA
- Shared PostgreSQL container on same droplet
- Separate databases:
jubiloop_devandjubiloop_qa - Can be reset anytime
- Test data seeding scripts available
Production
- DigitalOcean Managed PostgreSQL 16 (
db-s-1vcpu-1gb) - Automated daily backups with 7-day retention
- Point-in-time recovery
- PgBouncer connection pooling (transaction mode, pool size 14)
- Firewall rules restrict access to production droplet only
Environment Promotion
Development → QA
- Code Freeze: Feature complete on
develop - Create PR: From
developtoqa - Automated Checks:
- Tests must pass
- Linting must pass
- Build must succeed
- Manual Review: Code review required
- Merge: Triggers automatic deployment
QA → Production
- QA Sign-off: All test cases passed
- Create PR: From
qatomain - Final Checks:
- No critical bugs
- Performance acceptable
- Security scan passed
- Approval: Requires admin approval
- Deploy: Merge triggers deployment
- Verify: Post-deployment checks
Rollback Procedures
Quick Rollback
No commit SHA input exists
The deploy workflows do not accept a commit SHA. You cannot target a specific previous commit directly from the Actions UI. The correct rollback method is a git revert.
Option A — Git revert (recommended, keeps full history):
bash
# Revert the bad commit on the relevant branch
git revert <bad-commit-sha>
git push origin main # or develop/qa
# This triggers automatic deployment of the reverted commit.Option B — Manual re-trigger from the Actions UI (re-deploys HEAD):
If the branch is already in a good state (e.g. the bad commit was already reverted by someone else):
- Go to the Actions tab in GitHub
- Select the relevant workflow: Deploy Server, Deploy Web App, or Deploy Marketing Site
- Click Run workflow → select the branch → click Run workflow
This deploys the current HEAD of the selected branch — it does not accept a commit SHA input.
Each app has its own deploy workflow. If you need to roll back all three (server + webapp + marketing), you must trigger each one separately.
Database Rollback
For database changes:
- Migrations are reversible
- Run rollback migration
- Deploy previous code version
Environment Isolation
Network Level
- No cross-environment communication
- Separate Redis instances
- Independent service deployments
Data Level
- No production data in dev/QA
- Separate secret keys
- Different API credentials
Access Control
- Production app requires Zero Trust auth (temporary)
- Production marketing site is public
- QA requires Zero Trust auth for all frontend apps
- Dev requires Zero Trust auth for all frontend apps
- API endpoints protected by session auth (not Zero Trust)
Monitoring & Observability
What exists today
| Capability | Detail |
|---|---|
| Health endpoint | GET /health — checks disk space, memory heap, and PostgreSQL connectivity. Returns sanitized { status, isHealthy } by default; pass x-monitoring-secret header for full diagnostic report. |
| Docker healthchecks | All server containers poll http://localhost:3333/health every 30s. Containers are automatically restarted if unhealthy. |
| CI deployment verification | GitHub Actions curls /health after every server deployment (3 retries). Deploy fails if the endpoint doesn't return healthy. |
| Deployment failure alerts | GitHub Actions posts a PR comment when a deployment job fails. No other alert channel is configured. |
| DigitalOcean metrics agent | monitoring = true on all droplets — provides basic CPU, memory, disk, and bandwidth graphs in the DO console. No alert policies are defined. |
| Docker log rotation | json-file driver, 10MB max per file, 3 files retained. View via docker compose logs. |
Not yet implemented
- Error tracking — Sentry is planned but not installed (
SENTRY_DSNis commented out inenv.deploy.yml) - Uptime monitoring — no external service configured (no UptimeRobot, Pingdom, Better Uptime, etc.)
- Runtime alerts — no Slack, PagerDuty, email, or webhook notifications for errors or downtime
- APM / performance tracking — no Datadog, New Relic, or OpenTelemetry
- Metrics dashboards — no Prometheus or Grafana
Common Tasks
Refresh Dev/QA Database
bash
# SSH to dev-qa server
ssh deploy@dev-qa.jubiloop.ca
# Backup current data (optional)
docker exec postgres pg_dump jubiloop_dev > backup.sql
# Reset database
docker exec postgres psql -c "DROP DATABASE jubiloop_dev;"
docker exec postgres psql -c "CREATE DATABASE jubiloop_dev;"
# Run migrations
docker exec server npm run migration:runCheck Environment Health
bash
# Check all services
curl https://dev-api.jubiloop.ca/health
curl https://qa-api.jubiloop.ca/health
curl https://api.jubiloop.ca/healthView Logs
bash
# Development/QA
ssh deploy@dev-qa.jubiloop.ca
docker logs server-dev -f
docker logs server-qa -f
# Production
ssh deploy@<production-ip> # Get IP from terraform output
docker logs server -fTroubleshooting
Environment Not Deploying
- Check GitHub Actions for errors
- Verify branch protection rules
- Check secrets are set correctly
- Ensure Docker registry is accessible
Wrong Environment Variables
- Check
env.deploy.ymlis correct - Run
npm run generate:envlocally - Verify GitHub Secrets match
- Restart containers after changes
Database Connection Issues
- Check DATABASE_URL is correct
- Verify firewall rules
- Check connection pool limits
- Review PostgreSQL logs