You provisioned an app on Rindle Cloud (if not, start
with the Cloud quickstart). The managed connection is the
same shape that rindle dev injects locally: one application ingress and one trusted
credential.
The two values on your dashboard
Open the app’s Connect panel:
| Value | Purpose |
|---|---|
RINDLE_URL |
Unified HTTPS ingress for SQL, migrations, reads, leases, and subscriptions |
RINDLE_DATABASE_TOKEN |
Database-wide bearer for trusted server code |
An app URL looks like https://app-<id>.rindle.cloud. The edge routes each protocol
to the right process: writes reach the HCTree master. Sync reads and live-query traffic
reach its follower. You do not configure master, follower, control-plane, or WebSocket
addresses separately.
The token is a per-app secret. Put it in the API server’s secret store, never source control or browser-visible environment variables.
SQL services and scripts
Use @rindle/sql-client for a SQL-plan app or ordinary server-side
SQL against either plan:
import { createSqlClient } from "@rindle/sql-client";
export const db = createSqlClient({
url: process.env.RINDLE_URL!,
authToken: process.env.RINDLE_DATABASE_TOKEN!,
});
const rows = await db.execute({
sql: "select id, title from issue where status = ?",
args: ["open"],
});
The CLI uses the same values:
rindle sql "select count(*) from issue"
rindle sql --file scripts/backfill.sql
Several statements passed to rindle sql are applied as one atomic batch. For
versioned schema or data changes, use rindle migrate apply
instead.
Synced app API server
On the Sync plan, give createRindleApiServer the unified
connection. It derives its read/control and SQL transports internally:
import { createRindleApiServer } from "@rindle/api-server";
import { schema, queries, mutators } from "./shared/rindle";
export const api = createRindleApiServer({
rindle: {
url: process.env.RINDLE_URL!,
token: process.env.RINDLE_DATABASE_TOKEN!,
},
schema,
queries,
mutators,
});
The API server can run anywhere with fetch: a Node process, Worker, Lambda, or
another server runtime. All trusted calls use the one bearer. It never reaches the
browser.
Browser client
The browser points only at your API server:
import { createRindleClient } from "@rindle/optimistic";
import { schema, mutators } from "./shared/rindle";
const app = await createRindleClient({
schema,
mutators,
user: () => currentUser(),
api: { url: "" }, // same-origin /api/rindle/* routes
});
There is no browser topology configuration. Its first authorized query lease returns the public WebSocket endpoint and a signed follower-affinity ticket. The ticket keeps lease and subscription traffic on the same follower, but it is placement metadata, not authorization. Your API server still authorizes every named query.
Local and Cloud use the same contract
| Local development | Rindle Cloud | |
|---|---|---|
RINDLE_URL |
injected by rindle dev |
copied from Connect |
RINDLE_DATABASE_TOKEN |
injected by rindle dev |
copied from Connect |
| Data tier | local master, follower, and rindle-dev-edge |
packed OVH processes behind Cloudflare Tunnel |
| Browser setup | API URL only | API URL only |
Your schema, queries, mutators, SQL calls, and browser setup do not change between the two environments.
Migrations
After rindle deploy or rindle link has written .rindle/cloud.json, apply the
same ordered migration files remotely:
rindle migrate status --cloud
rindle migrate apply --cloud
The binding is safe to commit. It identifies the app but contains no database token.
The CLI authenticates the Cloud proxy with your rindle login session.
Next steps
- SQL client — queries, sessions, transactions, and Drizzle.
- The API server — named queries and authoritative mutators.
- The browser client — optimistic writes, live views, and rebase.
- Scale & operate — plan changes and fleet convergence.