Commands
Lifecycle commands for build, release, run, and post-start stages. Each value is a string (shorthand) or an object with command, optional timeout, and optional capture.
Platforms execute well-known commands in this order: build → release → start → (bootstrap on user request). The seed, test, and bootstrap commands are invoked on demand, not as part of the standard deploy lifecycle.
| Stage | Purpose | When |
|---|---|---|
build |
Install dependencies, compile — artifact prepare | Every deploy |
install |
Prepare from source (deps, codegen) — the source-mode pair of build |
Source-mode launches, on demand. See Source-mode commands below. |
release |
Migrations, cache clear, asset compilation | Every deploy, after build |
start |
Start the application — artifact run | Every deploy, after release |
dev |
Start the application from source — the source-mode pair of start |
Source-mode launches; preferred over start when running from source, ignored by artifact providers. See Source-mode commands below. |
bootstrap |
Post-start setup that must run against a running component: create the first admin user, generate an initial invite link, write runtime config that depends on the deploy URL | On demand after start (user-invoked, re-runnable, non-deploy-failing). See Bootstrap stage below. |
seed |
Seed the database with initial data | On demand (first deploy or explicit trigger) |
test |
Run the test suite | On demand (CI or explicit trigger) |
Additional named commands are allowed and invoked on demand.
commands:
build: "npm install"
release: "npx prisma migrate deploy"
start: "node server.js"
test: "npm test"With timeout:
commands:
release:
command: "npx prisma migrate deploy"
timeout: "5m"Source-mode commands
Lifecycle commands run in the context of the built artifact — start is what the production image runs, build produces that artifact. Running the app from source on a developer machine is a different execution context: the artifact's entrypoint may be a compiled binary absent from the source tree, and the source tree has dev affordances (hot reload, unbundled assets) the artifact doesn't. Execution mode — source vs. artifact — is a distinct axis from deployment environment (DESIGN.md D-37 / L-3): "dev" is a mode, not an environment.
Two well-known keys name the source-mode commands, and one optional component field names where they run:
| Key | Pairs with | Role | Example |
|---|---|---|---|
install |
build |
source-mode prepare | "bun install" |
dev |
start |
source-mode run | "bun run dev" |
source (component field) |
— | working directory for install / dev (defaults to build.context, then repo root) |
"./apps/api" |
Only prepare and run are mode-aware. release, bootstrap, seed, and test are mode-invariant — the same command runs whether you launch from source or from the artifact. A path or binary that genuinely differs by mode (a cache dir, a repo-local CLI) belongs in storage: / env / the provider's PATH, not a separate command.
Resolution (per component). A provider selects one mode for the launch (launchfile dev → source, launchfile up → artifact) and resolves each component:
- Run, by precedence
dev>image>start: adevcommand runs the component from source; animagekeeps it in artifact mode unlessdevoverrides it; a barestartruns from source only when there is noimage— so a prebuilt image is never replaced by astartthat assumes the image's internals (fallbacks must be detectably safe). - Prepare: source mode runs
install ?? build, on demand (first launch or a detected dependency/lockfile change), not on every run; artifact mode runsbuild(or pullsimage).
Providers that execute the built artifact (Docker, Kubernetes, cloud platforms) ignore install and dev. The values are ordinary command values — string shorthand or the expanded form with timeout and capture.
A Launchfile that declares both is launchable in either mode from the same file:
components:
api:
source: ./apps/api # cwd for install/dev
image: ghcr.io/acme/api:1.4 # artifact run (ENTRYPOINT = compiled binary)
commands:
install: "bun install" # source prepare
dev: "bun src/index.ts --port $PORT" # source run
bootstrap: "api-cli create-admin --url $app.url" # mode-invariantlaunchfile up pulls and runs the image; launchfile dev runs bun install (on demand) then bun src/index.ts from ./apps/api, ignoring the image. bootstrap is identical in both modes. Source mode is a deliberately narrow carve-out: it expresses that the same lifecycle intent needs a different command line from source than from the artifact — it is not an environment-override mechanism (staging vs. production config remains an orchestrator concern, DESIGN.md L-3).
Bootstrap stage
The bootstrap stage is for imperative post-start setup that can only run against a running component. Typical uses:
- Creating the first admin user via a CLI that's only available inside the container
- Generating a one-time invite link or password reset token the user needs to see
- Writing runtime configuration whose values depend on the deployment's public URL (via
$app.url) and must be picked up by an already-running process
Unlike release, which runs before start in an ephemeral container and fails the whole deploy on error, bootstrap runs after start against the running component, is invoked on user request (not automatically after deploy), is re-runnable, and failures are reported rather than deploy-failing. It is the place to encode operational knowledge that today lives in READMEs ("after deploying, run docker exec … <app-cli> create-admin").
Bootstrap commands should be written to be idempotent — running them a second time should either no-op or produce a new one-time credential, not corrupt state. The spec recommends idempotency; it does not enforce it.
commands:
start: "node server.js"
bootstrap:
command: "my-app-cli create-admin --url $app.url"
capture:
invite_link:
pattern: "https?://\\S+"
description: "One-time invite link"
sensitive: true
### Command Capture
Any command that uses the expanded form can declare **captures** — named values extracted from the command's stdout via regex patterns. The platform matches each capture's regex against the command's stdout and stores the first capture group's value (or, if the pattern has no capture group, the full match).
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `pattern` | `string` | **yes** | -- | Regex matched line-by-line against the command's stdout |
| `description` | `string` | no | -- | Human-readable description of the captured value |
| `sensitive` | `boolean` | no | `false` | If `true`, value is masked in API/UI unless explicitly revealed |
Captured values are surfaced by the platform under a `$outputs.*` namespace and made available through the platform's API or UI. If a pattern does not match any line, the capture is absent (not an error).
Capture is most commonly used with `release` (for migration-time generated values) and `bootstrap` (for post-start admin creation and invite flows), but is allowed on any command that uses the expanded form:
```yaml
commands:
release:
command: "./setup.sh"
capture:
admin_password:
pattern: "Admin password: (.+)"
description: "Generated admin password from initial setup"
sensitive: true
admin_url:
pattern: "Dashboard: (https?://\\S+)"
description: "URL to the admin dashboard"
bootstrap:
command: "my-app-cli create-invite --url $app.url"
capture:
invite_link:
pattern: "https?://\\S+"
description: "One-time invite link — open in a browser to register"
sensitive: trueNote on D-23 placement supersede. Earlier versions of this spec documented capture via a top-level
outputs:field at component level. That placement has been superseded by the nestedcapture:form documented above. The capture mechanism (pattern/description/sensitive) is preserved verbatim; only the location of the capture block in the schema has changed. See DESIGN.md D-34 for the migration rationale.