Skip to main content
Alpha

Deploy a NetScript workspace

Goal: take the workspace you scaffolded with netscript init and run it somewhere other than your laptop — a container host, a VM, or a managed platform — with clear expectations about what the scaffold wires for you and what you still own.

This is a task recipe, not a one-click button. NetScript is in alpha, and the scaffold is deliberately minimal about deployment: it gives you a single declarative description of every process (appsettings.json), runnable Deno entrypoints with explicit permissions, the Aspire AppHost that orchestrates them locally and can publish deployment artifacts for targets you configure in it, and starter GitHub Actions workflows for the shipped deployment targets. The CLI has thin deploy routers for Deno Deploy, Docker/Compose, Kubernetes, Azure, and Cloud Run, but it does not generate a Dockerfile, a docker-compose.yml, or a finished cloud infrastructure stack for you — target-specific infrastructure still lives in Aspire and your cloud account, assembled from the verified facts below.

Before you start

You need a working, type-checked workspace and a clear idea of where it is going. If you have not built one yet, start with Quickstart and the Storefront tutorial. Then confirm the workspace is healthy locally before you try to move it:

deno task check    # type-check apps, services, contracts
deno task lint
deno task test

For the orchestration model that underpins everything below — why the AppHost lives in its own aspire/ folder, and how it provisions Postgres and Redis — read the Aspire explanation alongside this recipe.

The mental model: three layers

A NetScript deployment is three layers, and you choose how much of each you keep in production:

1. Backing services

Postgres (the recommended database; mysql, mssql, or sqlite are first-class alternatives via --db) and Redis (KV/cache — the default --cache-backend; garnet and deno-kv are alternatives). In dev, Aspire provisions Postgres/MySQL/SQL Server as containers (sqlite is file-backed, no container) and Redis as a container. In production you bring your own — managed database, managed Redis-compatible cache.

2. NetScript processes

Each API service, plugin service, background processor, and the Fresh app is one Deno process with an entrypoint, a port, and an explicit permission set — all declared in appsettings.json.

3. Orchestration

Aspire's AppHost wires the graph together locally. In production you can keep Aspire (it can publish a deployment manifest) or drop it and run each process yourself with your own supervisor.

Step 1 — Know your deployable units (appsettings.json)

appsettings.json is the single source of truth for what runs. The CLI writes it during netscript init and updates it as you netscript plugin install. Every Aspire resource — and every process you would deploy by hand — is described there. From a workspace with the four first-party plugins installed, the graph looks like this:

Resources declared in appsettings.json (verified from a scaffolded workspace)
NameTypeDescription
users service · :3000 (the scaffold default; the exact port is OS-allocated from the SERVICE range starting at 3000) Example oRPC service. Entrypoint src/main.ts, runtime deno. RPC mounts under /api/rpc/*.
streams plugin · :4437 durable-streams runtime service. RequiresDb=false, RequiresKv=false. Real producer runtime — see Step 6.
workers-api plugin · :8091 Workers API. Requires DB + KV. References streams.
sagas-api plugin · :8092 Sagas API. Requires DB + KV. References workers-api, streams.
triggers-api plugin · :8093 Triggers API. Typed v1 oRPC contract for trigger/event introspection + management; the webhook ingress endpoint POST /api/v1/webhooks/:triggerId stays a raw HMAC-verifying route by design. Requires DB + KV. References workers-api, streams.
workers / sagas background processor Entrypoint bin/combined.ts. Watch mode + telemetry on. Workers runtime pool via WORKERS_CONCURRENCY; sagas via SAGA_CONCURRENCY.
triggers background processor Entrypoint src/runtime/trigger-processor.ts. Concurrency 10 via TRIGGER_CONCURRENCY.
dashboard app · :8010 Fresh frontend. References service users.
postgres / redis infrastructure Mode=Container in dev. PrimaryDatabase=postgres, PrimaryCache=redis (the default; garnet via --cache-backend garnet).

Each entry carries the exact information a deploy needs: Runtime, Port, Entrypoint, Workdir, RequiresDb/RequiresKv, the Deno Permissions array, the concurrency env var, and PluginReferences (the wiring order). When you containerize or write systemd units, copy these values verbatim — do not guess them.

Step 2 — Build and validate a release artifact

There is no opinionated build step that produces a single bundle — each Deno process runs from source. Your "build" is therefore: cache dependencies, type-check, and (optionally) pre-generate the database client and plugin registries so the container does not do it at boot.

# From the workspace root — prove the graph is healthy before you ship it.
deno task check
deno task lint
deno task test
# Warm the module cache so production start-up does no network fetch.
# Cache each deployable entrypoint you intend to run.
deno cache services/users/src/main.ts
deno cache plugins/workers/services/src/main.ts
deno cache plugins/sagas/services/src/main.ts
deno cache plugins/triggers/services/src/main.ts
# Requires Aspire (and therefore Postgres) up first — see the DB callout below.
# Bake the Prisma client + plugin registries into the artifact so boot is deterministic.
netscript db generate
netscript generate plugins

Step 3 — Provision backing services

In production you do not run Postgres and Redis as throwaway Aspire containers. You provision them as durable, managed resources and hand their connection details to NetScript through environment variables. NetScript reads the database URL from POSTGRES_URI (falling back to DATABASE_URL) and normalizes engine-specific connection strings to a URL — this is handled in database/postgres/prisma.config.ts.

Production environment a NetScript deployment expects
NameTypeDescription
POSTGRES_URI string (url) Primary Postgres connection. DATABASE_URL is the accepted fallback. Read by Prisma config.
REDIS_URI / cache url string Redis-compatible cache endpoint for the redis KV/cache resource (the default backend). With --cache-backend garnet the key is GARNET_URI for the garnet resource (managed Redis or Garnet in prod).
PORT number Per-process listen port. Each service reads it (e.g. Deno.env.get('PORT') ?? '8091') and falls back to its default.
OTEL_EXPORTER_OTLP_ENDPOINT string (url) OTLP collector. Dev defaults to http://localhost:4318 (http/protobuf) via the Aspire dashboard.
NETSCRIPT_SAGA_STORE kv | prisma Durable saga store backend (mandatory when sagas run). Also settable via appsettings sagas.store.backend.
NETSCRIPT_AUTH_BACKEND string Active auth backend if the auth plugin is installed. Default kv-oauth.
WORKERS_CONCURRENCY number Workers runtime process pool size. Current Aspire metadata also emits WORKER_CONCURRENCY, but the runtime honors WORKERS_CONCURRENCY; set the runtime var.
SAGA_CONCURRENCY number Sagas background processor concurrency (default 2).
TRIGGER_CONCURRENCY number Triggers background processor concurrency (default 10).

Step 4 — Choose an orchestration path

This is the real fork in the road. Pick based on whether your target understands Aspire.

# The AppHost is a TypeScript/Node project under aspire/ (apphost.mts).
# Locally it provisions Postgres + Redis and wires every process.
cd aspire
aspire restore   # one-time: restore the TS AppHost SDK
aspire start       # boots the full graph; dashboard at https://localhost:18888

# Aspire 13.x can publish/deploy target artifacts from the same AppHost.
netscript deploy kubernetes plan --project-root .
netscript deploy kubernetes up --project-root .
# Scaffold without the orchestration layer, then run processes yourself.
netscript init my-app --no-aspire

# You now own provisioning Postgres + a cache, and starting each process.
# Bring-your-own-supervisor: systemd, a container per process, or a PaaS.
# Start the Fresh app directly during dev:
deno task --cwd apps/dashboard dev

Aspire cloud targets

The Aspire-backed target routers all share the same shape:

# Generate artifacts without applying them.
netscript deploy kubernetes plan --project-root . --output-dir .deploy/kubernetes

# Apply/provision through the validated AppHost platform integration.
netscript deploy azure-aks up --project-root . --output-dir .deploy/azure-aks

# Tear down the previously deployed target.
netscript deploy azure-aks down --project-root . --output-dir .deploy/azure-aks

For Kubernetes, add the Aspire Kubernetes integration to the TypeScript AppHost (aspire add kubernetes, then builder.addKubernetesEnvironment('k8s')). Use publishAsKubernetesService(...) in aspire/apphost.mts for per-service manifest customization such as replicas, labels, annotations, or extra manifests. plan runs aspire publish --apphost <path> --output-path .deploy/kubernetes; Aspire emits a Helm chart with Chart.yaml, values.yaml, and templates/.

Apply the published chart with your normal cluster workflow:

kubectl config current-context
helm upgrade --install my-netscript-app .deploy/kubernetes

# Or inspect/apply rendered manifests if your release process requires kubectl.
helm template my-netscript-app .deploy/kubernetes > .deploy/kubernetes/rendered.yaml
kubectl apply -f .deploy/kubernetes/rendered.yaml

For Azure, configure the matching AppHost hosting integration before using the router. The CLI validates the AppHost source contains an Azure Container Apps, Azure App Service, or Azure Kubernetes marker before it shells aspire publish|deploy --apphost <path>. Azure CLI login, subscription/location parameters, provider feature registration, and RBAC are operator prerequisites.

For Cloud Run, configure the Docker-image provider fields:

export default defineConfig({
  // ...
  deploy: {
    targets: {
      'cloud-run': {
        registry: 'us-docker.pkg.dev/acme/prod',
        imageName: 'orders-api:latest',
      },
    },
  },
});

netscript deploy cloud-run up runs docker build -t <registry>/<imageName> ., docker push <registry>/<imageName>, then gcloud run deploy <service> --image <registry>/<imageName> --quiet.

Step 5 — Wire generated CI and promotion

New Aspire-backed workspaces include three GitHub Actions starter workflows under .github/workflows/:

Generated deployment workflows
NameTypeDescription
deploy-compose-ghcr.yml Compose + GHCR Restores the Aspire AppHost, emits Compose output with netscript deploy compose plan, builds/pushes images to GHCR, then runs netscript deploy docker up with --clear-cache.
deploy-deno-deploy.yml Deno Deploy Runs workspace checks, then calls netscript deploy deno-deploy up using GitHub secrets and variables for the Deno Deploy token, organization, and app.
deploy-bare-metal.yml Bare metal Compiles service artifacts with netscript deploy build on Linux and Windows runners, then uploads the output as workflow artifacts for host-specific installation.

Treat the workflow environment input as the promotion ladder: development first, then staging, then production. In GitHub, map those names to protected environments and keep secrets environment-scoped so a staging run cannot accidentally read production credentials. Promote the same reviewed commit through each environment; do not rebuild from a different branch between staging and production.

Step 6 — Run a process by hand (the bare-metal primitive)

Under every option above, the atomic unit is the same: one Deno process started from an entrypoint with the exact permission set from appsettings.json. This is what a container CMD, a systemd ExecStart, or a PaaS start command ultimately becomes. For the workers API (:8091), it is:

# Run from the workspace root. Flags and entrypoint come straight from appsettings.json.
PORT=8091 \
deno run \
  --unstable-kv --allow-net --allow-env --allow-read --allow-write --allow-run \
  plugins/workers/services/src/main.ts

The corresponding background processor (which actually executes jobs) runs its own entrypoint:

# Workers + sagas background processors share bin/combined.ts; triggers uses its own.
WORKERS_CONCURRENCY=2 \
deno run \
  --unstable-kv --allow-net --allow-env --allow-read --allow-write --allow-run \
  workers/bin/combined.ts

Map this pattern across every enabled resource and you have a complete, container-free deployment. To containerize, each process becomes one image whose CMD is the matching deno run line; orchestrate them with compose or your platform of choice, honoring the PluginReferences start order (streams → workers → sagas/triggers, plus auth-api when present).

Step 7 — Verify the deployment

Once your processes are up against real backing services, hit the health endpoints to confirm the graph is wired. These are the exact routes the local runtime exposes (substitute your production host):

Health and liveness endpoints (verified live)
NameTypeDescription
GET /health :8091 Workers API health.
GET /health/live :8092 Sagas API liveness.
GET /health :8093 Triggers API health (Hono).
GET /api/v1/workers/jobs :8091 Lists registered worker jobs — proves the jobs registry generated.
GET /api/v1/sagas/sagas :8092 Lists registered sagas — proves saga metadata is in KV.
POST /api/v1/webhooks/inbound/generic :8093 Inbound webhook → enqueues the workers health-check job (end-to-end proof).
GET /api/v1/events?limit=10 :8093 Recent trigger events.
GET /api/v1/auth/session :8094 Auth session probe (only if the auth plugin is installed).
(dashboard) https://localhost:18888 Aspire dashboard: every resource, health, logs, distributed traces (Aspire path only).
# Smoke a deployed graph (replace localhost with your host).
curl -fsS http://localhost:8091/health
curl -fsS http://localhost:8092/health/live
curl -fsS http://localhost:8093/health
curl -fsS "http://localhost:8091/api/v1/workers/jobs"

If every health endpoint returns and /api/v1/workers/jobs lists your jobs, the processes are running with their permissions, reaching Postgres/KV, and discovering their registries — the deployment is live.

Where to go next

For the full generated API of each deployable unit, see the reference: workers, sagas, triggers, and streams. For every CLI command grouped by workflow, see the CLI reference.