Skip to main content
Alpha

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.

  1. 1 · Scaffold
  2. 2 · Auth
  3. 3 · Workspace data
  4. 4 · Provision job
  5. 5 · Route authz
  6. 6 · Deploy

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:

The local resource graph aspire start brings up
NameTypeDescription
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:

Aspire dashboard surfaces
NameTypeDescription
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 restore and aspire start succeed from inside aspire/.
  • [ ] The dashboard on :18888 lists postgres, workspace (db), redis, workspace (service), auth-api, and workers-api — all green.
  • [ ] curl http://localhost:8094/health/ready succeeds.
  • [ ] An anonymous GET /api/workspace returns 401; Bearer read returns 200.
  • [ ] GET /api/v1/workers/jobs lists provision-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