Environments
| Branch | Railway environment | Services | Trigger |
|---|---|---|---|
| main | staging | web, api, PostgreSQL | Railway-native GitHub integration |
| production | production | web, api, PostgreSQL | Human 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
- 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.
- 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).
- Verify staging health and representative user flows. A human runs scripts/promote.sh. Agents never run it.
- 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.
- It shows the production-to-main diff and heuristically flags certain destructive Alembic changes for manual acknowledgement. This heuristic does not replace migration review.
- 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 — names only
| Names | Consumer and purpose |
|---|---|
| INTERNAL_API_URL, NEXT_PUBLIC_SITE_URL, PORT, NODE_ENV | Web API boundary, site metadata and runtime |
| DATABASE_URL, PORT, STALE_AFTER_DAYS | API persistence, binding and age threshold (45 days default) |
| ADMIN_USERNAME, ADMIN_PASSWORD | Shared editor credentials on web and API; missing configuration fails closed on protected paths |
| LEAD_IP_HASH_SALT, LEAD_RETENTION_DAYS | API consent IP hashing and retention window |
| SENDGRID_API_KEY, LEAD_NOTIFICATION_FROM, LEAD_NOTIFICATION_TO | API’s best-effort lead notification |
| REVALIDATE_SECRET | Optional protected revalidation endpoint; unused by the CMS data path |
| STAGING_API_URL, STAGING_WEB_URL, PROD_API_URL, PROD_WEB_URL, POST_DEPLOY_TIMEOUT | Human promotion script overrides |
| TF_VAR_github_token, TF_VAR_github_owner, TF_VAR_github_repo | Terraform GitHub provider/module inputs |
| PG_CONN_STR | Draft 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
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
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 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.
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
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.
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.