Deploy locally with Aspire
Goal: run your whole NetScript workspace on one machine under .NET Aspire — scaffold the AppHost, bring up the resource graph (Postgres, Redis, every service and background processor), and watch it from the Aspire dashboard. This is the local orchestration companion to the Deploy recipe (which covers shipping to a remote target); for why the AppHost works the way it does, read Orchestration with Aspire.
Prerequisites
| Name | Type | Description |
|---|---|---|
A scaffolded workspace |
netscript init |
Created WITHOUT --no-aspire, so the aspire/ AppHost folder exists. See the CLI reference for init flags. |
Docker daemon running |
container engine |
Aspire provisions Postgres and Redis as local Docker containers. No daemon = the default workflow does not start. |
The Aspire CLI |
aspire (external) |
The aspire restore / aspire start commands are the external .NET Aspire CLI, run from inside aspire/ — not netscript subcommands. |
A reachable port range |
ports |
Dashboard :18888 (HTTPS) / :18889 (HTTP), OTLP :4318, services from :3000, plugin APIs :8091–8099, the Fresh app :8010. |
Step 1 — Confirm the AppHost was scaffolded
The AppHost is a small TypeScript/Node program (not C#) at aspire/apphost.mts, configured by
aspire/aspire.config.json. netscript init generates it; you do not hand-write it. Verify it is
on disk before going further:
# From the workspace root — these two files are the orchestration entry point.
ls aspire/apphost.mts aspire/aspire.config.json
aspire.config.json pins language: "typescript/nodejs", appHost.path: "apphost.mts", and the
Aspire SDK version 13.4.6. The graph inside the AppHost is derived from your installed
plugins at boot via composeAppHost (from @netscript/aspire/application) — add a plugin and
its API plus background processor appear in the graph; remove it and they vanish, no edit to
apphost.mts required. The mechanics are in Orchestration with Aspire.
Step 2 — 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):
# Run from inside the aspire/ folder. One-time SDK restore.
cd aspire
aspire restore
Step 3 — Start the resource graph
A single aspire start translates appsettings.json plus the 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. The
graph a single run stands up:
| Name | Type | Description |
|---|---|---|
aspire (dashboard) |
https://localhost:18888 / http://localhost:18889 |
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 (http/protobuf) the dashboard runs; framework spans and structured logs land here automatically. |
postgres |
Container |
Provisioned via Docker. The database netscript db commands target — reachable only once Aspire is up. Postgres is the default; pass --db mysql or --db mssql at init for those engines (each also an Aspire container), or --db sqlite for a file-backed database with no container. |
redis |
Container (cache) |
Redis cache — the default --cache-backend; Redis-compatible. Backs KV/queue workloads for the runtime plugins. Swap it for garnet (also a container) or app-level deno-kv via --cache-backend. |
users (example service) |
:3000+ (SERVICE range, OS-allocated from 3000) |
The scaffolded oRPC service, when you init with --service. OpenAPI at /api/v1/users/* and RPC at /api/rpc/*. |
plugin APIs |
:8091–8099 (PLUGIN_API range) |
Each installed runtime plugin's HTTP API (workers, sagas, triggers, auth). Ports are allocated from the plugin range, not hardcoded. |
background processors |
executables (no port) |
Each plugin's isolated runners (workers, sagas, triggers) — separate processes, not threads inside the API. |
dashboard (Fresh app) |
:8010 |
The scaffolded Fresh frontend, when present (the app range start 8000 + 10). |
Step 4 — Initialize the database (through the running AppHost)
With Aspire up, your database engine is live and the netscript db commands can reach it. The
resource graph above shows Postgres — the default and the engine these recipes use; if you
scaffolded with --db mysql / --db mssql you get that container instead, and --db sqlite
gives a file-backed database with no Aspire container. Run the commands from the
workspace root (a second terminal — leave aspire start running in the first):
# From the workspace root, with `aspire start` still up in another terminal.
netscript db init --name init # create + apply the first migration
netscript db generate # generate the Prisma client
netscript db seed # optional: seed development data
These talk to the Postgres container Aspire provisioned. Outside Aspire (an isolated CI container,
say), point them at your own database via POSTGRES_URI / DATABASE_URL instead — covered in
Step 5 — Use the dashboard
Open https://localhost:18888, paste the login token aspire start printed, and you have a single
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 background processor is one click away, not buried in a terminal. |
Structured logs + Traces |
tab |
Spans and structured logs your handlers emit, correlated by traceparent across services. Aspire collects them via the OTLP endpoint at :4318. |
Because Aspire starts each resource with its OTEL_SERVICE_NAME and an OTLP endpoint pointed at
http://localhost:4318, framework-level spans (job dispatch, job execution, scheduler runs)
surface here with no extra wiring. See
Observability
for the framework-vs-scaffold span boundary.
In-production pitfalls
See also
-
Why it works this way: Orchestration with Aspire
— the AppHost, plugin contributions, two-pass reference resolution, and the dashboard.
-
Ship it remotely: Deploy — the production companion: deployable units, backing services, and the
--no-aspirepath. -
The database sequence: Database and
-
Exact symbols + full port map: the Aspire reference
and the CLI reference .