Deployment Overview
Two things deploy on this platform and they move on separate clocks — the platform runtime you operate, and the metadata app you build. Which one you are doing decides which pages in this section are yours.
Two different things get deployed here, and they run on separate clocks:
| The platform runtime | Your metadata app | |
|---|---|---|
| The sentence that fits | "I operate ObjectStack." | "I build an app that runs on it." |
| Ships as | a Docker image (or the os CLI on a host) | a compiled artifact, dist/objectstack.json |
| Versioned by | our release train (X.Y.Z) | your own catalog |
| Whose cadence | ours | yours |
| The deploy action | move the image tag, restart | compile, then install it or pin it at boot |
| Rolling back | restore the previous tag or artifact | install the previous version |
The two are genuinely independent: a deployment can move from platform 16 to 17 without touching its app, and publish twelve app versions without moving the platform. Most deployment questions are really the question which of these am I doing — so this section is ordered that way, and so is the sidebar.
Where it runs — Docker, Compose, Kubernetes, a bare Node.js host, or an ObjectStack Cloud environment — is a detail inside the first of those two, not a third thing alongside them. Pick which job you are doing first; the venue is a choice you make inside it.
Doing both is normal; doing both in one motion is not. A team that self-hosts its own app operates the runtime and ships the app — still two jobs, two cadences, two checklists. Read the one you are acting on today.
The platform runtime — you operate it
The platform ships as a single version-locked train: every @objectstack/*
package shares one version number, and that number is the platform version. It
is published as an official runtime image, ghcr.io/objectstack-ai/objectstack,
whose tags mirror @objectstack/cli versions: the exact X.Y.Z, plus the
rolling X.Y, X and latest tags. Pin the exact version in production and
move it deliberately.
Where it runs
- Docker — the standard path. The image is Node 22 +
@objectstack/cli+os start, running as a non-root user with a built-in health check. Compose with Postgres and Kubernetes are shapes of that same path rather than alternatives to it; bare Node.js under systemd is the minority path, for hosts where a container runtime is not available. See Docker (official image). - ObjectStack Cloud. The control plane provisions environments and operates the runtime for you, and apps arrive by installing a package version from your organization's catalog. Cloud hosts, database provisioning, billing, and public SaaS distribution live outside this repository; the framework runtime exposes the environment registry, marketplace proxy, and runtime-config seams that distribution consumes.
- A local runtime, for development.
pnpm devin this repository, oros devin a generated app: one process, one active environment, no control-plane hop. See Single-Environment Mode.
How many environments one runtime serves
A single-environment host owns exactly one active environment, selected with
OS_ENVIRONMENT_ID. An environment-aware host resolves one per request —
from the scoped URL, the hostname, X-Environment-Id, the session, or a
configured default. That is a property of the runtime you operate, not of the
app it runs; the full precedence list is Resolution
order. Control-plane endpoints
under /api/v1/cloud/... manage environments rather than running inside one and
are deliberately excluded from it.
The pages for operating the platform
| Page | What it answers |
|---|---|
| Self-Hosted Deployment | Running the artifact on infrastructure you operate — Docker, Compose with Postgres, Kubernetes, systemd, health checks, reverse proxy and TLS, and the four values every deployment must pin |
| Production Readiness | Security headers, rate limiting, observability, and the go-live checklist |
| Backup & Restore | What state a deployment actually holds, how to back up each piece per database driver, and the restore drill to rehearse before you need it |
| Tenancy Postures & Membership | Which of the three postures this deployment runs in, how new users join an organization, and the degraded-tenancy boot guard |
| Single-Environment Mode | The one-environment boot shape — what it includes, and what it deliberately does not |
Upgrading the platform
Moving the platform forward is its own procedure: move the tag, restart, and know what the boot does when the database is still shaped for the previous version. See Upgrading → The platform runtime.
Your metadata app — you build it
An app is not part of the platform. It compiles to its own immutable artifact, carries its own version, and is released by you, when you decide:
objectstack.config.ts ──(os compile)──▶ dist/objectstack.jsonDeployment config stays outside that artifact. Database URLs, auth secrets,
runtime credentials, and environment identity are host inputs, injected as
OS_* variables or CLI flags.
Validate before you compile
ObjectStack metadata is data, not code paths, so most mistakes pass tsc and
then fail silently at runtime. One command catches them at author time:
os validate # schema + CEL predicates + widget bindings — no artifactRun it after every metadata edit — see Validating Metadata.
How the app reaches a running platform
There are two ways in, and picking the wrong one is the expensive mistake:
| Catalog install | Artifact-pinned boot | |
|---|---|---|
| The app arrives | after the platform is running | at boot, as an input to the process |
| Named by | package id + version (com.acme.crm@1.2.0) | a URL to the compiled artifact (OS_ARTIFACT_URL) |
| Switching versions | install another version; no restart | change the variable, restart |
| Apps per runtime | many, side by side | one — it is this runtime's app |
One question decides it: is this app the reason the runtime exists? If yes, pin the artifact — the deployment defines the app. If no, install from the catalog — the runtime is a platform that receives apps. The full comparison, the tie-breakers, and every publish command are on Publish, Versioning & Preview; the operational detail of the pinned path is Artifact-pinned boot.
Publishing and installing are separate steps. os package publish uploads a
versioned package to your organization's catalog and changes nothing that is
running; installing a version into an environment is what moves an app. For an
unattended pipeline, os cloud login --email … --password … skips the browser
device flow — and note that under --json that command emits NDJSON:
compact JSON documents, one per line. Parse its stdout line by line rather than
with a single JSON.parse (see os cloud login).
The pages for shipping an app
| Page | What it answers |
|---|---|
| Publish, Versioning & Preview | Compile the artifact, publish a version, install it, and the preview patterns — including how to rehearse a pinned rollout locally |
| Validating Metadata | The mistakes a type-checker cannot see, and the one gate that rejects them before you ship |
Upgrading the app
os migrate meta --from <major> replays every step between the major your
metadata was authored against and this runtime's, in one pass; you then rebuild
and ship as usual. See Upgrading → The metadata
app.
Where the two meet
Three places, and each is where treating the two as one thing goes wrong:
-
The
engines.protocolhandshake. The artifact declares which platform majors it can run on, and a runtime outside that range refuses the app rather than half-loading it. Everything else about the two version numbers is independent. -
The artifact source is an operator's input. Whoever runs the platform sets it, and it selects which version of the app boots. The sources have a fixed precedence, so a container that presets one variable cannot shadow a deliberate override:
--artifact > OS_ARTIFACT_URL > OS_ARTIFACT_PATH > <cwd>/dist/objectstack.json -
The database. Moving either side can leave the physical schema shaped for the other.
os migrate planreports the drift, categorised safe / needs-confirm / destructive;os migrate applyreconciles it.
Every deployed app is AI-operable by default: an MCP server is served at
/api/v1/mcp in every deployment, exposing your objects and actions to AI
clients under the same permissions and RLS (set OS_MCP_SERVER_ENABLED=false to
opt out). See MCP Server env
vars and Your app as an MCP
server.
Shared reference
Three pages belong to neither side and are read from both:
| Page | Why both |
|---|---|
| CLI reference | One command surface covers both jobs: os start and os migrate operate the runtime, while os validate, os compile, and os package publish ship the app |
| Environment Variables | The canonical OS_* catalog. Configuration belongs to the host rather than to either shipped thing, which is exactly why it is neither side's page |
| Troubleshooting & FAQ | Symptom-first answers for validation, query, configuration, and performance problems on either side |