跳转到内容
文档试用 Aspire
文档试用

Add Aspire to an existing app

此内容尚不支持你的语言。

Add Aspire to the app you already have instead of rebuilding your solution around a new template. The fastest path is aspire init paired with an AI coding agent that automatically discovers your services and wires them into an AppHost. If you prefer full control, manual steps are provided below.

As distributed applications grow, local development often turns into a collection of fragile scripts, copied connection strings, and startup-order tribal knowledge. Aspire gives you a single orchestration layer for the resources you already own. Define the relationships once in code, and Aspire handles service discovery, configuration injection, startup ordering, and dashboard visibility.

You can also adopt Aspire incrementally. Start by modeling the parts that are hardest to keep aligned by hand, such as containers, databases, caches, queues, background workers, and local dev commands. Add telemetry when you’re ready, then deepen the model as your app grows.

Before you begin, make sure you have:

  • Aspire CLI installed
  • An existing application or workspace to add Aspire to
  • The runtimes and tools your existing services already need
Section titled “Recommended: Use an AI coding agent with the “aspireify” skill”

The fastest way to add Aspire to an existing app is to let aspire init scaffold the skeleton, then hand off wiring to the aspireify agent skill. The skill handles resource discovery, dependency wiring, OpenTelemetry setup, and validation automatically.

  1. Run aspire init in your repo root:

    Initialize Aspire
    aspire init

    Choose your AppHost language (C# or TypeScript) when prompted, or pass --language csharp / --language typescript. The command creates a minimal AppHost, an aspire.config.json, and installs the aspireify skill into your agent’s skill directory. For existing JavaScript or TypeScript apps with a root package.json, the TypeScript AppHost is created in an aspire-apphost/ subfolder so Aspire doesn’t change the existing app package’s module settings.

  2. Ask your AI coding agent to run the aspireify skill. The agent will:

    • Scan your repo and discover existing projects, services, containers, and infrastructure
    • Ask you to confirm the resources it found, which ones you want included, and other clarifying questions before starting
    • Wire resources into the AppHost with WithReference, WaitFor, endpoints, and volumes
    • Add ServiceDefaults and configure OpenTelemetry for each service
    • Validate the setup by running aspire start
  3. Once the agent reports success, run aspire start yourself and open the dashboard to verify everything looks correct. Something not right? Tell the agent-it has plenty of tools from Aspire to troubleshoot!

For more details on the aspire init command and the aspireify skill, see the CLI reference: aspire init.


If you prefer full control over the wiring, or want to understand what the aspireify skill does under the hood, follow the manual steps below. This is also the reference for anyone extending or customizing an AppHost after the initial setup.

The AppHost is the orchestration layer. Your choice here changes how you express orchestration, not what Aspire can orchestrate.

Use a TypeScript AppHost when your repo already centers on a Node.js workspace or when you prefer path-based orchestration in TypeScript.

  • Lives in apphost.mts; for existing JavaScript and TypeScript apps, aspire init creates it under aspire-apphost/
  • Runs under supported package managers including npm, pnpm, Yarn 4+, and Bun
  • Fits naturally into existing package-manager and monorepo workflows
  1. Run aspire init from your workspace root with the TypeScript language option:

    Initialize Aspire with a TypeScript AppHost
    aspire init --language typescript
  2. Add hosting integrations:

    Add hosting integrations
    aspire add redis
    aspire add postgres
  3. Wire the resources in aspire-apphost/apphost.mts:

    apphost.mts
    import {
    function createBuilder(): IDistributedApplicationBuilder

    Creates a new distributed application builder

    createBuilder
    } from './.aspire/modules/aspire.mjs';
    const
    const builder: IDistributedApplicationBuilder
    builder
    = await
    function createBuilder(): IDistributedApplicationBuilder

    Creates a new distributed application builder

    createBuilder
    ();
    const
    const cache: RedisResource
    cache
    = await
    const builder: IDistributedApplicationBuilder
    builder
    .
    IDistributedApplicationBuilder.addRedis(name: string, options?: {
    port?: number;
    password?: string | ParameterResource;
    }): RedisResource (+1 overload)

    Adds a Redis container to the application model.

    addRedis
    ('cache');
    const
    const db: PostgresDatabaseResource
    db
    = (await
    const builder: IDistributedApplicationBuilder
    builder
    .
    IDistributedApplicationBuilder.addPostgres(name: string, options?: {
    userName?: string | ParameterResource;
    password?: string | ParameterResource;
    port?: number;
    }): PostgresServerResource (+1 overload)

    Adds a PostgreSQL resource to the application model. A container is used for local development.

    addPostgres
    ('postgres')).
    PostgresServerResource.addDatabase(name: string, options?: {
    databaseName?: string;
    } | undefined): PostgresDatabaseResource (+1 overload)

    Adds a PostgreSQL database to the application model.

    addDatabase
    ('mydb');
    const
    const api: ProjectResource
    api
    = await
    const builder: IDistributedApplicationBuilder
    builder
    .
    IDistributedApplicationBuilder.addProject(name: string, projectPath: string, options?: {
    launchProfileOrOptions?: ProjectResourceOptions;
    }): ProjectResource (+1 overload)

    Adds a .NET project resource

    addProject
    ('api', '../src/Api/MyApp.Api.csproj')
    .
    ProjectResource.withReference(source: EndpointReference | string | uri, options?: {
    connectionName?: string;
    optional?: boolean;
    name?: string;
    } | undefined): ProjectResource (+1 overload)

    Adds a reference to another resource

    withReference
    (
    const db: PostgresDatabaseResource
    db
    )
    .
    ProjectResource.withReference(source: EndpointReference | string | uri, options?: {
    connectionName?: string;
    optional?: boolean;
    name?: string;
    } | undefined): ProjectResource (+1 overload)

    Adds a reference to another resource

    withReference
    (
    const cache: RedisResource
    cache
    )
    .
    ProjectResource.waitFor(dependency: IResource | IResourceWithConnectionString, waitBehavior?: WaitBehavior): ProjectResource

    Waits for another resource to be ready

    waitFor
    (
    const db: PostgresDatabaseResource
    db
    );
    await
    const builder: IDistributedApplicationBuilder
    builder
    .
    IDistributedApplicationBuilder.addViteApp(name: string, appDirectory: string, options?: {
    runScriptName?: string;
    }): ViteAppResource (+1 overload)

    Adds a Vite app to the distributed application builder.

    addViteApp
    ('web', '../services/web')
    .
    ExecutableResource.withReference(source: EndpointReference | string | uri, options?: {
    connectionName?: string;
    optional?: boolean;
    name?: string;
    } | undefined): ViteAppResource (+1 overload)

    Adds a reference to another resource

    withReference
    (
    const api: ProjectResource
    api
    )
    .
    ExecutableResource.waitFor(dependency: IResource | IResourceWithConnectionString, waitBehavior?: WaitBehavior): ViteAppResource

    Waits for another resource to be ready

    waitFor
    (
    const api: ProjectResource
    api
    );
    await
    const builder: IDistributedApplicationBuilder
    builder
    .
    IDistributedApplicationBuilder.build(): DistributedApplication

    Builds the distributed application

    build
    ().
    DistributedApplication.run(cancellationToken?: cancellationToken): void

    Runs the distributed application

    run
    ();

After setup, a typical workspace layout looks like this:

  • aspire.config.json (new)
  • package.json (updated with Aspire delegate scripts)
  • 文件夹aspire-apphost/ (new)
    • apphost.mts
    • 文件夹.aspire/modules/
    • package.json
    • tsconfig.apphost.json
  • 文件夹services/
    • 文件夹web/
      • package.json
      • 文件夹src/
  • 文件夹src/
    • 文件夹Api/
      • MyApp.Api.csproj

When your app already lives inside a subdirectory of a larger workspace (for example, apps/my-app/), run aspire init from that subdirectory. The CLI places the AppHost under aspire-apphost/ relative to where you run the command:

Initialize from a workspace subdirectory
cd apps/my-app
aspire init --language typescript --non-interactive

The resulting layout looks like this:

  • 文件夹apps/
    • 文件夹my-app/
      • 文件夹aspire-apphost/ (new)
        • apphost.mts (new)
        • package.json (new)
        • tsconfig.apphost.json (new)
        • 文件夹.aspire/ (new)
          • 文件夹modules/ (new)
      • aspire.config.json (new)
      • package.json (updated with delegate scripts)
      • 文件夹src/

aspire init adds delegate scripts to the root package.json so you can run the AppHost from the workspace root without cd-ing into aspire-apphost/:

ScriptWhat it does
aspire:startStarts the Aspire dashboard and all orchestrated services
aspire:buildCompiles the TypeScript AppHost
aspire:devWatches the AppHost for changes

These scripts delegate to the AppHost subdirectory using your package manager. For example, with pnpm and an AppHost at aspire-apphost/:

package.json — delegate scripts added by aspire init
{
"scripts": {
"aspire:start": "pnpm --dir aspire-apphost run aspire:start",
"aspire:build": "pnpm --dir aspire-apphost run aspire:build",
"aspire:dev": "pnpm --dir aspire-apphost run aspire:dev"
}
}

The AppHost package and your guest apps can use different package managers. The AppHost directory takes precedence for running AppHost scripts — if the AppHost’s package.json declares "packageManager": "pnpm@..." or contains a pnpm-lock.yaml, pnpm is used for AppHost operations regardless of the toolchain in the parent workspace.

Scenario: Existing services with hosting integrations

Section titled “Scenario: Existing services with hosting integrations”

Use this approach when Aspire already has a first-class resource type for the workload you want to run. That keeps the application model focused on what the service is and what it depends on, instead of reducing it to a generic shell command.

Common examples include Node.js apps, Vite frontends, Python workers, and Uvicorn-based APIs.

apphost.mts — Existing services with hosting integrations
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
const
const cache: RedisResource
cache
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addRedis(name: string, options?: {
port?: number;
password?: string | ParameterResource;
}): RedisResource (+1 overload)

Adds a Redis container to the application model.

addRedis
('cache');
const
const api: UvicornAppResource
api
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addUvicornApp(name: string, appDirectory: string, app: string): UvicornAppResource

Adds a Uvicorn-based Python application to the distributed application builder with HTTP endpoint configuration.

addUvicornApp
('api', '../services/api', 'main:app')
.
UvicornAppResource.withUv(options?: {
install?: boolean;
args?: string[];
} | undefined): UvicornAppResource (+1 overload)

Adds a UV environment setup task to ensure the virtual environment exists before running the Python application.

withUv
()
.
ExecutableResource.withReference(source: EndpointReference | string | uri, options?: {
connectionName?: string;
optional?: boolean;
name?: string;
} | undefined): UvicornAppResource (+1 overload)

Adds a reference to another resource

withReference
(
const cache: RedisResource
cache
)
.
ExecutableResource.withExternalHttpEndpoints(): UvicornAppResource

Marks existing http or https endpoints on a resource as external.

withExternalHttpEndpoints
();
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addPythonApp(name: string, appDirectory: string, scriptPath: string): PythonAppResource

Adds a Python application to the application model.

addPythonApp
('worker', '../workers/inventory-sync', 'worker.py')
.
ExecutableResource.withReference(source: EndpointReference | string | uri, options?: {
connectionName?: string;
optional?: boolean;
name?: string;
} | undefined): PythonAppResource (+1 overload)

Adds a reference to another resource

withReference
(
const cache: RedisResource
cache
);
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addViteApp(name: string, appDirectory: string, options?: {
runScriptName?: string;
}): ViteAppResource (+1 overload)

Adds a Vite app to the distributed application builder.

addViteApp
('web', '../services/web')
.
ExecutableResource.withReference(source: EndpointReference | string | uri, options?: {
connectionName?: string;
optional?: boolean;
name?: string;
} | undefined): ViteAppResource (+1 overload)

Adds a reference to another resource

withReference
(
const api: UvicornAppResource
api
)
.
ExecutableResource.waitFor(dependency: IResource | IResourceWithConnectionString, waitBehavior?: WaitBehavior): ViteAppResource

Waits for another resource to be ready

waitFor
(
const api: UvicornAppResource
api
);
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

build
().
DistributedApplication.run(cancellationToken?: cancellationToken): void

Runs the distributed application

run
();

If a workload does not have a dedicated hosting API yet, model it as an executable resource with AddExecutable or addExecutable so it can still participate in the same application model.

For first-class workload guidance, see JavaScript integration, Python integration, and Multi-language architecture.

Scenario: Existing containers and shared infrastructure

Section titled “Scenario: Existing containers and shared infrastructure”

Use this approach when the important boundary is the runtime environment itself: a published container image, a shared database, a cache, a queue, or an existing infrastructure topology. Model the shared resources first, then attach the workloads that consume them so connectivity, configuration, and startup order are explicit.

When Aspire has a first-class integration for that infrastructure, add it first:

Add hosting integrations
aspire add postgres
aspire add redis
apphost.mts — Existing containers and shared infrastructure
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
const
const db: PostgresDatabaseResource
db
= (await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addPostgres(name: string, options?: {
userName?: string | ParameterResource;
password?: string | ParameterResource;
port?: number;
}): PostgresServerResource (+1 overload)

Adds a PostgreSQL resource to the application model. A container is used for local development.

addPostgres
('postgres')).
PostgresServerResource.addDatabase(name: string, options?: {
databaseName?: string;
} | undefined): PostgresDatabaseResource (+1 overload)

Adds a PostgreSQL database to the application model.

addDatabase
('orders');
const
const cache: RedisResource
cache
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addRedis(name: string, options?: {
port?: number;
password?: string | ParameterResource;
}): RedisResource (+1 overload)

Adds a Redis container to the application model.

addRedis
('cache');
const
const api: ContainerResource
api
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addContainer(name: string, image: AddContainerOptions): ContainerResource

Adds a container resource to the application.

addContainer
('api', {
AddContainerOptions.image?: string | undefined
image
: 'ghcr.io/contoso/orders-api',
AddContainerOptions.tag?: string | undefined
tag
: 'latest' })
.
ContainerResource.withReference(source: EndpointReference | string | uri, options?: {
connectionName?: string;
optional?: boolean;
name?: string;
} | undefined): ContainerResource (+1 overload)

Adds a reference to another resource

withReference
(
const db: PostgresDatabaseResource
db
)
.
ContainerResource.withReference(source: EndpointReference | string | uri, options?: {
connectionName?: string;
optional?: boolean;
name?: string;
} | undefined): ContainerResource (+1 overload)

Adds a reference to another resource

withReference
(
const cache: RedisResource
cache
)
.
ContainerResource.withHttpEndpoint(options?: {
port?: number;
targetPort?: number;
name?: string;
env?: string;
isProxied?: boolean;
} | undefined): ContainerResource (+1 overload)

Adds an HTTP endpoint

withHttpEndpoint
({
port?: number | undefined
port
: 8080,
targetPort?: number | undefined
targetPort
: 8080,
name?: string | undefined
name
: 'http' });
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addContainer(name: string, image: AddContainerOptions): ContainerResource

Adds a container resource to the application.

addContainer
('web', {
AddContainerOptions.image?: string | undefined
image
: 'ghcr.io/contoso/orders-web',
AddContainerOptions.tag?: string | undefined
tag
: 'latest' })
.
ContainerResource.withReference(source: EndpointReference | string | uri, options?: {
connectionName?: string;
optional?: boolean;
name?: string;
} | undefined): ContainerResource (+1 overload)

Adds a reference to another resource

withReference
(
const api: ContainerResource
api
)
.
ContainerResource.withHttpEndpoint(options?: {
port?: number;
targetPort?: number;
name?: string;
env?: string;
isProxied?: boolean;
} | undefined): ContainerResource (+1 overload)

Adds an HTTP endpoint

withHttpEndpoint
({
port?: number | undefined
port
: 3000,
targetPort?: number | undefined
targetPort
: 3000,
name?: string | undefined
name
: 'http' });
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

build
().
DistributedApplication.run(cancellationToken?: cancellationToken): void

Runs the distributed application

run
();

Use this scenario when Docker Compose already captures the shape of your system. Treat the Compose file as a map of workloads, shared infrastructure, exposed ports, and dependency edges that you want to restate in the AppHost.

The goal is not a line-by-line translation of every field, but a clearer resource model of the same relationships.

docker-compose.yml
services:
postgres:
image: postgres:latest
environment:
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=mydb
ports:
- "5432:5432"
api:
build: ./api
environment:
- DATABASE_URL=postgres://postgres:postgres@postgres:5432/mydb
depends_on:
- postgres
web:
build: ./web
environment:
- API_URL=http://api:8080
depends_on:
- api

These scenarios are starting points, not mutually exclusive modes. Most real apps mix workload-specific resources, containers, shared infrastructure, project-path references, and occasional custom commands in a single application model. The key is that dependencies, endpoints, configuration, and startup behavior become explicit.

Telemetry is configured inside the workloads that emit it, not in the AppHost itself. Aspire gives those workloads an OTLP destination and a shared dashboard during local orchestration, but each service still uses the observability libraries that fit its runtime.

If your app includes Node.js or TypeScript services, configure OpenTelemetry inside the service and point it at the Aspire OTLP endpoint.

  1. Install the OpenTelemetry packages:

    Install OpenTelemetry packages
    npm install @opentelemetry/api @opentelemetry/sdk-node \
    @opentelemetry/auto-instrumentations-node \
    @opentelemetry/exporter-trace-otlp-grpc \
    @opentelemetry/exporter-metrics-otlp-grpc
  2. Create a telemetry bootstrap file:

    telemetry.ts
    import { NodeSDK } from '@opentelemetry/sdk-node';
    import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
    const sdk = new NodeSDK({
    instrumentations: [getNodeAutoInstrumentations()],
    });
    sdk.start();
  3. Import it first in your app entry point:

    src/server.ts
    import './telemetry';
    import express from 'express';
    const app = express();

For other runtimes, use the OpenTelemetry SDK or instrumentation library that matches the runtime you are already using, then export telemetry to the OTLP endpoint Aspire provides during local orchestration.

Once the AppHost captures the resources and relationships you care about, start everything together with the Aspire CLI.

  1. From the directory that contains your AppHost, run:

    Run your application with Aspire
    aspire run
  2. Wait for the CLI to discover the AppHost, launch the resources, and print the dashboard URL.

    Example output
    Finding apphosts...
    Dashboard: https://localhost:17068/login?t=example
    Press CTRL+C to stop the apphost and exit.
  3. Open the dashboard in your browser and verify:

    • All resources start successfully
    • Service dependencies appear in the expected order
    • Logs, traces, and metrics are visible
    • Endpoints and environment variables look correct
  4. Exercise the actual app flows you care about, such as frontend-to-API calls, worker jobs, or database access.

  5. Stop the system by pressing ⌃+CControl + CControl + C in your terminal.

At this point, you have the core workflow: describe the resources your app needs, connect the workloads that depend on them, and let Aspire run the system together during local development. From there, you can deepen the setup incrementally instead of trying to remodel the entire app at once.