Skip to content

Build Lifecycle & UX

Build state machine:

stateDiagram-v2
  direction LR
  [*] --> pending
  pending --> building
  building --> built
  built --> deploying
  deploying --> deployed
  pending --> cancelled
  building --> cancelled
  building --> failed
  deploying --> cancelled
  deploying --> failed
  deployed --> [*]
  failed --> [*]
  cancelled --> [*]

  classDef ok fill:#0EAB5D22,stroke:#0EAB5D,color:#0EAB5D
  classDef alert fill:#EC385622,stroke:#EC3856,color:#EC3856
  classDef muted fill:transparent,stroke:#808080,color:#808080
  class deployed ok
  class failed alert
  class cancelled muted
Diagram — Build state machine (§16.12). Happy path pending → building → built → deploying → deployed; failed and cancelled are terminal (previous version stays live).

State transitions:

State Meaning
pending build job enqueued, not yet picked up by worker
building Depot has accepted the build; Starbase Worker streaming logs
built image pushed to DO Container Registry, image ref recorded
deploying desired state updated; Shuttle detecting and applying
deployed Starbase flips to deployed when the §25.4 status report shows ≥1 ready pod on the new image digest (first_ready_at, FR-072)
failed terminal; error message recorded — build error, or rollout failure reported via §25.4 (ProgressDeadlineExceeded / crash loop); previous version remains live
cancelled terminal; user-initiated or superseded by debounce

Tables

builds carries the artifact and the state machine above. deployments is a build released to one service-environment — its lifecycle is driven by §25.4 status reports (FR-072). Rollback = insert a new deployment row pointing at an old build; history stays immutable. The deploying/deployed states above describe a build's original release; re-releases live on their own deployment rows.

Delivery · builds + deployments · Starbase Postgres
builds (
    id              UUID PRIMARY KEY,
    service_id      UUID NOT NULL REFERENCES services(id),
    environment_id  UUID REFERENCES environments(id),   -- env whose branch triggered it (§16.11)
    commit_sha      TEXT NOT NULL,
    commit_message  TEXT,
    branch          TEXT,
    status          TEXT NOT NULL CHECK (status IN
                    ('pending','building','built','deploying','deployed','failed','cancelled')),
    image_ref       TEXT,                    -- registry image@sha256 digest (§16.2)
    depot_build_id  TEXT,                    -- resume/re-poll handle (job queue, §14)
    log_object_key  TEXT,                    -- DO Spaces archive: <build_id>.log.zst (§16.6)
    error           TEXT,
    started_at      TIMESTAMPTZ,
    finished_at     TIMESTAMPTZ,
    created_at      TIMESTAMPTZ DEFAULT NOW()
);

deployments (
    id                     UUID PRIMARY KEY,
    service_environment_id UUID NOT NULL REFERENCES service_environments(id) ON DELETE CASCADE,
    build_id               UUID NOT NULL REFERENCES builds(id),
    status                 TEXT NOT NULL CHECK (status IN ('deploying','deployed','failed','superseded')),
    triggered_by           UUID REFERENCES users(id),   -- NULL = webhook/auto
    first_ready_at         TIMESTAMPTZ,                 -- §25.4 → FR-072 deployed trigger
    created_at             TIMESTAMPTZ DEFAULT NOW()
);

ALTER TABLE service_environments
    ADD CONSTRAINT fk_current_deployment
    FOREIGN KEY (current_deployment_id) REFERENCES deployments(id);

API endpoints

Conventions (auth, errors, Developer* notation) → Dashboard API.

Method Path Permission Purpose
GET /services/{id}/builds Viewer Build history
POST /services/{id}/builds Developer* Manual build ({branch} or {sha})
GET /builds/{id} Viewer Build + state
POST /builds/{id}/cancel Developer* Cancel (semantics above)
GET /builds/{id}/logs Viewer Live SSE relay during the build; DO Spaces archive replay after (§16.6)
GET /service-environments/{id}/deployments Viewer Deployment history
POST /service-environments/{id}/deployments Developer* Deploy — and rollback (payload below)
GET /deployments/{id} Viewer Deployment + rollout state (FR-072)
Deploy / rollback · POST /service-environments/{id}/deployments → 202
// request — a new build's id deploys it; an OLD build's id IS the rollback
{ "build_id": "0d9f…" }

// 202 response — the deployment resource; poll GET /deployments/{id}
{
  "id": "a41c…",
  "service_environment_id": "…",
  "build_id": "0d9f…",
  "status": "deploying",          // → deployed | failed, driven by §25.4 reports (FR-072)
  "first_ready_at": null,
  "created_at": "2026-07-11T14:03:00Z"
}

Progress visibility:

The dashboard surfaces five distinct progress stages for each deployment, each with its own log stream and timing:

  1. Clone — source fetch from Git (typically <10s)
  2. Build — Depot-executed build with Railpack/Dockerfile (30s–5min)
  3. Push — image push to the region's Container Registry (10–60s)
  4. Pre-deploy — only when configured: the one-shot migration Job (§20.3), with its own log stream; failure ends the deployment here (PreDeployFailed) with the old version still serving
  5. Deploy — Shuttle rollout with readiness checks (30s–2min), tracked live via §25.4 status reports

Total target: <5 minutes for typical applications.

Rollout & error-handling semantics

  • Zero-downtime: every Deployment rolls with maxSurge: 1, maxUnavailable: 0 — a new pod must be ready (per the §20.3 health-check mapping) before an old one dies (FR-018).
  • Deploy-during-deploy: newest wins — the in-flight deployment's status becomes superseded; Shuttle converges on the latest desired state. A running pre-deploy Job is cancelled first.
  • Rollback pins nothing: rolling back is deploying an old build_id (endpoints above); the next push auto-deploys normally.
  • History & registry GC: build/deployment rows are kept forever; registry images are GC'd by a Worker cleanup job — currently-deployed + the last 10 builds per service stay pullable (the rollback window); older rollbacks rebuild from the commit.
  • Timeouts: build 45 min (Worker watchdog) · pre-deploy Job 600 s · rollout progressDeadlineSeconds: 600 (§20.3).
  • Retries: deterministic build failures (compile errors) are never auto-retried; infra-classed failures (Depot 5xx, clone/network errors) ride the §14 job-lease machinery (max_attempts).
  • Image size: no hard limit at MVP; the dashboard warns above ~5 GiB (pull latency).
  • Log archive: a failed DO Spaces upload (§16.6) never fails the build — warn + async retry.

User-initiated cancellation:

  • Customer clicks "Cancel" during pending, building, or deploying states
  • Starbase calls BuildService.CancelBuild (Depot stops the build)
  • Starbase updates state to cancelled; no charge for the build
  • If cancel arrives during deploying, Starbase reverts the desired image and marks the build cancelled; Shuttle applies the revert on its next tick (≤30s, §32). The previous ReplicaSet is still live — the new one never fully rolled out — so the revert lands as a fast rollback of an in-flight rollout. (There is no push "signal" channel; none is needed.)

Post-deploy credential display:

After a database or bucket is successfully provisioned, the service detail page displays connection strings and credentials in a "Connection Info" panel. This is environment-scoped and subject to RBAC (§15) — Developers see full creds in non-protected envs; in protected envs they see masked values unless they have Project Admin role.


Cross-references

The built → deploying handoff to Shuttle's reconcile loop → §16.2 · the BuildService.CancelBuild contract → §16.3 · debounce that supersedes a build into cancelled§16.11 · the RBAC roles gating credential display → Starbase §15 · the desired-state update behind deployingStarbase §32.