Appearance
Environments
This page records environment addresses, branch mapping, isolation, current configured sizing, and estimated costs. Deployment procedure belongs in CI/CD Pipeline.
URLs And Branches
| Environment | Branch | Webapp | API | Marketing |
|---|---|---|---|---|
| Development | develop | https://dev-app.jubiloop.ca | https://dev-api.jubiloop.ca | https://dev.jubiloop.ca |
| QA | qa | https://qa-app.jubiloop.ca | https://qa-api.jubiloop.ca | https://qa.jubiloop.ca |
| Production | main | https://app.jubiloop.ca | https://api.jubiloop.ca | https://jubiloop.ca and https://www.jubiloop.ca |
Development and QA frontends are protected by Cloudflare Access. The production webapp is also protected during pre-launch. Production marketing is public. API domains are not behind Access; the API applies its own session authentication, authorization, CORS, and throttling.
Current Infrastructure
| Environment | Backend compute | PostgreSQL | Redis | Frontends |
|---|---|---|---|---|
| Development | Shared dev/QA DigitalOcean droplet | Dedicated container | Dedicated container | Separate Cloudflare deployments |
| QA | Shared dev/QA DigitalOcean droplet | Dedicated container | Dedicated container | Separate Cloudflare deployments |
| Production | Dedicated DigitalOcean droplet | DigitalOcean Managed PostgreSQL 16 | Container on API droplet | Separate Cloudflare deployments |
Terraform defaults both droplets to s-1vcpu-1gb, but DEV_QA_DROPLET_SIZE and PROD_DROPLET_SIZE override those defaults through GitHub variables. Do not infer RAM or disk from the default slug in an operational decision. Read the current Terraform plan and provider console. The production database is currently configured as db-s-1vcpu-1gb in Terraform with a PgBouncer transaction pool of 14 connections.
Isolation And Persistence
- Dev and QA have separate API, PostgreSQL, Redis, Docker volumes, credentials, and application deployments even though they share a host.
- Production uses separate compute, credentials, Redis, and managed PostgreSQL.
- Dev/QA PostgreSQL and Redis volumes are backed by the shared droplet's storage. Droplet backups are disabled and no scheduled database dump job is configured.
- Production droplet backups are enabled. Production database backup and recovery are managed by DigitalOcean; verify the currently available recovery point before relying on it.
- Production data must not be copied into development or QA.
- R2 currently stores Terraform state only. Application file storage is not implemented.
Redis is not a general application-query cache today. The server uses it for Better Auth secondary storage, Redis-backed AdonisJS sessions, and rate-limiter counters. Current request limits come from apps/server/start/limiter.ts; see API rate limiting rather than maintaining environment-tier values here.
Current Cost Reference
The checked-in Terraform defaults represent two small droplets and one small managed PostgreSQL cluster. Provider prices, configured droplet-size variables, domains, and Cloudflare usage can change independently. Before budgeting, use the current Terraform plan plus the DigitalOcean and Cloudflare billing consoles; do not rely on a fixed total in this page.
Promotion
| Promotion | Pull request | Result after merge |
|---|---|---|
| Development to QA | develop to qa | Matching changed applications deploy to QA |
| QA to production | qa to main | Matching changed applications deploy to production |
Release and application workflows are separate. See Pull Request Guidelines and CI/CD Pipeline for checks and triggers.
Rollback
Deployment workflows do not accept an arbitrary commit SHA and do not roll back automatically.
- Revert the bad commit on the environment branch.
- Push the revert and let path-matched workflows run.
- Manually dispatch any required application workflow if its path filter does not run.
- Verify application health and logs.
A manual workflow dispatch redeploys the selected branch HEAD. Server, webapp, and marketing have separate workflows, so recover each affected application explicitly. For database changes, prefer a forward repair migration. Restore production data only from a verified recovery point under an approved recovery plan.
Manual SSH Access
Set up a local SSH alias once for each server you need to access:
- Sign in to DigitalOcean and open Droplets.
- Select the shared dev/QA droplet or the separate production droplet.
- Copy its public IP address. If it has an assigned Reserved IP, use that address.
- Add an entry to
~/.ssh/configon your computer. Replace the IP and private-key path with the values for that server:
text
Host jubiloop-dev-qa
HostName <dev-qa-droplet-ip>
User deploy
IdentityFile ~/.ssh/<your-dev-qa-private-key>The Host name is a local alias, not a DNS record. If you already use an alias such as dev-qa.jubiloop.ca, keep it and substitute that name in the commands below. For production, add a separate entry with its own alias, IP address, and private key. Update the entry if the server's IP changes.
Connect from your computer:
bash
ssh jubiloop-dev-qaManual SSH access does not require initializing Terraform. Automated deployments continue to read their target from Terraform state.
Dev/QA Database Reset Runbook
Dev/QA has no scheduled backup. Stop writes, create and export a non-empty dump, then reset only the intended environment.
After completing Manual SSH Access, connect from your computer:
bash
ssh jubiloop-dev-qaThen run on the droplet:
bash
cd /opt/jubiloop/dev-qa-docker-compose
docker compose stop server-dev
backup="$HOME/jubiloop-dev-$(date -u +%Y%m%dT%H%M%SZ).dump"
if ! docker compose exec -T postgres-dev sh -c \
'PGPASSWORD="$POSTGRES_PASSWORD" pg_dump --format=custom --username="$POSTGRES_USER" --dbname="$POSTGRES_DB"' \
> "$backup" || ! test -s "$backup"; then
echo "Backup failed; database reset aborted."
rm -f "$backup"
docker compose start server-dev
exit 1
fi
echo "Backup: $backup"
sha256sum "$backup"Keep this SSH session open. In a second terminal on your computer, copy the printed backup path and verify its checksum matches the droplet's output:
bash
scp "jubiloop-dev-qa:/home/deploy/jubiloop-dev-<timestamp>.dump" ./jubiloop-dev-backup.dump
shasum -a 256 ./jubiloop-dev-backup.dumpReplace the remote path with the exact printed path. If the copy fails or checksums differ, stop; do not reset the database. Only after verifying the off-host copy, return to the original SSH session and run:
bash
if ! docker compose exec -T postgres-dev sh -c \
'PGPASSWORD="$POSTGRES_PASSWORD" dropdb --username="$POSTGRES_USER" --force "$POSTGRES_DB" &&
PGPASSWORD="$POSTGRES_PASSWORD" createdb --username="$POSTGRES_USER" "$POSTGRES_DB"'; then
echo "Database recreation failed; server remains stopped. Backup retained at $backup."
exit 1
fi
docker compose start server-dev
docker compose logs --tail=100 server-dev
curl --fail https://dev-api.jubiloop.ca/healthUse server-qa and postgres-qa for QA, with a QA-labelled backup filename. Starting the server container reruns its entrypoint and applies migrations.
Operations
bash
# Public health checks
curl --fail https://dev-api.jubiloop.ca/health
curl --fail https://qa-api.jubiloop.ca/health
curl --fail https://api.jubiloop.ca/healthFor dev/QA logs, complete Manual SSH Access, then connect from your computer:
bash
ssh jubiloop-dev-qaOn the droplet, read the logs:
bash
cd /opt/jubiloop/dev-qa-docker-compose
docker compose logs -f server-dev server-qaThe public health response is sanitized. Authorized monitoring can send x-monitoring-secret for the detailed report. Deployment workflows also check health. DigitalOcean supplies basic host metrics; no external uptime monitor, APM, or runtime alerting service is configured.