Urza’s Wallet / Engineering

Infrastructure and operations

Costa Rica personal finance comparison platform.

environments

Environments

BranchRailway environmentServicesTrigger
mainstagingweb, api, PostgreSQLRailway-native GitHub integration
productionproductionweb, api, PostgreSQLHuman promotion advances the branch

Each environment has isolated data and credentials. Railway configuration is manual. The public domain belongs to web; api and PostgreSQL communicate privately. Redis, an ingestion service, and document storage are not part of the current service layout.

Canonical operator runbooks remain in Obsidian: Handbooks/Infra/railway-config.md, Handbooks/Infra/db-migrations.md and Handbooks/Infra/promotion.md. Repository config is evidence of intended setup, not proof of live dashboard state.

deploy and promote

Deploy and promote

  1. Configure each Railway service’s GitHub source and environment branch. Root directory stays at the repo root. Use railway.web.json and railway.api.json, and watch their app/package paths and the lockfile.
  2. The web config installs the frozen workspace and builds @urza/web with transitive packages. API builds a Python virtualenv and installs apps/api; its start command runs Alembic upgrade head before Uvicorn on PORT (8000 default).
  3. Verify staging health and representative user flows. A human runs scripts/promote.sh. Agents never run it.
  4. The script requires a clean tree on the source branch, fetches origin and verifies local/remote agreement. It checks staging API/web health and runs Playwright smoke tests unless an explicitly selected skip mode applies.
  5. It shows the production-to-main diff and heuristically flags certain destructive Alembic changes for manual acknowledgement. This heuristic does not replace migration review.
  6. After operator confirmation it creates a vYYYY.MM.DD-N tag, pushes it, and updates production with an explicit force-with-lease expectation. It polls production API/web health every 10 seconds for up to 300 seconds.

The script’s implementation uses a guarded branch update, not a guaranteed fast-forward. Its destructive-migration acknowledgement precedes promotion; the API start command still runs upgrade head. Follow the canonical migration runbook and take an operator-verified database snapshot before risky migrations.

Hotfix path

Cut hotfix branches from production and merge to production. Human hotfix tagging skips staging checks. Cherry-pick the same commits and archived specification back to main through a PR, and merge it before the next promotion. PR previews for the product are deferred; local development and staging are the review environments.

configuration

Configuration — names only

NamesConsumer and purpose
INTERNAL_API_URL, NEXT_PUBLIC_SITE_URL, PORT, NODE_ENVWeb API boundary, site metadata and runtime
DATABASE_URL, PORT, STALE_AFTER_DAYSAPI persistence, binding and age threshold (45 days default)
ADMIN_USERNAME, ADMIN_PASSWORDShared editor credentials on web and API; missing configuration fails closed on protected paths
LEAD_IP_HASH_SALT, LEAD_RETENTION_DAYSAPI consent IP hashing and retention window
SENDGRID_API_KEY, LEAD_NOTIFICATION_FROM, LEAD_NOTIFICATION_TOAPI’s best-effort lead notification
REVALIDATE_SECRETOptional protected revalidation endpoint; unused by the CMS data path
STAGING_API_URL, STAGING_WEB_URL, PROD_API_URL, PROD_WEB_URL, POST_DEPLOY_TIMEOUTHuman promotion script overrides
TF_VAR_github_token, TF_VAR_github_owner, TF_VAR_github_repoTerraform GitHub provider/module inputs
PG_CONN_STRDraft Supabase remote-state backend migration

Keep values in Railway variables or an operator’s credential manager. Never commit credentials or publish values here. Clerk and R2 credentials are future work. Railway connection references must resolve to the correct environment’s database and private API port.

backup and rollback

Backup and rollback

Database restore points and retention depend on the live Railway plan and configured backup policy; the repository does not verify them. Before a production schema change, the operator confirms a recent usable snapshot and a restore procedure. A database restore is distinct from rolling back application code.

Use the Railway deployment history and the canonical promotion runbook to restore a known-good application version. Release tags use the date-based script format. Any branch movement is human-operated. Do not assume a fixed count of retained deployments or automatic rollback on failed health checks. The promotion script exits with a failure if health polling times out; it does not roll back automatically.

Draft Document-volume snapshots, R2 recovery/versioning, cross-region backups and verified restore exercises require separate setup. No backup job or retention promise is established by this repository.

observability

Observability

Railway service logs and resource dashboards are the operational starting point. The API exposes /health and /api/v1/health, request-ID middleware and rate-limit response headers. Web health checks use /. Lead-notification and retention jobs log outcome IDs/counts.

Draft Sentry packages, a web-vitals reporting endpoint, structured per-request duration logging, log drains, custom freshness/latency dashboards and external uptime monitoring are not implemented here. LCP/CLS/INP and API latency figures in the earlier architecture were targets, not deployed monitoring guarantees. There is no CI; the Copilot setup workflow only installs agent tooling.

terraform scope

Terraform scope and local state

Terraform ≥1.15 loads integrations/github ~>6.0 and the github module. Railway is not managed by Terraform. Current files inspect repository/owner data; branch protections, environments and extra branch resources are commented out. Do not mistake those examples for provisioned policy.

cd infra/terraform
terraform init
terraform plan
terraform apply

These are human operator commands after credentials are configured. The provider reads sensitive github_token input. Use infra/setup.sh only after reviewing its prompts and file writes. No Terraform command was run for this documentation change.

The active backend is local; state and variable files remain private. Historical Railway state detach instructions removed tracking without deleting services, but any state migration is an operator procedure requiring review.

terraform state

Supabase remote state — Draft

Draft Remote PostgreSQL state is a future migration; providers.tf currently declares backend local. The former Supabase guide remains the intended setup: create a dedicated terraform_state login with a generated password, grant CONNECT on postgres, create terraform_remote_state, grant schema access and default table privileges, and obtain the project reference from Supabase settings.

Store the connection string securely with sslmode=require and expose PG_CONN_STR only in the operator shell. Verify connectivity with SELECT 1. Replace the backend declaration with pg, review and run terraform init -migrate-state, then inspect the terraform_remote_state.states row count to confirm initialization. Configure any future automation secret only if automation is introduced; no Terraform CI job exists today.

operations access

Operations access

Railway is managed through its dashboard and CLI. Operators authenticate with railway login and railway link, selecting the correct project and environment before migrations or log access. An environment-scoped project token is an alternative for approved automation. There is no Terraform Railway provider and no current Railway token input in Terraform.

The former token guide’s terraform-provisioning API token, production-only environment model and repeated Terraform apply steps are obsolete. Obtain the current CLI token from Railway’s environment/project settings if needed, store it securely and verify its scope before use. Never publish the token or place it in documentation.

docs site hosting

Documentation hosting

Cloudflare Pages serves the committed docs/site folder with no build command, production branch main. Cloudflare Access must protect the production hostname and preview hostnames before sharing. This setup is manual and has not been provisioned by this change. The hosting runbook describes setup and unauthenticated verification.

deployment

Deployment layout

Railway production

Railway staging

main pushes

main pushes

INTERNAL_API_URL

Tag and guarded production branch update

production pushes

production pushes

INTERNAL_API_URL

GitHub repository

Staging web · public

Staging API · private

Staging PostgreSQL

Human promotion after staging verification

Production web · public

Production API · private

Production PostgreSQL

Cloudflare Pages docs · manual setup

Cloudflare Access · manual policy

Isolated staging and production environments