Provisioning
import { Aside } from ‘@astrojs/starlight/components’;
Provisioning is the multi-step pipeline that turns
vantage websites create into an actually deployed site. It runs
asynchronously on a worker Lambda, persists per-step progress to DynamoDB,
and retries on failure.
Why an orchestrator instead of inline?
Section titled “Why an orchestrator instead of inline?”Creating a Website touches GitHub, Vercel, and (optionally) several other vendors. End-to-end it takes 60-90 seconds for a basic site, longer with features. That doesn’t fit in an API request.
Splitting the work into a SQS-queued job has two benefits:
- The API responds immediately — callers get a
websiteIdthey can poll - The job is idempotent per-step and resumable on failure — if
vercel_create_projectfails because Vercel was 503, the job retries that step without redoing the GitHub steps
The pipeline
Section titled “The pipeline”github_create_repo ↓github_invite_collaborator ↓vercel_create_project ↓openrouter_create_key (only if features.openrouter) ↓resend_create_domain (only if features.resend) ↓vercel_set_env_vars ↓vercel_initial_deploy ↓finalize (flips Website.status to "active")Each step is one function in packages/api/src/provisioning/orchestrator.ts.
Tracking job state
Section titled “Tracking job state”Every job is one row in vantage-provisioning-jobs:
type ProvisioningStep = | "github_create_repo" | "github_invite_collaborator" | "vercel_create_project" | "openrouter_create_key" | "resend_create_domain" | "vercel_set_env_vars" | "vercel_initial_deploy" | "finalize";
interface ProvisioningJob { jobId: string; websiteId: string; status: "queued" | "running" | "succeeded" | "failed"; /** The step the worker is currently inside (omitted once the job ends). */ currentStep?: ProvisioningStep; /** Steps already finished, in the order they completed. */ completedSteps: ProvisioningStep[]; /** Per-step failures — usually empty; populated when a step throws. */ errors: Array<{ step: ProvisioningStep; message: string; at: string }>; startedAt: string; completedAt?: string;}The portal’s Blueprint canvas polls this so each node turns green
as the corresponding step name lands in completedSteps — gives
you the visual progress indicator.
Reading job state
Section titled “Reading job state”const { jobs } = await vc.websites.jobs("wb_01J7...");for (const j of jobs) { console.log(j.status, "currently:", j.currentStep, "done:", j.completedSteps);}Or via REST: GET /v1/websites/:id/jobs.
Failure modes
Section titled “Failure modes”Step fails transiently (vendor 5xx, network blip):
The worker retries the step up to N times with exponential backoff.
Job stays running.
Step fails permanently (4xx, validation error):
A { step, message, at } entry is appended to errors, the job’s
status flips to failed, and the Website’s status flips to failed
too. You can delete the Website and start fresh, or fix the underlying
issue and re-trigger (today: by deleting + recreating; future: a retry
endpoint).
Worker crashes mid-step:
SQS visibility timeout expires, message redelivered, worker picks
up where it left off. Steps already in completedSteps are skipped.
This is why steps must be idempotent — creating a GitHub repo
that already exists should not be an error.
Feature toggles also run the orchestrator
Section titled “Feature toggles also run the orchestrator”When you call vantage websites features <id> --openrouter:
- API updates the
featuresflag on the Website row - API enqueues a feature-delta job
- Worker compares before/after, runs only the steps for changed features:
openrouter: false → true→openrouter_create_keyresend: true → false→resend_teardown_domain
The full pipeline is only run on create. Feature toggles are mini-pipelines.
Teardown follows the same pattern
Section titled “Teardown follows the same pattern”Deleting a Website enqueues a teardown job that runs the per-service
teardown handlers in packages/api/src/provisioning/teardown.ts.
Same observability, same retry semantics, same per-step state in
the jobs table.
See also
Section titled “See also”- Services — what gets created during provisioning
- How it fits together — where the worker Lambda sits in the system