Skip to content
Docs Try Aspire
Docs Try

Add Aspire to an existing app

Add Aspire to the app you already have instead of rebuilding your solution around a new template. Start by choosing the AppHost style that fits your repo, then register the services, containers, shared infrastructure, and, when needed, custom executables you already run today, regardless of whether those workloads are written in C#, Node.js, Python, Go, Rust, Java, or something else.

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.

This guide is organized around the kinds of resources you already manage rather than around a preferred service language. Aspire is multi-language by design, so one AppHost can orchestrate a system that spans C#, Node.js, Python, Go, Rust, Java, and other supported workloads.

  • Existing services with hosting integrations: You already have C# services, Node.js apps, Vite frontends, Python apps, or ASGI apps such as FastAPI and want to use Aspire’s dedicated hosting APIs.
  • Existing containers and shared infrastructure: You already have images, Docker Compose files, databases, caches, queues, or reverse proxies that you want Aspire to coordinate.
  • Existing C# services: You already have C# projects or file-based C# apps and want the C# AppHost to orchestrate them directly.

In every scenario, you can choose either AppHost style and mix resource types in the same application model.

When a dedicated hosting API exists for a workload, prefer it over AddExecutable or addExecutable. Executable resources are the fallback for custom tools, one-off commands, or workloads that do not yet have a dedicated hosting integration.

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

Use a C# AppHost when your repo already centers on C# or when you want a single-file orchestrator that still fits naturally into .NET SDK and IDE workflows.

  • Lives in a single apphost.cs file that uses #:sdk and #:package directives

  • Common APIs include:

    APIDescription
    AddContainer()Run a prebuilt container image that already exists in your current workflow.
    AddCSharpApp()Point a file-based AppHost at an existing C# app or .csproj without creating a separate AppHost project graph.
    AddDockerfile()Build and run a container from an existing Dockerfile in your repo.
    AddExecutable()Fall back to a custom command when a dedicated hosting API does not exist yet.
    AddJavaScriptApp(), AddNodeApp(), AddViteApp()Model JavaScript and TypeScript workloads, from general package-script apps to Node entrypoints and Vite frontends.
    AddParameter()Define reusable config values and secrets for the resources in your model.
    AddPostgres(), AddRedis()Add shared infrastructure with first-class integrations and connection wiring.
    AddPythonApp(), AddUvicornApp()Model Python scripts, workers, and ASGI apps such as FastAPI.
    WaitFor(), WithHttpEndpoint(), WithReference()Wire service discovery, startup ordering, and HTTP endpoints between resources.
  • Fits naturally into existing .NET SDK, IDE, and repo workflows

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

For workloads with hosting integrations:

  • Service directories and standard project metadata for the workloads you plan to model, such as package.json, pyproject.toml, or requirements.txt
  • The runtimes and package managers those services already need

For custom executables as a fallback:

  • Working start commands for each service you plan to model
  • Any repo-local config files or working directories those commands rely on

For containers and shared infrastructure:

  • Existing image names, Dockerfiles, or Compose knowledge for the services you want to model
  • The databases, caches, queues, or reverse proxies you want Aspire to own or connect to

For C# services:

  • One or more C# projects or file-based C# apps if you plan to use AddCSharpApp
  • A solution file is optional and not required for a file-based C# AppHost

Adding Aspire to an existing app usually follows these steps:

  1. Choose an AppHost that fits your repo and workflow.
  2. Initialize Aspire support with aspire init.
  3. Register your existing processes, containers, and shared resources in the AppHost.
  4. Add telemetry and integrations where they add value.
  5. Run and verify the full system with Aspire orchestration.

The aspire init command creates the orchestration layer and helps wire the first set of resources into it.

  1. Navigate to the root of your existing repo:

    Navigate to your repo
    cd /path/to/your-repo
  2. Run aspire init:

    Initialize Aspire with a C# AppHost
    aspire init

    The command runs in interactive mode by default. It can detect existing C# projects, create a single-file AppHost, and add the initial package directives and configuration it needs.

For more details on the aspire init command and its options, see the CLI reference: aspire init.

After initialization, a typical C#-centric repo might look like this:

  • apphost.cs (new)
  • apphost.run.json (new)
  • Directoryservices/
    • DirectoryApi/
      • ExampleEcommerce.Api.csproj
    • DirectoryWeb/
      • package.json
      • Directorysrc/
  • Directoryworkers/
    • Directoryinventory-sync/
      • worker.py

Starter AppHost:

apphost.cs — Initial state
#:sdk Aspire.AppHost.Sdk@13.2.0
var builder = DistributedApplication.CreateBuilder(args);
// TODO: Add resources here
builder.Build().Run();

Add #:package directives in apphost.cs for the hosting integrations you use, such as Aspire.Hosting.Redis, Aspire.Hosting.Python, Aspire.Hosting.JavaScript, and Aspire.Hosting.PostgreSQL.

Once you have an AppHost, use it to model the parts of your system that matter during local development: workloads, supporting infrastructure, and the connections between them. A resource does not need to mirror every repo boundary or implementation detail; it should represent something Aspire needs to start, observe, or connect.

Think first about relationships: which workloads consume which backing services, which endpoints need to be reachable, and which resources must be ready before others can do useful work.

The same patterns apply across AppHost styles:

  • Use WithReference or withReference to express that one resource depends on another and to flow connection information through the model.
  • Use WaitFor or waitFor when readiness matters and one resource should not start until another is available.

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.cs — Existing services with hosting integrations
#:sdk Aspire.AppHost.Sdk@13.2.0
#:package Aspire.Hosting.Redis@13.2.0
#:package Aspire.Hosting.Python@13.2.0
#:package Aspire.Hosting.JavaScript@13.2.0
var builder = DistributedApplication.CreateBuilder(args);
var cache = builder.AddRedis("cache");
var api = builder.AddUvicornApp("api", "../services/api", "main:app")
.WithUv()
.WithReference(cache)
.WithExternalHttpEndpoints();
var worker = builder.AddPythonApp("worker", "../workers/inventory-sync", "worker.py")
.WithReference(cache);
var web = builder.AddViteApp("web", "../services/web")
.WithReference(api)
.WaitFor(api);
builder.Build().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.cs — Existing containers and shared infrastructure
#:sdk Aspire.AppHost.Sdk@13.2.0
#:package Aspire.Hosting.PostgreSQL@13.2.0
#:package Aspire.Hosting.Redis@13.2.0
var builder = DistributedApplication.CreateBuilder(args);
var db = builder.AddPostgres("postgres")
.AddDatabase("orders");
var cache = builder.AddRedis("cache");
var api = builder.AddContainer("api", "ghcr.io/contoso/orders-api:latest")
.WithReference(db)
.WithReference(cache)
.WithHttpEndpoint(port: 8080, targetPort: 8080, name: "http");
var web = builder.AddContainer("web", "ghcr.io/contoso/orders-web:latest")
.WithReference(api)
.WithHttpEndpoint(port: 3000, targetPort: 3000, name: "http");
builder.Build().Run();

Use this approach when your repo already contains .NET services that should stay where they are. The goal is to register those services as resources by pointing at their existing paths, then connect the infrastructure, endpoints, and health checks they already need as part of the larger application model.

In a file-based C# AppHost, AddCSharpApp is the direct way to point at an existing C# service path. This works with single-file C# apps, directories, or existing .csproj files.

apphost.cs — Existing C# services
#:sdk Aspire.AppHost.Sdk@13.2.0
#:package Aspire.Hosting.Redis@13.2.0
#pragma warning disable ASPIRECSHARPAPPS001
var builder = DistributedApplication.CreateBuilder(args);
var cache = builder.AddRedis("cache");
var api = builder.AddCSharpApp("api", "./src/Api/Store.Api.csproj")
.WithReference(cache)
.WithHttpHealthCheck("/health");
var worker = builder.AddCSharpApp("worker", "./src/Worker/Store.Worker.csproj")
.WithReference(cache);
builder.Build().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 C# services, ServiceDefaults is the standard way to add observability, resilience, and health checks.

  1. Add ServiceDefaults if you did not enable it during aspire init:

    Add ServiceDefaults
    dotnet new aspire-servicedefaults -n YourProject.ServiceDefaults
    dotnet sln add YourProject.ServiceDefaults
    dotnet add YourProject reference YourProject.ServiceDefaults
  2. Update your service’s Program.cs:

    Program.cs — Add ServiceDefaults
    var builder = WebApplication.CreateBuilder(args);
    builder.AddServiceDefaults();
    var app = builder.Build();
    app.MapDefaultEndpoints();
    app.Run();

For more information, see Service Defaults.

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 ⌘+C Control + C Control + 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.