Your first deployment
sutra deploy has two paths onto a running engine: a ConfigMap patch (the default, for a
Kubernetes deployment source) and the synchronous API (--api, against an engine running the
db deployment source, or any engine you can reach directly). This page walks the API path,
because it is the deterministic one — the call returns only once the deployment is Active, so
what you see on the wire matches what actually happened.
What "deploy" means
The deploy unit is one sealed .sutra archive. sutra package runs the full fail-closed
validation suite and derives the archive's manifest — including its content-addressed
deploymentId = sha256(manifest) — from the package directory; nothing in the manifest is
hand-authored. Deploying that archive is idempotent: re-deploying identical bytes is a no-op,
and a changed archive under the same slot (its stable tenant--module--version key) replaces
the slot's active revision in one transaction — a hot-deploy, not a restart.
Deploy over the API
Point the CLI at a reachable engine and hand it a sealed archive:
sutra deploy my-first-app-main.sutra --api --engine-url http://localhost:<port>
What happens, in order:
- The CLI uploads the archive bytes to
POST /admin/deployments. - The engine re-verifies the archive fail-closed (
sutra_loader::read_archive) — a corrupt or invalid archive is rejected here, before anything is stored. - The engine stores the archive as the new active revision for its slot, in its own datasource.
- The engine runs its in-process two-phase activation flip (drain the old revision if one exists, activate the new one).
- The call returns synchronously:
200 {deploymentId, phase: "Active"}on success, or a4xxcarrying theSUTRA.DEPLOY.*reject diagnostic on failure.
There is no propagation window to wait out — the HTTP response is the "it's live" signal. For
a deployment large enough that the engine's own plan-and-flip work risks a long-held request
(mainly a concern behind a Kubernetes ingress with a short proxy-read-timeout), the same
endpoint accepts an async mode: it returns 202 {deploymentId, status: "Pending"} immediately and
you poll GET /sutra/deployments/{id} (or request a completion webhook/broker notification) until
it flips to Active or Failed. Small local deploys — everything in this book's examples — use
the synchronous form.
What you see
A successful deploy leaves you with:
- A running deployment.
GET /sutra/deployments/{id}reportsActive, the same statussutra deploy --waitpolls on the ConfigMap path. - A live channel. Whatever channels the package declared in
channels.yamlare now bound and serving — an HTTP channel accepts requests immediately; a broker channel's consumer is running (or, for asingleton: truechannel, running on whichever replica currently holds the per-channel lease). - Nothing extra. Deploying does not create infrastructure — no database, no broker, no
ingress. Those are provisioned separately (locally via
docker compose, in a cluster via the OpenTofu modules underdeploy/); the deploy call only ever registers and activates the package's processes, channels, and data-store bindings against infrastructure that already exists. See Deployment model for the full model, including how a fleet of replicas converges on the same active set.
Hot-deploy and rollback
Edit a package's source, re-package under the same slot, and re-deploy — the archive gets a
new content-addressed deploymentId, but the slot name doesn't change, so the flip happens
in-process with in-flight instances on the old revision left to drain. Rolling back is the
identical operation in reverse: re-deploy the previous archive under the same slot. See
Deploy, hot-deploy, and rollback for the operator-facing detail.
Next
You've scaffolded an app, packaged it, and deployed it. Building BPMN solutions picks up from here — the ideas and file formats behind what you just deployed.