Deploy from a local machine
Run an omg app locally and understand the source-snapshot deploy path the forthcoming omg CLI will use.
CLI status: the platform-side deploy contract is implemented in
control-plane/lib/cli-deploy.tsandcontrol-plane/lib/cli.tsonfeat/cli-deploy-from-snapshot(PR #944). TheomgCLI binary is still being built and is not available to install yet. This page documents the platform path and local runtime today; it does not invent a command surface for an unreleased binary.
Run the app locally
The templates/react-ts app is a normal public-npm project. From a copy of the
template:
bun install
bun run devNo omg token or VM is required for package installation or the local dev
server. The Vite plugin starts the Bun API server in the same process, runs
schema codegen and safe migrations, writes the local SQLite database at
.vibes/data.db, streams realtime updates, and schedules cron() handlers
in-process.
For a production-shaped local build:
bun run build
bun x vibes-buildvibes-build is the binary exposed by @omg-dev/vite-plugin. It emits the
client in dist/ and the server bundle, seeded database, and manifest under
.vibes/, including .vibes/server.mjs, .vibes/data.db, and
.vibes/artifacts.json.
Local runtime boundaries
| Capability | On a plain local machine |
|---|---|
Vite UI, functions/, schema codegen, migrations, SQLite, realtime | Works |
cron() | Works; dev mode uses the in-process scheduler |
@omg-dev/ai | Bring your own ANTHROPIC_API_KEY (or the matching OpenAI configuration) because the sandbox proxy is absent |
@omg-dev/media | VM-only; calls expect the sandbox-injected OMG_MEDIA_URL proxy |
| App billing | VM-only; the runtime client calls the in-VM agent's /_billing/* routes and has no local dev transport |
These boundaries come from the package transports themselves:
packages/ai/src/index.ts, packages/media/src/index.ts,
packages/server/src/billing.ts, and packages/server/src/triggers.ts.
Auth on localhost
deriveAppId() only recognizes deployed hosts such as
<slug>.apps.omg.dev; on localhost it returns null. The auth provider then
falls back to "local", and POST https://auth.omg.dev/token returns 404
because lazy app registration is only allowed from omg.dev or an omg app
origin.
Set the app id explicitly to a slug you own:
VIBES_APP_ID=my-existing-slug bun run devThe Vite plugin injects that value into the page, so the SDK requests a token
for the real app rather than "local". The relevant code is
packages/sdk/src/auth/client.ts (deriveAppId) and
packages/vite-plugin/src/index.ts (readPreviewAppId).
The deploy path
The forthcoming CLI uses the existing deploy pipeline; local source does not introduce a second build or artifact type:
- Create an owner-scoped sandbox through
POST /v1/sandboxes. - Upload the local working tree into
/home/user/project. - Call
POST /v1/sandboxes/{id}/tarball. The sandbox stays running while infra captures the project tree and returns a durable snapshot withkind: "files_only". - Send its
idassnapshotIdtoPOST https://omg.dev/api/cli/apps/deploy. - Control-plane registers a project and run, then calls the same
projectDeploys.publishForUserpath used by dashboard publishing. - Infra forks a short-lived builder from the source snapshot, runs the normal
production build asynchronously, and publishes either static assets or a
serverful build artifact. The client follows the build through
GET /api/cli/apps/status?slug=....
The snapshot from step 3 is a source carrier. It is not the production
artifact. For serverful apps, deploys.artifactId identifies the build output
created in step 6 and retained for runtime creation and cold-start restore.
See Deploy flow for the builder, static/serverful split, atomic flip, and rollback details.
Endpoint contracts
POST /v1/sandboxes/{id}/tarball returns the standard snapshot shape. The
fields relevant to deploy are:
{
"id": "snapshot-id",
"kind": "files_only",
"sizeBytes": 0,
"tarballSizeBytes": 123456,
"sourceSandboxId": "sandbox-id"
}The control-plane request body is:
{
"name": "My App",
"snapshotId": "snapshot-id",
"projectId": "optional-existing-project-id"
}name is optional and defaults to "My App". snapshotId is required.
projectId is optional on the first deploy. Pass the returned projectId on
later deploys to publish a new run under the same project and slug.
The response is:
{
"slug": "my-app",
"url": "https://my-app.apps.omg.dev",
"status": "building",
"projectId": "project-id",
"runId": "run-id"
}apps/deploy returns after infra accepts the build; it does not hold the HTTP
request open for a terminal state. Poll:
GET https://omg.dev/api/cli/apps/status?slug=my-app
Authorization: Bearer <user-jwt>The status response includes status, buildError, phase, buildLog,
static, version, runId, and updatedAt. A client should only report a
successful deploy after status becomes "ready" and should surface
buildError when it becomes "failed".
The future CLI is expected to persist the returned project binding in
.omg/project.json; that behavior is documented in
control-plane/lib/cli-deploy.ts, but no public CLI flags or commands are
promised yet.
Why deploy goes through control-plane
The two hosts intentionally accept different credentials:
| Surface | User JWT (aud=vibes) | omg_sk_ key | Service JWT (aud=vibes-infra) |
|---|---|---|---|
/v1/sandboxes* on infra | Yes | Yes | Yes, with X-On-Behalf-Of for owner-scoped calls |
/v1/deploys, /v1/models, /v1/messages on infra | No | No | Yes |
/api/cli/apps/deploy on control-plane | Yes | No | Internal service credential not required |
A signed-in client can mint the user JWT with
POST https://auth.omg.dev/token and { "appId": "vibes" }; the session
cookie identifies the user. That JWT can own the temporary sandbox and call
the control-plane CLI surface, but it cannot call infra's service-only deploy
route directly. Control-plane is the single owner of the privileged publish
hop and supplies the user identity to the existing publish pipeline.
The auth boundary is implemented in apps/auth/src/routes/token.ts,
apps/infra/internal/api/server.go, apps/infra/internal/auth/middleware.go,
and control-plane/lib/cli-auth.ts.