Deploy locally
You have built every layer: scaffold, auth, workspace data, a provisioning job, and route guards. This
final chapter runs the whole thing as one coherent system. A single aspire start stands up Postgres,
the Redis cache, your workspace service, the auth-api service on :8094, the Workers API on
:8091, 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.
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 — /api/workspace requires a scoped principal, /health stays public. |
auth-api |
:8094 |
The auth plugin's service from chapter 2 — /api/v1/auth/* (signin, callback, signout, session, me). |
workers-api |
:8091 |
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 http://localhost:8091/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
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, workspace
data, off-path provisioning, and a real route-authz seam — single-tenant by design, with org scoping an
explicit app-level extension.
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.