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
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.
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) |
// 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:
- Clone — source fetch from Git (typically <10s)
- Build — Depot-executed build with Railpack/Dockerfile (30s–5min)
- Push — image push to the region's Container Registry (10–60s)
- 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 - 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, ordeployingstates - 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 buildcancelled; 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 deploying → Starbase §32.