Deployment runbook
The current trial deployment uses GitHub → Vercel + Render. Vercel serves the static assets and forwards dynamic pages and API requests to Render. Render runs the Docker application and initializes its database before accepting traffic. Supabase PostgreSQL is an optional later upgrade.
Current free trial
- Public site: https://open-sensations.vercel.app
- Render origin: https://open-sensations.onrender.com
- GitHub: https://github.com/r3dlabs/open-sensations
- Render service:
open-sensations, Docker, Singapore, Free, health check/health. - Vercel: Other framework, build
node scripts/build_vercel.mjs, outputvercel-public.vercel.jsonconfigures the external rewrite;.vercelignoreexcludes the Python backend from Vercel.
Render uses DATABASE_URL=sqlite:////tmp/opensensation.db. This database is a disposable projection of the committed YAML: startup runs migrations and reimports the canonical records after restarts. This is suitable for the current read-only app and does not store user submissions. GitHub remains the source of truth. No paid database or disk is required. Free Render instances sleep when idle, so the first request may be slow or need retrying while the service wakes.
Set Render's APP_ENV=production, PUBLIC_BASE_URL=https://open-sensations.vercel.app, CORS_ORIGINS=["https://open-sensations.vercel.app"], ALLOWED_HOSTS=["open-sensations.vercel.app","open-sensations.onrender.com","127.0.0.1","localhost"], and WEB_CONCURRENCY=1. The same settings are recorded in render.yaml. Empty environment values use application defaults; list settings must contain valid JSON.
Pushes to main deploy both services. Verify Render's /health first, then the Vercel homepage, /explore/sensors, and /v1/sensors/lidar. Backend changes may take longer to deploy than Vercel's assets. Vercel needs no Python/database environment variables in this setup. Add a custom domain in Vercel and update Render's public URL and host/origin lists together.
For persistent writes or an always-on deployment later, choose a paid compute plan and a managed PostgreSQL database, set DATABASE_URL, and keep the existing migration/import startup. The following sections describe that optional PostgreSQL path.
Local verification
docker compose up --build -d
docker compose ps
curl http://localhost:8001/health
curl http://localhost:8001/v1/sensors/lidar
docker compose exec app python -m scripts.validate_data
docker compose exec app python -m scripts.import_data
The second import should report changed: false. PostgreSQL is bound to localhost, not the LAN. Credentials in Compose are explicitly local development defaults. Production must use a new secret stored in the hosting environment.
GitHub
Initialize the checkout, review all files for secrets, create a repository under your actual account/organization and push the initial branch. Do not commit .env, database files, runtime artifacts or credentials. Set the real repository URL in application configuration, CITATION.cff if desired, and the documentation. Replace the commented CODEOWNERS examples with real consenting maintainers.
Enable branch protection and require CI. Enable private vulnerability reporting. Configure a protected release environment with the appropriate maintainers. Tags matching v* run release validation and create a draft GitHub release with dataset and graph archives. A maintainer reviews the changelog and assets before publishing it. Pull requests never deploy production.
Supabase PostgreSQL
Create a dedicated project, then obtain its direct or session-pooler PostgreSQL connection string from the dashboard. Use an appropriate TLS configuration (sslmode=require at minimum; use verified certificates where your provider supports it). Keep the password in Render secrets. Do not use the anon key or service-role API key as a database password. IPv4-only hosts may need the provider's session pooler rather than its direct IPv6 endpoint.
Use SQLAlchemy's postgresql+psycopg:// URL. Plain postgres:// and postgresql:// URLs are normalized by settings. URL-encode special password characters. Use a dedicated database/schema and restrict access to the application; do not expose projection tables through a separate public database API unless you deliberately configure its permissions. No Supabase browser client is needed.
Apply Alembic and import using the release container/environment. The default single-service entrypoint does both automatically. For multiple replicas, move those commands to a serialized release job and start application workers directly; advisory import locking does not serialize Alembic schema migrations.
Render
Connect the actual GitHub repository and use the provided render.yaml, or create a Docker web service manually. The blueprint specifies the paid 0.5c-512mb compute plan; review current pricing before creating the service. Configure:
| Variable | Production value |
|---|---|
| DATABASE_URL | Supabase PostgreSQL URL with TLS |
| APP_ENV | production |
| PUBLIC_BASE_URL | https://www.open-sensations.com |
| ALLOWED_HOSTS | JSON list of website/API hostnames and the assigned PaaS hostname |
| CORS_ORIGINS | JSON list containing the website origin; no credentials or wildcard needed |
| DATASET_VERSION | Exact release version |
| SOURCE_GIT_COMMIT | Commit used for this dataset release |
| GITHUB_REPOSITORY_URL | Actual canonical repository URL |
| PORT | Provided by the PaaS |
| WEB_CONCURRENCY | Worker count appropriate to instance capacity; default 2 |
The process binds 0.0.0.0:$PORT. Configure /health as the readiness/health route. Review database pool budget: each worker permits 5 persistent plus 5 overflow connections. Set edge request limits and observability appropriate to the chosen instance. The app itself does not provide distributed request throttling.
Run migrations/import with a migration role; for stricter production separation, launch the API with a read-only database role after the release job completes. The shipped single-container default uses one role to simplify initial operation. Backups and infrastructure retention are operator responsibilities.
Domains and TLS
Add www.open-sensations.com and api.open-sensations.com as custom domains on the service. At the DNS provider, use the exact CNAME/verification values returned by Render; do not guess a service hostname or IP. Both domains may point to the same service. Configure apex redirects if desired. Wait for provider verification and managed certificates, then verify HTTPS health, explorer, JSON API, CORS, sitemap and canonical URLs. Documentation is at /documentation; /docs is interactive Swagger UI.
Rollback
Keep the previous image, dataset archive, Git commit and database backup. For data-only regressions, deploy the previous canonical commit/version and re-run the importer. For schema regressions, assess Alembic downgrade data loss before using it; prefer restoring a tested backup and matching application release. Never blindly downgrade production. See the maintainer guide for the release checklist.
Optional Neo4j AuraDB
Create a separate AuraDB instance if needed; store its encrypted URI, username and password as execution secrets. Run python -m scripts.sync_graph after the canonical validation/import job. Neo4j failure must not block the public PostgreSQL API. Verify a known LiDAR relationship and dataset marker. No cloud graph credential is required for local tests or exports.
Provider references checked during implementation: Render Blueprint specification, Supabase connection modes, and Render custom domains. Use the dashboard-provided values for your actual account.