Deploy locally
You have built every layer of the team workspace: scaffold, an identity layer, isolated team data, a
provisioning job that never blocks the caller, and routes that fail closed. This final chapter runs
the whole thing as one coherent system — the point where the app stops being a set of chapters and
becomes something a team could actually sit inside. A single aspire start stands up Postgres, the
Redis cache, your workspace service, the auth-api service on :8094, the Workers API, and every
background processor — all wired together and visible in one dashboard. It is the
local story, and this chapter is precise about exactly that: a complete observable stack on one
machine, not a production deployer.
The one-command reach is the differentiator, and you hand-wrote none of it: netscript init
generated the AppHost in Chapter 1, each plugin you installed since — auth, then workers —
contributed its own service and processor to the resource graph, and one aspire start boots the
whole thing with cross-references resolved into injected environment. Scaffold to a running,
observable stack, with no orchestration wiring you maintain by hand.
What you will build
Your complete my-workspace/ running under one Aspire AppHost: every service, plugin API, background
processor, and backing container in a single resource graph, observable from the dashboard on :18888
— including the :8094 auth service you added in chapter 2. By the end you can read the whole running
topology, including the exact port each resource bound, from one pane.
Before you begin
You need everything from chapters 1–5: the scaffolded workspace, the auth plugin, the second
database, the workers plugin and provision-member job, and the guarded workspace service. The
AppHost was scaffolded by netscript init in chapter 1 (you did not pass --no-aspire). Confirm the
orchestration entry point is on disk and the workspace type-checks:
# From the workspace root
ls aspire/apphost.mts aspire/aspire.config.json
deno task check
Step 1 — Restore the AppHost SDK (once)
The AppHost runs on its own isolated Node runtime inside aspire/, so its dependency graph never leaks
into your Deno workspace. Restore that runtime once per machine (and after an SDK bump):
cd aspire
aspire restore
Step 2 — Start the whole graph
A single aspire start translates appsettings.json plus your plugin contributions into a coherent
resource graph and boots all of it — infrastructure first, then services, plugin APIs, and background
processors, with cross-references resolved into injected environment variables:
# Still inside aspire/. Boots the whole graph; prints the dashboard URL + a login token.
aspire start
When boot finishes, aspire start prints the dashboard address and a one-time login token. This is the
graph your complete app stands up:
| Name | Type | Description |
|---|---|---|
aspire (dashboard) |
https://localhost:18888 |
The Aspire dashboard — live resource list, console logs, structured logs, and traces. A login token is printed on start. |
OTLP collector |
http://localhost:4318 |
OpenTelemetry endpoint; framework spans (job dispatch/execution, scheduler runs) land here automatically. |
postgres |
Container |
The primary datasource — the auth.prisma migration from chapter 2 lives here. |
workspace (second db) |
Container |
The isolated workspace datasource from chapter 3, provisioned alongside the primary. |
redis |
Container (cache) |
Redis cache — the default --cache-backend; Redis-compatible. Backs KV/queue workloads and the kv-oauth session store. |
workspace (service) |
:3001 |
Your guarded oRPC service from chapter 5 (note: this tutorial assumes port 3001; in unpinned scaffolds, each project is allocated its own randomized high-range ports) — /api/workspace requires a scoped principal, /health stays public. |
auth-api |
:8094 (pinned in chapter 2) |
The auth plugin's service from chapter 2 — /api/v1/auth/* (signin, callback, signout, session, me). |
workers-api |
allocated port |
The Workers API from chapter 4 — triggers and inspects the provision-member job. |
background processors |
executables (no port) |
The workers processor that drains the job queue — a separate process, not a thread in the API. |
Step 3 — Use the dashboard
Open https://localhost:18888, paste the login token aspire start printed, and you have one pane over
the running graph:
| Name | Type | Description |
|---|---|---|
Resources |
tab |
Every container and executable above with status, endpoints, and the resolved environment. The authority for which port each resource bound. |
Console logs |
tab |
stdout/stderr per resource — a failing auth-api or workers processor is one click away. |
Structured logs + Traces |
tab |
Spans and structured logs correlated by traceparent across services — collected via the OTLP endpoint at :4318. |
Verify your progress
With the graph up, walk the whole app end to end — the auth service, the guarded route, and the job:
# Auth service is up (chapter 2)
curl http://localhost:8094/health/ready
# The guarded route rejects an anonymous caller (chapter 5)
curl -i http://localhost:3001/api/workspace # 401 UNAUTHORIZED
# ...and allows a correctly-scoped one
curl -i -H 'authorization: Bearer read' http://localhost:3001/api/workspace # 200
# The Workers API is live (chapter 4)
curl <workers-endpoint>/api/v1/workers/jobs # provision-member appears
- [ ]
aspire restoreandaspire startsucceed from insideaspire/. - [ ] The dashboard on
:18888listspostgres,workspace(db),redis,workspace(service),auth-api, andworkers-api— all green. - [ ]
curl http://localhost:8094/health/readysucceeds. - [ ] An anonymous
GET /api/workspacereturns401;Bearer readreturns200. - [ ]
GET /api/v1/workers/jobslistsprovision-member.
What you built
The complete authenticated team-workspace backend, running locally as one orchestrated system:
Postgres, an isolated workspace database, Redis, the guarded workspace service, the auth-api
auth service on :8094, and the Workers API and its processor — all in one Aspire resource graph,
observable from one dashboard. You walked the whole arc: a pluggable auth backend, a session, team
data on its own catalog datasource, off-path provisioning, and a route-authz seam that fails closed —
single-tenant by design, with org scoping an explicit app-level extension. The off-boarded contractor
gets a 401; the engineer you paged gets provisioned without anyone waiting on the write.
Where to go next
- Ship it remotely → Deploy — the production companion: deployable units,
managed backing services, and the
--no-aspirepath. - Go deeper on auth → Authentication capability and The authentication model.
- Understand the orchestrator → Orchestration with Aspire.
- Browse more recipes → the how-to guides.