> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getbased.health/llms.txt
> Use this file to discover all available pages before exploring further.

# Deployment

> Production build, hosted service boundaries, self-host variables, service worker, CSP, and PWA configuration.

# Deployment

The current official deployment separates the static browser app from several narrow, independently deployed VPS services. Vercel still builds and serves the app, compatibility redirects, diagnostics, and bounded transition adapters, but current official clients do not send wearable/CAMS traffic or new profile shares through the static app host. Cross-device Sync and Agent Access use the dedicated relay stack.

## Production build

Source deployments require Node.js 24. `npm ci` compiles canonical `.ts` and `.mts` sources with TypeScript 7 through `prepare`; the emitted `.js` and `.mjs` files are ignored in Git. `vercel.json` enables Git deployment only for `main` and then runs:

```json theme={null}
{
  "buildCommand": "node scripts/fetch-catalog.mjs && node scripts/build-production.mjs",
  "outputDirectory": "."
}
```

After dependency installation and compilation, the catalog fetch runs first. The Rolldown build then rewrites the native-module development entry to a content-hashed startup bundle while retaining feature-level lazy chunks. `vercel.json` gives hashed `js/bundle-*.js` files immutable cache headers and applies the CSP and security headers before the legacy documentation redirects and `/app` rewrite.

Production catalog fetch variables are build-time secrets, not browser runtime configuration:

| Variable | Purpose |
| - | - |
| `CATALOG_FETCH_URL` | Full HTTPS URL for the private recommendations catalog; use a GitHub Contents API URL for a private GitHub repository |
| `CATALOG_FETCH_TOKEN` | Read-only token for that source |
| `CATALOG_FETCH_HEADER_NAME` | Optional authentication-header name; defaults to `Authorization` |
| `CATALOG_FETCH_HEADER_VALUE` | Optional complete header value; defaults to `Bearer ${CATALOG_FETCH_TOKEN}` |

With no URL, the script accepts an existing readable catalog or copies the public example stub. When a URL is present but a preview build has no token, it also uses the stub. A configured production fetch that returns invalid JSON or the wrong catalog shape fails the build rather than silently shipping it.

For local production verification, use `npm run production:build` or `npm run production:check`; both compile first. Direct execution of `scripts/*.mjs` after source edits requires `npm run typescript:build`.

Standalone compatibility and profile-share Docker images use a build stage with compiler dependencies, then copy compiled runtime modules into a non-root Node runtime stage. Rebuild from source when updating these services; copying uncompiled TypeScript into the runtime image is insufficient. Browser imports, worker URLs, service routes, and Companion download paths retain their JavaScript runtime contracts. Vercel's function configuration refers to canonical API sources such as `api/proxy.ts`.

## Official service topology

| Origin | Responsibility | Plaintext boundary |
| - | - | - |
| `getbased.health` | Landing page and `/app` compatibility route | Static files |
| `app.getbased.health` | Browser application, build identity, compatibility routes | Static files and ordinary request metadata |
| `integrations.getbased.health` | Narrow compatibility endpoint at `/api/proxy` | Transient provider credentials/responses for approved operations; no intentional payload logging or persistence |
| `shares.getbased.health` | Opaque encrypted profile envelopes at `/api/share` | Ciphertext plus expiry, deletion, abuse-control, and connection metadata; never the share password or profile plaintext |
| `sync.getbased.health` | Evolu encrypted sync, owner self-service, and encrypted Agent Access context gateway | Encrypted sync records and Agent Context envelopes; owner/token mappings, quotas, and ordinary operational metadata |
| `docs.getbased.health` | Mintlify documentation | Public documentation only |

`js/proxy-runtime.ts` routes official hosts to the integrations service and independent self-hosts to same-origin `/api/proxy`. `js/profile-share.ts` routes current `vps1_...` links from official hosts directly to the share service while older IDs and self-hosted links remain same-origin.

## Sync relay stack

The sync stack is released separately through immutable [getbased-relay](https://github.com/elkimek/getbased-relay) tags. The app's compatibility CI currently pins v2.0.0, whose dependencies include `@evolu/common` 8.9.0 and `@evolu/nodejs` 3.2.0, with Docker pinned to Node.js 24.20.0. These describe the tested pin, not every later relay release or a live deployment revision. Evolu source is installed from the locked npm dependencies; it is not vendored into the relay.

The browser app and relay use independent adapter packages and version streams. The app currently uses `@evolu/common` 8.7.0 with `@evolu/web` 3.1.0, while the CI-pinned relay uses the versions above. They share the Evolu 8 protocol generation; matching package numbers are neither expected nor required. The app's `Sync compatibility` workflow pins the exact relay release commit and exercises both the Evolu 8 default client and the temporary Evolu 7 rollback client against a disposable real relay.

The public TLS origin routes three surfaces while keeping private administration off the network:

| Route | Internal destination | Boundary |
| - | -: | - |
| WebSocket fallback | Relay `:4000` | Encrypted Evolu CRDT traffic |
| `/self/*` | Relay self-service `:4003` | Owner-signed storage reads and replay-protected compaction |
| `/api/*` | Context gateway `:4001` | Encrypted Agent Context reads and writes |
| Admin health/metrics | Container-only relay `:4002` | Operator access; never publicly routed |
| Owner-proof verifier | Private Unix socket | Gateway receives only a yes/no proof result; no owner-key export |

The reverse proxy must overwrite the gateway's dedicated client-IP header from the real connection peer. It must not trust a caller-supplied value. The relay database and context store use separate persistent volumes, and the gateway mounts only the verifier socket—not the relay database.

Before a relay major upgrade, record the current release and image, stop the stack, and create verified backups of both data volumes and the deployment configuration. Keep the previous images available. Rollback may require restoring the matching data backup rather than changing only the image.

## Vercel routes that remain

| Route | Current role |
| - | - |
| `/app` | Rewrites to `index.html` |
| `/api/commit` | No-store build identity for diagnostics, previews, and service-worker cache keys |
| `/api/proxy` | Same-origin compatibility endpoint for independent deployments and legacy clients; not the current official-client destination |
| `/api/share` | Bounded legacy Blob/standalone-service transition adapter; not the destination for new official `vps1_` writes |
| `/guide/*` and `/docs/*` | Redirect old paths to `docs.getbased.health` |
| Hashed bundles and other app assets | Served from the filesystem |

User documentation is not built from the application repository. It lives in the separate Mintlify project.

## Compatibility proxy service

The standalone entry point is `server/compat-proxy-server.ts`; deployment assets live in `deploy/compat-proxy/`. The container listens on its internal port and should be published only on host loopback behind a TLS reverse proxy that exposes the exact `/api/proxy` path.

The operated policy accepts only classified operations:

* official Oura, Withings, Polar, and existing legacy Fitbit OAuth/API requests;
* the exact NVIDIA NRAS GPU-attestation operation;
* CAMS atmosphere requests with coordinates forced to a 0.1° grid;
* bounded credential-free public-page fetches used by reviewed application features.

It is not a hosted Custom API, chat, voice, WHOOP, Ultrahuman, or Google Health proxy. Generic authenticated AI requests fail closed. Request bodies, wearable credentials, health responses, and OAuth payloads are held only long enough to forward the request and are not intentionally written or logged.

### Standalone compatibility variables

| Variable | Required | Purpose |
| - | -: | - |
| `OURA_CLIENT_SECRET` | For official Oura | Confidential token exchange |
| `WITHINGS_CLIENT_SECRET` | For official Withings | Confidential token exchange |
| `POLAR_CLIENT_SECRET` | For official Polar | Confidential token exchange |
| `UVDATA_BEARER` | For the fixed CAMS upstream | Server-side upstream authorization |
| `COMPAT_PROXY_BIND` | No | Listener address; Compose sets `0.0.0.0` inside the container |
| `COMPAT_PROXY_PORT` | No | Listener port; Compose sets `8787` |
| `COMPAT_PROXY_REQUEST_TIMEOUT_MS` | No | Overall incoming-request timeout |
| `PROXY_RATE_LIMIT_MAX` | No | Requests allowed per bounded window; default deployment value is 300 |
| `PROXY_RATE_LIMIT_WINDOW_MS` | No | Window length; default deployment value is 60,000 ms |

Never expose the container port directly, add request-body logging, or broaden an operated allowlist without documenting the target, data visible to the service, origin policy, limits, and tests.

## Profile-share service

The current official store is `server/profile-share-server.ts` with a SQLite adapter in `lib/profile-share-sqlite-store.ts`; deployment assets live in `deploy/profile-share/`. The browser compresses and encrypts a single-profile export before upload. The service accepts at most a 3.75 MB envelope and a 30-day expiry, while the reverse proxy caps the HTTP body at 4 MB.

Cleanup runs at service start, hourly, after creation, and when an expired record is read. The database ceiling defaults to 512 MiB. The operated policy intentionally keeps no retained database backup, so service loss can invalidate outstanding links without affecting source profiles.

### Standalone share variables

| Variable | Required | Purpose |
| - | -: | - |
| `PROFILE_SHARE_RATE_LIMIT_KEY` | Yes | At least 32 characters; keys rotating HMAC abuse-control identifiers |
| `PROFILE_SHARE_BIND` | No | Listener address; Compose sets `0.0.0.0` inside the container |
| `PROFILE_SHARE_PORT` | No | Listener port; Compose sets `8790` |
| `PROFILE_SHARE_SQLITE_PATH` | Yes outside the Compose default | Absolute database path |
| `PROFILE_SHARE_DATABASE_MAX_BYTES` | No | Database ceiling; Compose sets 536,870,912 bytes |
| `PROFILE_SHARE_REQUEST_TIMEOUT_MS` | No | Overall incoming-request timeout |

The service must run in its own container and writable volume, separate from the compatibility proxy, Evolu relay, and UV service. Do not log request bodies, query strings containing share IDs, or management material.

### Legacy Blob transition

Existing Vercel Blob envelopes are not copied. `api/share.ts` implements a bounded transition configured with:

| Variable | Purpose |
| - | - |
| `GETBASED_PROFILE_SHARE_UPSTREAM_URL` | Exact HTTPS standalone-service URL |
| `GETBASED_PROFILE_SHARE_TRANSITION_STARTED_AT` | Cutover instant |
| `GETBASED_PROFILE_SHARE_LEGACY_BLOB_UNTIL` | End of legacy reads/deletes, no more than 31 days after cutover |
| `BLOB_READ_WRITE_TOKEN` | Legacy Blob access during the bounded window only |

Before cutover, the adapter retains legacy behavior. During the window, it redirects new writes, serves live legacy reads/deletes from Blob, and redirects legacy misses. After the deadline, all supported operations redirect and Blob credentials can be removed. Do not turn the transition into an indefinite fallback.

## Self-hosted `/api/proxy`

An independent deployment keeps the same-origin endpoint and its own provider credentials. Relevant variables are:

| Variable | Purpose |
| - | - |
| `OURA_CLIENT_ID`, `OURA_CLIENT_SECRET` | Oura OAuth app |
| `WITHINGS_CLIENT_ID`, `WITHINGS_CLIENT_SECRET` | Withings OAuth app |
| `POLAR_CLIENT_ID`, `POLAR_CLIENT_SECRET` | Polar OAuth app |
| `WHOOP_ENABLED`, `WHOOP_CLIENT_ID`, `WHOOP_CLIENT_SECRET` | Fail-closed self-host WHOOP support; WHOOP is a confidential client and the operator owns developer-program compliance |
| `ULTRAHUMAN_ENABLED`, `ULTRAHUMAN_CLIENT_ID`, `ULTRAHUMAN_CLIENT_SECRET` | Fail-closed experimental Ultrahuman support |
| `GOOGLE_HEALTH_ENABLED`, `GOOGLE_HEALTH_CLIENT_ID`, `GOOGLE_HEALTH_CLIENT_SECRET` | Fail-closed Google Health support |
| `FITBIT_CLIENT_ID` | Existing legacy Fitbit connections only |
| `UVDATA_UPSTREAM`, `UVDATA_BEARER` | Explicit compatible CAMS service and optional bearer; an independent host does not inherit the operated upstream |
| `PROXY_RATE_LIMIT_MAX`, `PROXY_RATE_LIMIT_WINDOW_MS` | Rate-limit bounds |
| `PROXY_RATE_LIMIT_BLOB_TOKEN` | Optional cross-instance limiter token on Vercel |
| `PROXY_ALLOW_INSTANCE_RATE_LIMIT` | Explicitly permit the weaker instance-local fallback on Vercel |
| `PROXY_UPSTREAM_TIMEOUT_MS` | Upstream lifecycle timeout |
| `PROXY_POSTAL_QUEUE_MAX` | Per-instance queued postal lookups, bounded to 1–64; default 8 |

Client secrets never enter runtime config. The browser receives only public client IDs and capability booleans. A missing or incomplete fail-closed provider configuration must block connect, exchange, refresh, sync, and backfill while leaving disconnect available.

For CAMS, set `UVDATA_UPSTREAM` explicitly on an independent self-host or choose browser-direct Open-Meteo. `UVDATA_BEARER` is required only when that selected upstream expects it.

## Local development

The production build also emits `getbased-companion.mjs` for the CLI-provider bootstrap. Serve that artifact alongside the app, but run Companion on each browser user's machine, not on the public web server. Hosted discovery grants chat-only credentials; the current service deliberately reserves lifecycle controls for installation-authorized local connections. Custom page origins require an explicit Companion allowlist. See [CLI agent build and security](/developers/cli-agent-providers#build-and-operate).

`npm run dev-server -- 8000` compiles TypeScript, builds the Companion download, serves emitted native modules, and mirrors same-origin APIs. Open `/app`; direct `node dev-server.js` invocation requires already compiled files.

| Path | Behavior |
| - | - |
| `/` | Sibling landing page when available; otherwise the app |
| `/app` | `index.html` |
| `/api/proxy` | Local runtime-config and proxy-policy mirror |
| `/api/share` | Process-local encrypted-envelope store, cleared on restart |
| `/api/commit` | Local build identity |
| `/api/deploy-catalog` | Operator catalog workflow when configured |
| `/docs/*` | Redirect to public documentation |

Use `.env.local` for untracked local secrets. Environment variables already present in the process take precedence.

The local-only `/api/deploy-catalog` workflow has a separate set of maintainer variables. They are not browser configuration and are unrelated to the production build's `CATALOG_FETCH_*` inputs:

| Variable | Purpose |
| - | - |
| `CATALOG_GIT_REPO` | Optional repository path when the catalog symlink target cannot be detected automatically |
| `CATALOG_COMMIT_MSG` | Optional commit-message override for the catalog update |
| `VERCEL_DEPLOY_HOOK_URL` | Secret Vercel deploy-hook URL called only after the catalog commit is pushed successfully |

Missing configuration skips the corresponding operation. The development endpoint validates the deploy-hook URL shape and never exposes these values to the browser.

## Content Security Policy

The current hosted policy allows self scripts, Wasm evaluation, blob workers, the approved analytics origin, jsDelivr for explicitly retained runtime needs, HTTPS/WebSocket connections, and loopback Local AI connections. It denies frames, objects, and foreign base URLs. The exact source of truth is `vercel.json`.

When adding a provider or browser runtime, review the CSP, direct-CORS requirements, service-worker network-only list, proxy policy, and public privacy disclosure together. CSP permission does not authorize use of the compatibility relay.

## Service worker

Each deployed build has an identity derived from its commit and deployment ID, embedded in emitted `version.js` and the worker by the production builder. New builds use separate caches even when the user-facing `APP_VERSION` is unchanged. Semver labels are for releases and changelogs; a version bump is not required to deliver every patch.

While visible, the app checks for updates every five minutes and on return. It installs the new app shell in the background and offers **Reload / Later** only when that build is ready. Choosing Later keeps the current build running. A failed download leaves the active build usable. Initial installation does not produce a false update prompt.

Required shell assets are precached with bounded concurrency/retries, including offline settings artwork and the bundled Fitbit fallback icon. Keep this inventory aligned with production build output.

| Request | Strategy |
| - | - |
| AI/provider and classified cross-origin requests | Network only; streams remain direct |
| Same-origin `/api/*` | Network only |
| Production same-origin app assets | Current-build cache first, then network |
| Preview/local same-origin app assets | Network first, then current cache |
| Navigation | Cached route when present, with app-shell fallback |
| Explicit update probe | Network first |

Activation cleans obsolete app caches. Version 1.19.3 provides the migration signal for older version-based clients. Local development normally unregisters the worker; use `/app?dev-sw=1` only for deliberate offline verification.

For release tags, do not move historical tags that happen to share a semver label. The repository already uses descriptive tags such as `v1.19.0-cli-agents` to disambiguate its older history.

## PWA manifest

The current manifest contract is:

```json theme={null}
{
  "id": "/app",
  "name": "getbased — Personal Health Intelligence",
  "short_name": "getbased",
  "start_url": "/app",
  "scope": "/app",
  "display": "standalone",
  "background_color": "#0f1117",
  "theme_color": "#0a0a12",
  "launch_handler": { "client_mode": "navigate-existing" }
}
```

The 192 px and 512 px PNG icons use relative paths and `any maskable`; the SVG is an `any` icon. Keep the manifest, service-worker `/app` fallback, installed-PWA smoke test, and landing-page routes aligned.

## Change-scoped release checks

Before release, run the checks related to the changed boundary: production-build validation for build or asset changes, proxy policy/server tests for integrations, profile-share transition/service tests for sharing, and service-worker/manifest checks for PWA changes. GitHub Actions owns the exhaustive browser and combined-coverage matrix.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.