# Deployment and upgrade

## Prepare

Keep existing `.env`, database and uploads. Save an encrypted database dump and S3 object/version backup; rehearse restoration into a DIFFERENT staging database before upgrading. Do not run `down -v`, reset or demo seed on a real stack.

Use Node 22, Docker Compose and the standalone `docker-compose.production.yml` (do not merge it with development YAML). Create a new environment file only on a new installation. Set a random URL-safe `POSTGRES_PASSWORD`, random >=32-character JWT/encryption/IP secrets, real HTTPS PUBLIC_SITE_URL/WEB_URL/ADMIN_URL/API_PUBLIC_URL, explicit HTTPS CORS_ORIGINS, SMTP credentials/from address, private S3 bucket/region/endpoint credentials. Configure Stripe price IDs/webhook signing secret only when billing is enabled; configure Search Console only with an authorized property/service account. Never use example/test values in production.

Run `docker compose -f docker-compose.production.yml config --quiet` without printing rendered secrets. Build and start with `docker compose -f docker-compose.production.yml up --build -d`. Migration startup applies forward migrations only; production never seeds. The API and workers wait for healthy PostgreSQL, Redis and ClamAV. ClamAV needs signature download time/resources and outbound access. Web/admin browser-facing domain values are build arguments: rebuild images when domains change.

Only web/admin/API ports bind to loopback. Put a TLS reverse proxy in front, use the configured public domains, set trusted proxy hops for that topology, and keep DB/cache/scanner private. SMTP/S3/Stripe/Google need outbound connectivity. Do not make the entire service network internal and block these integrations.

Bootstrap the FIRST owner using `docker compose -f docker-compose.production.yml run --rm -e ADMIN_OWNER_EMAIL=your-address@example.com api npm run owner:bootstrap`. Record the generated one-time password securely, change it at first sign-in, then enrol MFA. Bootstrap refuses when an active owner exists. Existing owners continue using their accounts. Invite other staff with least privilege.

## Verify and operate

Check container health, administrator health page, worker heartbeats/job outcomes, email deliverability, private upload/download authorization and scanner outage rejection. Verify HTTPS cookies, real reset/verification mail links, Stripe test-to-live webhook setup, real S3 access and Search Console authentication. Confirm robots/sitemap URLs, JSON-LD, thin-page noindex and Terms/Privacy content.

Backups are not automatically configured. Schedule PostgreSQL dumps (or provider PITR), encrypted offsite replication and S3 versioning/retention. Monitor success externally; perform periodic restores with documented recovery objectives. The health page reports backup configuration honestly as unconfigured until an operator establishes it. Do not equate container health with a successful backup.

For rollback, preserve a pre-upgrade database snapshot and original images. Ranking rollback is an application governance action; schema rollback is an operator procedure. New migration 0008 adds tables/columns/indexes without deleting V2 records. Avoid reverting older code against changed credentials/token flows until staging confirms compatibility.

Implementation verified locally; live external integration requires production credentials.

## Backup and isolated restore rehearsal

Examples for the deploying operator (not run against the existing project during this release):

```sh
# Write into an operator-controlled encrypted backup location.
docker compose -f docker-compose.production.yml exec -T postgres pg_dump -U justice_choice -d justice_choice -Fc > /secure-backups/justice-choice.dump
# Restore into a NEW staging database, never the live justice_choice database.
docker compose -f docker-compose.production.yml exec -T postgres createdb -U justice_choice justice_choice_restore_rehearsal
docker compose -f docker-compose.production.yml exec -T postgres pg_restore -U justice_choice -d justice_choice_restore_rehearsal --exit-on-error < /secure-backups/justice-choice.dump
```

Rehearse on separate staging infrastructure where possible, validate record counts and private object links, record recovery time, then dispose only that staging database through the operator's normal process. Encrypt and transfer backups offsite; the dump command alone does not establish retention, redundancy or backup monitoring. S3 object backups/versioning are separate from PostgreSQL.
