BuildService Port & Frontend Strategy¶
§16.3 BuildService Port Interface¶
type BuildService interface {
EnsureProject(ctx context.Context, workspaceID string) (ProjectRef, error)
SubmitBuild(ctx context.Context, spec BuildSpec) (*BuildHandle, error)
StreamLogs(ctx context.Context, handle BuildHandle) (<-chan LogLine, error)
GetBuildStatus(ctx context.Context, handle BuildHandle) (*BuildStatus, error)
CancelBuild(ctx context.Context, handle BuildHandle) error
}
EnsureProject is idempotent. It creates a Depot project on first call for a workspace; subsequent
calls return the existing project reference. This enforces per-customer cache isolation (a Depot
requirement for building untrusted code).
§16.4 Per-Customer Isolation¶
Depot requires one project per customer entity to prevent cache poisoning between tenants. Starform maps this 1:1 to workspaces (not users, not projects — workspaces):
- On workspace creation, Starbase calls
EnsureProjectand stores the Depot project ID - All builds for services in that workspace run inside that Depot project
- Cache is scoped per workspace (one customer's cached layers cannot be read by another)
- Cache quota per Depot project: 30 GB default, configurable per tier later
§16.5 Frontend Strategy¶
Starform uses Railpack as the primary build frontend, with Dockerfile as the explicit fallback. This matches the git-push-and-deploy UX that Railway, Render, Heroku, and Vercel all provide. Requiring a Dockerfile at the entry point would put Starform in a visibly weaker position on the pricing page and the "getting started" flow — the two surfaces where prospects compare platforms.
Resolution order at build time:
- If the repo root contains a
Dockerfile, use it. Customer intent is explicit; respect it. - Otherwise, run Railpack against the repo. Railpack inspects the source (looks for
package.json,requirements.txt,go.mod,Gemfile, etc.) and emits a BuildKit LLB dependency graph. - If Railpack cannot detect a supported framework, fail the build with a clear error message pointing the customer toward either adding a Dockerfile or adjusting their project structure.
Why Railpack specifically:
- Open source (MIT), Go-based, maintained by Railway
- Successor to Nixpacks with significantly smaller image sizes (38% smaller Node images, 77% smaller Python images per Railway's public benchmarks)
- Emits BuildKit LLB natively — integrates cleanly with Depot's lower-level
BuildKitServiceAPI without requiring a synthetic Dockerfile intermediate step - Same build system Railway uses today, which means the ecosystem of framework detection logic is maintained by the market leader in developer PaaS
- Library-mode integration: Starbase Worker imports Railpack as a Go dependency, calls it in-process. No separate binary, no subprocess orchestration.
Depot integration paths:
- Dockerfile path uses Depot's high-level
BuildService.CreateBuildendpoint with the Dockerfile frontend. Simple, well-supported, matchesdepot buildCLI behavior. - Railpack path uses Depot's
BuildKitServiceendpoint. Railpack generates LLB; Starbase Worker submits LLB directly to Depot's BuildKit endpoint. This bypasses Depot's Dockerfile frontend entirely and runs the build using whatever LLB Railpack produced.
Both paths use the same per-workspace Depot project, so cache isolation and billing work identically.
Vendor risk note
Railpack is maintained by Railway, a direct Starform competitor. The MIT license makes forking possible if Railway ever gates features or changes direction. Starform treats Railpack as a vendored dependency — pinned to a specific version, upgraded deliberately, with a fork always available as insurance.
Cross-references
The MVP flow that exercises both Depot paths → §16.2 · the
BuildService port in the broader port set → Starbase §10 ·
per-workspace isolation maps to the billing boundary workspace_id →
§24.1 · build-minute billing →
§16.7 · known limitations of this design →
§16.8.