Deployment
What production takes: a database Ablo can reach, a key minted for the plane you mean, and a schema push in the deploy.
One command answers whether the infrastructure needed for a write is ready right now, and if not, why:
ABLO_API_KEY=sk_… npx ablo status
ablo status
key sk_51H8… (ABLO_API_KEY env — overrides stored)
org org_3nKq…
project checkout (prj_7Yb2…)
acts on production root
push production with sk_51H8… (env)
api https://api.abloatai.com reachable
data ✓ database connected to this plane (direct)
schema 4 models pushed (rev 12) hash 3f9a2c81 @ 2026-07-18
• orders typename=orders
• lineItems typename=lineItems
• fulfilments typename=fulfilments
• reviews typename=reviews
✓ write infrastructure is ready. Your database constraints and row-level policies still apply.
status asks the routing authority rather than sampling a read, because reads
resolve while writes are held — a plane with no database connected serves every
read and refuses every write. The verdict at the bottom is the whole page in one
line, and --json puts the same conclusion in a blockers array you can gate a
deploy on.
The three ingredients
There is no Ablo service for you to deploy. Ablo is hosted, your rows live in your own Postgres, and your app runs where it already runs — so a deployment is three pieces pointed at the same plane.
| Ingredient | Who runs it | What “deploying” means for it |
|---|---|---|
| Your Postgres | You (or your provider) | Registering it against the production root, once, with a root-bound key. |
| Ablo | Hosted at api.abloatai.com |
Nothing to run. You choose a project, a plane, and the keys that reach them. |
| Your app and agents | You | Holding the right credential for the runtime, and pushing the schema in the deploy. |
Everything below is those three in order.
Planes: what a deployment targets
A plane is the isolation unit a credential acts on. production is the root
branch; development and preview branches are children. Three things are
per-plane, and knowing which three is most of what production readiness means:
- Rows: a child-branch write is invisible to production and every sibling.
- The registered database: one per plane, so your production database and your dev database are separate registrations.
- The active schema artifact: the model shapes the engine actually routes on.
Every sk_ is fixed to one branch at mint. The persisted branch id—not a
suffix or user-supplied slug—says whether that is the production root or one
development child. This is why application code never passes an environment.
A child copies its parent’s active schema when it is created, then owns its schema history. A child push never changes production. Production gets new models only when the reviewed deployment pushes them to the root.
There is no shared development plane. New credentials are branch-bound; follow Branch-first development.
1. The database production writes to
Your production database joins Ablo the same way your dev database did — logical replication so Ablo can read and confirm, a scoped writer role so Ablo can land rows — run once, with a root-bound key so the registration attaches to the production root:
ABLO_API_KEY=sk_… npx ablo connect apply --url postgres://admin:…@host:5432/db
ABLO_API_KEY=sk_… npx ablo connect check
Connect Your Database is the full walkthrough — the SQL, the two roles, and the complete list of what Ablo touches. Five things about it are specifically production concerns:
Your agents do not each hold a connection. Every agent, worker, and function
talks to Ablo, and Ablo holds the database connections — at most 4 connections
per plane, the same 4 whether one caller is writing behind them or ten thousand
are. They identify themselves as ablo-direct-writer, so pg_stat_activity
accounts for everything Ablo has open at any moment. Size the database for that
number rather than for your agent count.
Register the direct host, not the pooler. A pooler terminates the session
that replication needs, and it refuses the connection in the same words a wrong
password would — so a pooled host reads as a credentials problem for as long as
you let it. ablo status names a pooled host when it sees one, with the direct
host to use instead.
Reachability is measured from Ablo’s network, not yours. connect check
runs from the infrastructure replication runs on, so an IPv6-only,
IP-allowlisted, or VPC-private database still verifies — and a database your
laptop can reach but Ablo cannot fails here rather than at the first write.
wal_level = logical needs a restart. It is server-wide and not reloadable.
On RDS and Aurora it is a parameter-group change plus a reboot. Schedule it;
it is the one setup step with downtime in it.
A replication slot retains WAL. While Ablo is connected the slot holds what it has not yet acknowledged, so a long disconnection accumulates disk. Ablo monitors slot lag and retention and surfaces it, and drops an abandoned slot rather than letting it grow without bound.
A database that cannot grant a REPLICATION role connects through the signed
Data Source endpoint instead. Same model surface, same
commit chokepoint — it is the marked fallback, so reach for it when replication
is genuinely unavailable.
2. The credential each runtime holds
Credential configuration follows the runtime. Long-lived root keys use
apiKey; scoped actors use session; browser login exchange uses
session.endpoint:
| Runtime | Credential | Notes |
|---|---|---|
| Server, worker, agent, cron | sk_ in ABLO_API_KEY |
Defaults from the environment, so most code passes nothing. |
| Serverless function | sk_ in ABLO_API_KEY |
Stateless request/response; nothing held open across invocations. |
| Long-running agent | session: () => sessions.create(...) |
One renewable identity and one multiplexed WebSocket per client and Ablo cell; checkpoint durable deltas before acknowledging. |
| Browser, read-only | root-bound pk_ |
Publishable, safe to ship, and read-only. |
| Browser, writing as the signed-in user | session: { endpoint } |
A route on your backend mints a short-lived ek_ per user. |
API Keys covers the model; Sessions covers minting. Two things bite specifically at deploy time.
ablo login does not give the application production authority. It stores
a project-scoped mk_ management credential that manages branches and exchanges
for temporary child credentials; it cannot read or write application data or
push the production schema. A production deploy needs a secret sk_
from the dashboard, supplied as ABLO_API_KEY. ablo whoami names what an
explicit key acts on, and ablo status --json reports its capability as
runtimeKey.kind for a pipeline to check before pushing. There is no local
mode switch.
An explicit key always wins. Application-facing commands resolve
ABLO_API_KEY, then .env.local, then .env, then a legacy stored runtime
credential — never the mk_ management login for data operations. ablo status
prints what it found under key, with its source. When a deploy lands somewhere
surprising, that line is usually the answer.
Use ablo whoami when you need only the authoritative identity:
ABLO_API_KEY=sk_… npx ablo whoami
For a deployment secret already stored under another name, prefer
ablo whoami --key-env <NAME> over copying it into argv or permanently
maintaining parallel local variables. The command reads the explicitly named
value from the process or project env files and reports its project and branch
without printing the full secret.
3. Pushing the schema is a deploy step
The server keeps its own copy of your schema and routes on that copy. Until it
has yours, a write to a new model fails with server_execute_unknown_model — so
ablo push belongs in your deploy pipeline, ordered before the code that
depends on the new models goes live.
ABLO_API_KEY=sk_… npx ablo push --yes
# or, explicitly:
npx ablo push --env-file .env.production --yes
Production requires confirmation: interactively you type the destination
project’s name, which is what makes a wrong-project deploy impossible to do by
reflex. In CI there is no TTY, so --yes is the confirmation and a push without
it stops rather than proceeding unattended.
Additive changes pass; destructive ones ask. Adding a model or an optional
field applies cleanly. Dropping a model or a field, narrowing an enum, or a lossy
cast is classified as data loss and needs --force; adding a required field to a
populated table needs a --backfill. A push that fails is recorded failed and
never activated, so a broken migration cannot leave clients gated against tables
that do not match.
This is the same expand-and-contract shape any online migration has, and it sequences the same way: push the additive change, deploy the code that writes both shapes, backfill, then push the removal in a later deploy once nothing reads the old field.
Drift is a connect-time rejection, not a runtime surprise. A client built
against a schema the server is no longer running is turned away when it connects.
ablo status prints the local hash beside the deployed one, and the running
client reports the same serverSchemaHash value, so the two can be matched at a
glance.
Gate the deploy on the verdict
ablo status --json reports the same conclusion the human output ends with, in a
form a pipeline can act on. An empty blockers array is the machine-readable
form of “ready”:
blockers=$(ABLO_API_KEY=$ABLO_API_KEY npx ablo status --json | jq '.blockers | length')
[ "$blockers" -eq 0 ] || { npx ablo status; exit 1; }
Each blocker carries a problem and the single fix that resolves it, in the
order you should act on them: an unreachable API makes every other finding
unverifiable, and a plane with nothing connected makes a schema question
academic. The JSON also carries confirmedTarget — the org, project, and
environment the server says this key resolves to — which is the authoritative
answer to where a push would land.
Webhooks point at the deployed URL
npx ablo dev prepares the schema branch; it does not forward webhooks. A
deployed HTTPS endpoint is registered once, and Ablo returns the signing secret
a single time:
ABLO_API_KEY=sk_… npx ablo webhooks create https://yourapp.com/api/ablo/[...all]
ABLO_API_KEY=sk_… npx ablo webhooks list # endpoints + delivery health
webhooks list reports each endpoint’s status, cursor, and last error — the
place to look when a mirror falls behind. Webhooks covers the
handler, the Standard Webhooks signature, and rolling a secret.
Multi-stage schema changes
ablo plan is the read-only front door for source, active artifact, and
PostgreSQL together. For a live rename, type transition, or required-field
change, commit a deployment manifest and pass the same file to plan and push.
Its gates are expand, dual_write, backfill, verify, switch, and
contract; each names an owner, resource, dependencies, status, and action.
npx ablo plan --manifest ablo/deployment.json
npx ablo push --manifest ablo/deployment.json --yes
A live manifest cannot combine expand and contract for the same resource.
Contract belongs in a later manifest with its own recorded approval. A
backfill being finished is not contract evidence; verification and client-drain
gates must also be satisfied.
What to watch once it is live
ablo logs: commit activity as it happens, scoped by the key. A root-bound key streams production; a child-bound key streams only that child.--jsonemits NDJSON for piping.ablo status: the readiness verdict. Cheap enough to run from a health check on your own side.- The audit log: every confirmed write traced back to the key that made it and the person who authorized that key.
- Your own logger: pass
loggerto the client and SDK lifecycle, sync, retry, and rollback events join your existing pipeline.
Writes carry receipts rather than being fire-and-forget: a commit is accepted the
moment Ablo takes it (queued) and becomes confirmed once the row appears on
your database’s WAL. Guarantees covers which state to wait for
and what each promises.
When something is wrong
| What you see | What it means | The fix |
|---|---|---|
no database is connected to this plane |
Writes are held rather than routed. Reads still resolve, which is why a read probe stays quiet. | ablo connect apply with a key for that plane. |
password authentication failed during connect |
Often a pooled host refusing a session it cannot serve, in the words of a wrong password. | Register the direct database host. |
server_execute_unknown_model |
The plane’s active schema does not carry that model. | ablo push with a key for that plane. |
| Clients rejected at connect | The deployed schema and the client’s schema disagree. | Push this tree, or deploy the revision the server is running. |
project_scope_denied (403) |
The model belongs to another project in your org. | Use a key minted for that project: a push cannot cross projects. |
403 on ablo push |
The key authenticated but cannot author schema. | A secret sk_; the mk_ stored by ablo login is management-only. |
The checklist
- Production database registered against the production plane, direct host, and
ablo connect checkall green. - A secret
sk_in the deploy environment asABLO_API_KEY— never in a browser bundle. ablo planreviewed, followed by fingerprint-gatedablo push --yes.ablo status --jsongating the deploy on an emptyblockersarray.- Browser clients on a root-bound
pk_orsession.endpoint, not a secret key. - Webhook endpoints registered at their deployed URLs, with the signing secret in your environment.
Next steps
- Connect Your Database — the setup this page registers, in full.
- Projects — one org, many apps, each with its own planes and keys.
- API Keys — which credential each runtime holds, and what it may do.
- CLI — every command, its flags, and the environment variables.
- Operating on Your Database — which actions run freely and which belong to a human.
- Debugging & Logs — watching claims, queueing, and grants while you build.