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"

Failure semantics

Every command has exactly one failure disposition, keyed on the slot it fills — not on its name. Keying on the name would be ambiguous, because D-38 lets one command fill a slot in either mode: source prepare resolves install ?? build, and source run resolves dev ?? start, so build and start each appear in two modes.

Slot Filled by On failure
prepare artifact mode: build · source mode: install ?? build Fails the invocation — the deploy when deploying, the session when running from source. There is nothing to run either way.
release release Fails the deploy. release runs after the component's required resources are provisioned and ready, and before the run slot — so a failed migration never serves traffic.
run artifact mode: start · source mode: dev ?? start Fails the invocation — the component did not come up.
bootstrap bootstrap Reported to the invoker — never affects deploy status.
on-demand seed, test, custom commands Reported to the invoker — never affects deploy status.

"Fails the invocation" is what makes the table non-overlapping: the prepare and run slots fail whatever asked for them. A deploy (up) fails; a source-mode session (dev) fails that session and leaves deployed state untouched. A component declaring only start: — no dev, no image — fills the run slot in source mode and is covered exactly once.

Timeout expiry is a failure with the same disposition as any other failure of that slot: a prepare or release that exceeds its timeout fails the invocation; a bootstrap that exceeds its timeout is reported.

A health check is not a command slot, but it has durations and therefore a disposition: a component that never becomes healthy fails the invocation, and an unparseable health duration fails it too — the provider surfaces the error rather than substituting a default, as it does for any other duration.

Command interpretation

A command string is interpreted by a POSIX shell. Shell features — &&, ||, ;, pipes, redirection, variable expansion, grouping — are available and are what authors write today; catalog/apps/paperclip's bootstrap is a multi-statement script and depends on it.

This is stated because it was ambiguous and the reference providers diverged on it: a provider that splits the string on whitespace and executes argv[0] directly will fail on any command using those features, and will fail confusingly — attempting to execute a binary whose name is the first token.

A provider that cannot offer a shell MUST report the command as unhonored (§10.8) rather than attempt a best-effort split.

Durations

Every duration in a Launchfile uses one grammar:

^(\d+)(ms|s|m|h)$

An integer immediately followed by exactly one unit — "500ms", "30s", "5m", "2h". No internal whitespace, no compound values ("1m30s"), no fractions. The grammar governs commands.*.timeout and the Health durations (interval, timeout, start_period).

An unparseable duration is surfaced as a non-fatal warning by validate. A provider MUST NOT silently substitute a default for an unparseable duration — it surfaces the error (see PROVIDERS.md §10). Numeric defaults for absent durations are provider-side: the spec does not mandate execution budgets, and each provider documents its own.

Source-mode commands

Lifecycle commands run in the context of the built artifactstart 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: a dev command runs the component from source; an image keeps it in artifact mode unless dev overrides it; a bare start runs from source only when there is no image — so a prebuilt image is never replaced by a start that 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 runs build (or pulls image).

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-invariant

launchfile 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: true

Note 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 nested capture: 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.

esc
Type to search the docs