Previous slide Next slide Toggle fullscreen Toggle overview view Open presenter view
Polyglot Aspire
Orchestrating Any Language with Aspire
Chris Ayers
The Polyglot Problem
Modern apps are a big city without a map β and for polyglot teams,
the streets are in five different languages.
Your team doesn't use one language β it uses five .
Your stack today
Python ML services
Go microservices
Java Spring Boot APIs
TypeScript/React frontends
.NET backend APIs
The question: How do you orchestrate, observe, and wire all of this from one place ?
Aspire: The Polyglot Answer
Aspire is an agent-ready, code-first tool to compose, debug, and deploy any distributed app.
The Four Pillars
Aspire CLI
Your control plane
aspire init Β· aspire run Β· aspire deploy β agent-ready, interactive, the same commands for every stack.
Aspire AppHost
Your stack in code
One file declares every service and how they connect β C#, TypeScript, Python, or aspire.config.json.
Aspire Dashboard
Your app at a glance
Logs, traces, metrics, and health for every resource β powered by OpenTelemetry, surfaced over an MCP server for agents.
Aspire Integrations
Building blocks, not black boxes
100+ prebuilt packages for databases, caches, queues, AI, and clouds β or bring your own container, CLI, or agent.
One Orchestrator for Every Language
Aspire gives you three things, regardless of language:
Orchestration
Define your entire stack β Python, Go, Java, TypeScript, .NET β in one AppHost file.
Service Discovery
Endpoints and connection strings auto-injected as environment variables.
Observability
One dashboard for logs, traces, and metrics across all services via OpenTelemetry.
Your Stack in One File
One C# AppHost wires Python, React, and .NET β auto-discovery, observability, lifecycle:
var builder = DistributedApplication.CreateBuilder(args );
var redis = builder.AddRedis("cache" );
var postgres = builder.AddPostgres("db" )
.AddDatabase("appdata" );
builder.AddUvicornApp("ml-service" , "../python" , "main:app" )
.WithUv()
.WithReference(redis);
builder.AddViteApp("frontend", "../react")
.WithHttpEndpoint(env: "PORT")
.WithReference(postgres);
builder.AddProject<Projects.Api>("api")
.WithReference(redis)
.WithReference(postgres);
builder.Build().Run();
The Dashboard β One View for Everything
Same dashboard regardless of what language your services use:
Aspire sets OTEL_EXPORTER_OTLP_ENDPOINT automatically β add OpenTelemetry to your service and traces flow to the dashboard.
Wire Any OTEL App in 30 Seconds
Point your Node.js app at the standalone dashboard:
import { NodeSDK } from "@opentelemetry/sdk-node" ;
import {
OTLPTraceExporter
} from "@opentelemetry/exporter-trace-otlp-grpc" ;
const sdk = new NodeSDK ({
traceExporter : new OTLPTraceExporter ({
url : "http://localhost:4317"
})
});
sdk.start ();
Migration path: start with the standalone dashboard for observability today; adopt the AppHost later when you want service discovery, integrations, and aspire deploy.
OpenTelemetry β Python
Wire OTEL once. Aspire injects OTEL_EXPORTER_OTLP_ENDPOINT automatically.
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc \
.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export \
import BatchSpanProcessor
provider = TracerProvider()
provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(
endpoint=os.environ.get(
'OTEL_EXPORTER_OTLP_ENDPOINT' )
))
)
trace.set_tracer_provider(provider)
One block of boilerplate β and your Python service is in the dashboard.
OpenTelemetry β Node.js
Same idea, different runtime β and you get auto-instrumentation for free.
const { NodeSDK } =
require ('@opentelemetry/sdk-node' );
const { getNodeAutoInstrumentations } =
require ('@opentelemetry/auto-instrumentations-node' );
const { OTLPTraceExporter } =
require ('@opentelemetry/exporter-trace-otlp-grpc' );
const sdk = new NodeSDK ({
traceExporter : new OTLPTraceExporter ({
url : process.env .OTEL_EXPORTER_OTLP_ENDPOINT
}),
instrumentations : [getNodeAutoInstrumentations ()]
});
sdk.start ();
Auto-instrumentations capture Express, Fastify, fetch, pg, redis β without code changes.
Architecture Overview
AppHost orchestrates everything β DCP manages processes, Dashboard collects telemetry
How It Works
The patterns that make polyglot orchestration possible
Service Discovery β The Pattern
Aspire injects service endpoints as environment variables:
services__api__http__0=http://localhost:5000
services__frontend__http__0=http://localhost:3000
Why double underscore? Env vars can't have colons β so __ stands in for the path separator. Every language can read env vars; that's the universal interface.
No hardcoded URLs. No .env files. Aspire wires it.
Service Discovery β Read It Anywhere
Same env-var pattern, every language:
Python:
api_url = os.environ['services__api__http__0' ]
requests.get(f'{api_url} /data' )
Go:
apiURL := os.Getenv("services__api__http__0" )
Node.js:
const apiUrl = process.env ['services__api__http__0' ];
await fetch (`${apiUrl} /data` );
Java:
String apiUrl = System.getenv("services__api__http__0" );
Connection Strings β The Pattern
Infrastructure resources get connection strings as environment variables:
CONNECTIONSTRINGS__cache=localhost:6379
CONNECTIONSTRINGS__db=Host=localhost;Port=5432;Username=postgres;Password=...
CONNECTIONSTRINGS__messaging=localhost:9092
Python β Redis:
client = redis.from_url(
f"redis://{os.environ['CONNECTIONSTRINGS__cache' ]} "
)
Aspire injects the env var β your service reads it using its language's standard mechanism.
Same Pattern, Every Language
Node.js β Kafka:
const kafka = new Kafka ({
brokers : [process.env .CONNECTIONSTRINGS__messaging ]
});
Java β PostgreSQL:
String url = "jdbc:postgresql://"
+ System.getenv("PG_HOST" ) + ":"
+ System.getenv("PG_PORT" ) + "/"
+ System.getenv("PG_DB" );
Go, Rust, .NET, PowerShell β same pattern. Aspire sets the env var; your code reads it.
Resource Lifecycle Management
Dependency Order: Infrastructure β Backend Services β Frontend
Health Monitoring: WithHttpHealthCheck("/health") β automatic restarts on failure
Graceful Shutdown: Clean termination of all processes
Backed by 100+ integrations β Postgres, Redis, Kafka, Cosmos, OpenAI, Ollama, and your own containers all participate in the same lifecycle.
The AppHost β C#
Write your AppHost in the language your team knows. Here's C#:
var builder = DistributedApplication
.CreateBuilder(args );
var redis = builder.AddRedis("cache" );
builder.AddPythonApp("api" , "../api" , "app.py" )
.WithReference(redis)
.WithHttpEndpoint(env: "PORT" );
builder.Build().Run();
40+ integrations β Redis, Azure, Kafka, MongoDB, PostgreSQL β available out of the box.
The AppHost β TypeScript
Same model, different syntax. Best fit for Node.js / TS workspaces:
const builder = await createBuilder ();
const redis = await builder.addRedis ("cache" );
await builder
.addPythonApp ("api" , "../api" , "app.py" )
.withReference (redis)
.withHttpEndpoint ({ env : "PORT" });
await builder.build ().run ();
Same 40+ integrations as C# β auto-generated via [AspireExport] attributes. A JS/TS team never needs to touch .NET.
Two AppHost Languages
Author your AppHost in C# or TypeScript today β both officially supported.
C# (.NET) β AppHost.cs β best fit for teams already on .NET tooling.
TypeScript β apphost.ts β best fit for Node.js / TypeScript workspaces.
Same model, different syntax. Both produce the same dashboard, service discovery, health checks, and deployment artifacts.
Same integration surface β the TypeScript SDK is auto-generated from the same .NET hosting integrations via the Aspire Type System (ATS) . No separate integration code to maintain.
Workloads Can Be Anything
Pick the AppHost language that fits your repo. Your services don't need to match.
The TypeScript SDK is auto-generated from the same .NET hosting integrations via the Aspire Type System (ATS) β there's no separate integration surface to maintain.
Workloads inside the AppHost can be written in:
C#, JavaScript, TypeScript, Python, Go, Java, Rust, PowerShell, and more
via AddProject, AddJavaScriptApp, AddPythonApp, AddDockerfile, AddContainer, or AddExecutable.
Write Once with ATS
Author the integration in C#. Use it from any AppHost.
Aspire Type System (ATS) β the contract that bridges .NET and guest languages.
Author your hosting integration in C# as you always have
Annotate exported APIs with ATS attributes β [AspireExport], [AspireExportType], [AspireExportMethod]
Aspire's analyzer validates the export shape at build time
[AspireExport ]
public static class MyIntegrationExtensions
{
[AspireExportMethod ]
public static IResourceBuilder<MyResource>
AddMyService (
this IDistributedApplicationBuilder builder,
string name ) { ... }
}
Use From TypeScript β Zero Bindings
The CLI auto-generates a TypeScript SDK into .modules/ when a TS AppHost runs aspire add <your-package>. TypeScript callers get fluent, typed methods.
import { createBuilder } from "./.modules/aspire.js" ;
import { addMyService } from
"./.modules/my-integration.js" ;
const builder = await createBuilder ();
const svc = await addMyService (builder, "svc" );
The trade-off: the guest process talks to the .NET host over a local JSON-RPC socket (Unix socket / named pipe), authenticated with a per-session token. One IPC hop, no port exposure, no duplicated integration code per language.
Status: preview feature in 13.x.
Cheat Sheet β Runtimes
Pick the right Add* for your service:
Python / ASGI β AddPythonApp()
Python / Uvicorn β AddUvicornApp()
Node.js β AddNodeApp()
Vite / React β AddViteApp()
JavaScript β AddJavaScriptApp()
.NET project β AddProject<T>()
Go β AddGolangApp() (Community Toolkit)
Java / Spring Boot β AddSpringApp() (Community Toolkit)
Any Dockerfile β AddDockerfile()
Any executable β AddExecutable()
Infrastructure: AddRedis("name") Β· AddPostgres("name").AddDatabase("db") Β· AddKafka("name") Β· AddAzureCosmosDB("name").RunAsEmulator()
Cheat Sheet β Common Patterns
Chainable methods you'll use everywhere:
.WithReference(redis)
.WaitFor(postgres)
.WithHttpEndpoint(env: "PORT" )
.WithExternalHttpEndpoints()
.WithUv()
.WithNpm() / .WithBun()
.WithBuildSecret("key" , secret)
.WithRunScript("dev" )
.WithHttpHealthCheck("/health" )
.WithMcpServer("mcp" )
aspire.config.json β The File
This file tells the CLI which language your AppHost uses.
{
"appHost" : {
"path" : "apphost.py" ,
"language" : "python"
} ,
"sdk" : { "version" : "13.2.0" } ,
"channel" : "stable" ,
"features" : {
"polyglotSupportEnabled" : true
} ,
"profiles" : {
"default" : {
"applicationUrl" : "https://localhost:17000"
}
}
}
aspire.config.json β What Each Field Does
appHost.path + appHost.language β declares your stack (csharp, typescript, python...)
sdk.version β pins the Aspire SDK version
channel β release channel (stable, preview)
profiles β dashboard URLs (replaces apphost.run.json)
Feature flags use boolean true β not string "true"
Every sample in this talk has one at its root β peek inside.
Manage Config From the CLI
No manual JSON editing required:
aspire config list
aspire config get <key>
aspire config set <key> <value>
aspire secret set <key> <value>
aspire secret list / get / delete
aspire certs clean
aspire certs trust
Every sample in this talk has an aspire.config.json at its root β peek inside.
Getting Started
No .NET SDK required β get.aspire.dev
Install & scaffold
curl -sSL https://aspire.dev/install.sh | bash
aspire new aspire-py-starter -n my-app
aspire new aspire-ts-starter -n my-app
aspire new aspire-starter -n my-app
cd my-app && aspire run
Day-to-day commands
aspire run / aspire start
aspire ps / aspire stop
aspire describe --follow
aspire doctor
aspire otel / aspire logs
aspire export
Agent-Ready CLI
Two MCP servers. One model. Any agent.
MCP support out of the box β no plugins, no glue.
CLI MCP β stdio. Agent spawns aspire agent mcp as a subprocess. Set up by aspire agent init.
Dashboard MCP β streamable HTTP + API key. Click the MCP button in the dashboard top-right.
Tools agents get : list_resources, list_console_logs, list_traces, execute_resource_command, search_docs, doctor.
Clients : VS Code, Claude Code, Copilot CLI, OpenCode β any MCP-aware client works.
Polyglot bonus: the agent sees Python tracebacks, Go panics, Java stack traces, and Node errors through the same OTEL pipeline.
Demos
8 samples β live with the Aspire dashboard
8 Live Demos
Simple β Full-stack
ts-starter β Express + React (TS AppHost)
flask-markdown-wiki β Flask + Redis (Python AppHost)
vite-react-api β FastAPI + React + Redis (TS AppHost)
django-htmx-polls β Django + HTMX + PostgreSQL (Python AppHost)
Multi-runtime β Polyglot
5. spring-boot-postgres β Spring Boot + PostgreSQL (Java AppHost)
6. svelte-go-bookmarks β Go API + Svelte + PostgreSQL (Go AppHost)
7. dotnet-angular-cosmos β Angular + .NET + CosmosDB (C# AppHost)
8. polyglot-event-stream β .NET + Python + Node.js + Kafka (C# AppHost)
Demo: TypeScript Starter
TypeScript AppHost β Express API + React Frontend
Demo: Flask Markdown Wiki
Python AppHost β Flask Wiki App + Redis Cache
Demo: Vite React + FastAPI
TypeScript AppHost β React Frontend + Python FastAPI + Redis
Demo: Django HTMX Polls
Python AppHost β Django + HTMX + PostgreSQL
Demo: Spring Boot Notes
Java AppHost β Spring Boot API + PostgreSQL
Demo: Svelte + Go Bookmarks
Go AppHost β Go REST API + Svelte Frontend + PostgreSQL
Demo: Angular + .NET + CosmosDB
C# AppHost β Angular Frontend + .NET API + Azure CosmosDB
Demo: Polyglot Event Stream
C# AppHost β .NET Producer + Python Consumer + Node.js Dashboard + Kafka
Same Model, Two Commands
One AppHost. Local, staging, production.
aspire run
aspire deploy
aspire publish
aspire do
What Aspire generates from your AppHost:
Container images for all languages
Azure Container Apps / Kubernetes manifests
Infrastructure wiring (Redis, Postgres, Kafkaβ¦)
Service connections + environment variables
No separate deploy config. The AppHost is the contract.
What That Looks Like
aspire deploy (target)
β Building images for python, node, .net
β Pushing to acrcdbytes.azurecr.io
β Provisioning Container Apps environment
β Wiring Postgres + Redis connection strings
Deployed to dev environment in 4m 12s
Same code-first AppHost drives both.
Key Takeaways
One orchestrator for every language β Define your entire stack in one AppHost file, regardless of runtime
Unified observability out of the box β One dashboard for logs, traces, and metrics across all services via OpenTelemetry
From local dev to production β Same model, same CLI, same config β aspire run to aspire deploy
Resources
Follow Chris Ayers
We've all been there. The README says "just run docker-compose up" but it never works the first time. Five languages, one app, no map.
Each language has its own logging, its own config format, its own service discovery pattern. You end up with hardcoded URLs everywhere.
Use the official positioning sentence verbatim β it sets up everything that follows. Then transition to the four pillars.
These three promises map onto the rest of the talk. Each one shows up everywhere.
This is the Aspire AppHost β the central brain that starts everything and wires it together. Python, React, .NET, all visible in one dashboard.
This is the payoff β show it early. One dashboard for everything, regardless of language. Click a trace to see the full waterfall across Python, .NET, and Node.js. Export as .env for local debugging.
This closes a real adoption gap for polyglot teams: "I'm not on .NET, can I still use any of this?" Yes β start here.
Once they like the UI, the AppHost story becomes a much easier sell.
Add the OTel SDK, point at the env var Aspire injects, and you're done.
Node gets even better β auto-instrumentations grab HTTP, DB, redis, etc. for free.
The AppHost is the central orchestrator. All services report their telemetry to the unified dashboard.
Now let's look at the mechanisms under the hood.
This is the magic. The double underscore __ is used because environment variables can't have colons.
Each runtime has its own env-var idiom β but the pattern is identical.
Aspire handles connection-string complexity so you don't manage .env files.
For C# AppHosts using AddPostgres, connection strings are auto-generated. For polyglot AppHosts, you wire env vars explicitly β same result, more control.
Aspire manages startup ordering automatically based on WithReference and WaitFor dependencies. Infrastructure comes up first, then backends, then frontends. Over 100 integrations are wired to the same lifecycle so health/dependency rules apply to everything in the AppHost.
C# is the original AppHost language. Most existing samples use this.
The TypeScript AppHost uses the same integration packages as C#.
Per aspire.dev/languages-and-runtimes: only C# and TypeScript are documented AppHost authoring languages today.
AppHost authoring is C#/TS today; workload language support is separate and much broader.
Integration authors don't write a TS binding by hand β the analyzer + ATS scanner generates it.
That's how 100+ .NET integrations show up automatically in TypeScript AppHosts.
Quick reference for the runtime side.
Keep this slide handy β these are the building blocks for everything we covered.
Drop aspire.config.json at the project root β the CLI reads it on every command.
Common gotcha: feature flags are JSON booleans, not strings.
The CLI is the friendlier surface β most teams never edit aspire.config.json directly.
aspire doctor is great before a talk. aspire start runs in detached mode. aspire new is language-aware β it scaffolds the right AppHost structure for your chosen language.
Per aspire.dev/get-started/aspire-mcp-server/. CLI MCP = stdio + `aspire agent init`. Dashboard MCP = streamable HTTP + API key from the dashboard UI.
Earlier drafts invented an `http://localhost:15889/mcp` URL β doesn't exist for the CLI server.
Time to see Aspire in action!
Ordered by escalating complexity β same orchestration model, increasing sophistication.
The simplest polyglot demo. TypeScript AppHost with auto-wired API and frontend.
Python orchestrating Python β the AppHost and the service are both Python.
Full-stack TypeScript-orchestrated app with Python backend and Redis caching.
Real-time voting with HTMX partial updates, Django backend, PostgreSQL persistence.
Java orchestrating Java β experimental Java AppHost with Spring Boot and PostgreSQL.
Go orchestrating a full-stack app β Go API backend with Svelte frontend.
Classic .NET AppHost orchestrating Angular frontend with CosmosDB emulator.
The ultimate polyglot demo β three languages, one event pipeline, full distributed tracing.
This works whether your services are Python, Go, Java, TypeScript, .NET β you don't need per-language deploy plumbing.
The pivot: aspire run β aspire deploy. Same model, no extra plumbing.
If your team uses multiple languages, Aspire gives you a single place to define, run, observe, and deploy your entire stack.