Previous slide Next slide Toggle fullscreen Toggle overview view Open presenter view
Polyglot Aspire
Orchestrating Any Language with Aspire
Chris Ayers
The Polyglot Problem
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
Your orchestration pain
Docker Compose: manual port wiring, no built-in telemetry, no health-aware startup ordering
Each language has its own config format (.env, YAML, application.properties...)
No unified observability — good luck tracing a request across 4 services
15-step README to run locally — "just docker-compose up" never works first time
The question: How do you orchestrate, observe, and wire all of this from one place ?
Aspire: The Polyglot Answer
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
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 AppHost — Your Stack in Code
Write your AppHost in the language your team knows:
C# AppHost
var builder = DistributedApplication
.CreateBuilder(args );
var redis = builder.AddRedis("cache" );
builder.AddPythonApp("api" , "../api" , "app.py" )
.WithReference(redis)
.WithHttpEndpoint(env: "PORT" );
builder.Build().Run();
TypeScript AppHost
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 — Redis, Azure, Kafka, MongoDB, PostgreSQL — available in both C# and TypeScript.
AppHost Languages
Pick the language your team already knows:
TypeScript — apphost.ts
await builder.addNodeApp ("api" , "./src" , "server.js" )
.withHttpEndpoint ({ env : "PORT" });
C# (.NET) — AppHost.cs
builder.AddProject<Projects.Api>("api" )
.WithHttpEndpoint(env: "PORT" );
All produce the same result:
Dashboard, service discovery, and telemetry work identically . C# has the richest typed integrations; other SDKs use addDockerfile/addContainer as universal building blocks.
Python — apphost.py FUTURE STATE
api = builder.add_dockerfile("api" , "./src" )
api.with_http_endpoint(target_port=8080 , env="PORT" )
Java — AppHost.java FUTURE STATE
builder.addDockerfile("api" , "./src" , null , null )
.withExternalHttpEndpoints();
Go — apphost.go FUTURE STATE
api, _ := builder.AddDockerfile("api" , "./src" , nil , nil )
api.WithHttpEndpoint(nil , float64Ptr(8080 ), stringPtr("http" ), nil , nil )
aspire.config.json — One Config for Every Language
This file tells the CLI which language your AppHost uses:
{
"appHost" : { "path" : "apphost.py" , "language" : "python" } ,
"sdk" : {
"version" : "13.2.0" } ,
"packages" : {
"Aspire.Hosting.Redis" : "13.2.0" ,
"Aspire.Hosting.JavaScript" : "13.2.0" ,
"Aspire.Hosting.Python" : "13.2.0" ,
"Aspire.Hosting.OpenAI" : "13.2.0"
}
}
What it does:
appHost.path + appHost.language — declares your stack
sdk.version — pins the Aspire SDK version (e.g. 13.2.0)
Feature flags use boolean true (not string "true")
Replaces old .aspire/settings.json split config
Manage from CLI:
aspire config list / get / set
aspire secret set/list/get/delete
aspire certs clean/trust
Every sample in this talk has one at its root.
How It Works
The three patterns that make polyglot orchestration possible
Service Discovery
Aspire injects service endpoints as environment variables:
services__api__http__0=http://localhost:5000
services__frontend__http__0=http://localhost:3000
Read them in any language — same pattern everywhere:
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" );
No hardcoded URLs. No .env files. Aspire wires it.
Connection Strings
Infrastructure resources get connection strings automatically:
CONNECTIONSTRINGS__cache=localhost:6379
CONNECTIONSTRINGS__db=Host=localhost;Port=5432;Username=postgres;Password=...
CONNECTIONSTRINGS__messaging=localhost:9092
Same pattern in every language:
client = redis.from_url(f"redis://{os.environ['CONNECTIONSTRINGS__cache' ]} " )
const kafka = new Kafka ({ brokers : [process.env .CONNECTIONSTRINGS__messaging ] });
spring.datasource.url =jdbc:postgresql://${PG_HOST:localhost}:${PG_PORT:5432}/${PG_DB:notesdb}
spring.datasource.username =${PG_USER:postgres}
Aspire injects environment variables — your service reads them using its language's standard env var mechanism.
The Dashboard — One View for Everything
Same dashboard regardless of what language your services use:
Cross-language tracing example:
Trace ID: abc123...
└─ POST /api/process (Python, 245ms)
├─ POST /data (.NET, 200ms)
│ ├─ SQL SELECT (PostgreSQL, 50ms)
│ └─ POST /notify (Node.js, 100ms)
│ └─ Redis SET (25ms)
└─ Render response (40ms)
Aspire sets OTEL_EXPORTER_OTLP_ENDPOINT automatically — add OpenTelemetry to your service and traces flow to the dashboard.
CLI for Every Language
No .NET SDK required — the Aspire CLI is a standalone binary.
http://get.aspire.dev
irm https://aspire.dev/install.ps1 | iex
curl -sSL https://aspire.dev/install.sh | bash
Day-to-day workflow
aspire run
aspire run --detach
aspire ps
aspire stop
aspire describe --follow
Diagnostics & maintenance
aspire doctor
aspire restore
aspire export
aspire update --self
aspire docs search "redis"
Works identically whether your AppHost is TypeScript or C#.
aspire new — Language-Aware Scaffolding
Start a new project in your team's language:
aspire new aspire-ts-starter -n my-app
aspire new aspire-starter -n my-app
cd my-app && aspire run
What you get:
AppHost file in your chosen language
aspire.config.json pre-configured
Sample service wired up with service discovery
Dashboard ready on first run
Key Takeaways
Aspire is a polyglot orchestrator — not just for .NET
AppHost languages — C#, TypeScript, *Python, *Go, *Java
Universal patterns — services__name__http__0 and CONNECTIONSTRINGS__resource work in every language
One dashboard — Logs, traces, metrics across all services via OpenTelemetry
Standalone CLI — aspire run / aspire doctor / aspire new — no .NET SDK required
40+ integrations — Redis, Kafka, PostgreSQL, CosmosDB — richest in C#, growing in all SDKs
Deploy anywhere — Same AppHost model for local dev, staging, and production
Open source — github.com/dotnet/aspire — MIT license
Resources
Follow Chris Ayers
Architecture Overview
Aspire Polyglot Cheat Sheet
Runtime → Method (C# SDK)
Python / ASGI → AddPythonApp()
Python / Uvicorn → AddUvicornApp()
Node.js → AddNodeApp()
Vite / React → AddViteApp()
.NET project → AddProject<T>()
JavaScript → AddJavaScriptApp()
Any Dockerfile → AddDockerfile()
Any executable → AddExecutable()
Common Patterns
.WithReference(redis)
.WaitFor(postgres)
.WithHttpEndpoint(env: "PORT" )
.WithExternalHttpEndpoints()
.WithUv()
.WithNpm()
.WithBun()
.WithBuildSecret("key" , secret)
.WithHttpHealthCheck("/health" )
.WithMcpServer("mcp" )
Infrastructure: AddRedis("name") · AddPostgres("name").AddDatabase("db") · AddKafka("name") · AddAzureCosmosDB("name").RunAsEmulator()
OpenTelemetry Setup by Language
Python:
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)
Node.js:
const { NodeTracerProvider } =
require ('@opentelemetry/sdk-trace-node' );
const { OTLPTraceExporter } =
require ('@opentelemetry/exporter-trace-otlp-grpc' );
const { BatchSpanProcessor } =
require ('@opentelemetry/sdk-trace-base' );
const provider = new NodeTracerProvider ();
provider.addSpanProcessor (
new BatchSpanProcessor (new OTLPTraceExporter ({
url : process.env .OTEL_EXPORTER_OTLP_ENDPOINT
}))
);
provider.register ();
Aspire sets OTEL_EXPORTER_OTLP_ENDPOINT automatically — add this boilerplate once per service and traces flow to the dashboard.
Resource Lifecycle Management
Dependency Order: Infrastructure → Backend Services → Frontend
Health Monitoring: WithHttpHealthCheck("/health") — automatic restarts on failure
Graceful Shutdown: Clean termination of all processes
Deploying Polyglot Aspire Apps
aspire run
aspire deploy
aspire publish
What Aspire generates:
Container images for Python, Node.js, .NET, Go, Java
Azure Container Apps / Kubernetes manifests
Infrastructure wiring for Redis, Postgres, CosmosDB, Kafka
Service connections + environment variables
Same AppHost model for local, staging, and production.
We've all been there: README says "just run docker-compose up" but it never works the first time. Each language has its own logging, its own config format, its own service discovery pattern. You end up with hardcoded URLs everywhere.
This is the Aspire AppHost. It's the central brain that starts everything and wires it together. Python, React, .NET — all visible in one dashboard, auto-wired, observable. No .NET SDK required for non-.NET devs if you use a polyglot AppHost.
The TypeScript AppHost uses the same integration packages as C#, auto-generated via [AspireExport] attributes. A JS/TS team never needs to touch .NET.
Each language has its own idioms — Python uses snake_case, TypeScript uses camelCase, Go returns errors — but the Aspire model is the same everywhere. The C# SDK has the most typed integrations, but all five languages can orchestrate any service via Dockerfile or container.
This is the key file. Drop aspire.config.json in your project root, point it at your AppHost file, set the language, and aspire run just works. The CLI reads this to know how to build and launch your app.
Now let's look at the three mechanisms that power everything: service discovery, connection strings, and the dashboard. These work the same regardless of AppHost language.
This is the magic. The double underscore __ is used because environment variables can't have colons. Every language can read env vars — that's the universal interface.
Aspire handles the complexity of connection strings so you don't have to 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.
This is the killer feature. 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.
The standalone CLI bundles DCP — the Developer Control Plane. A Python or Java developer uses the exact same commands as a .NET developer. aspire doctor is great for troubleshooting environment issues before a talk or demo.
aspire new is language-aware — it scaffolds the right AppHost structure for your chosen language. This is the fastest way to start a polyglot project.
Time to see Aspire in action! We'll walk through 8 samples — each running live with the Aspire dashboard. Watch for: service startup ordering, cross-language logs, and the unified trace view.
The key message: if your team uses multiple languages, Aspire gives you a single place to define, run, observe, and deploy your entire stack. The AppHost language is a choice, not a constraint.
The AppHost is the central orchestrator. All services report their telemetry to the unified dashboard.
Keep this slide handy as a quick reference!