Polyglot Aspire

Orchestrating Any Language with Aspire

Chris Ayers

Chris Ayers

Principal Software Engineer
Azure CXP AzRel
Microsoft

BlueSky: @chris-ayers.com
LinkedIn: - chris-l-ayers
Blog: https://chris-ayers.com/
GitHub: Codebytes
Mastodon: @Chrisayers@hachyderm.io

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 Dashboard โ€” One View for Everything

Same dashboard regardless of what language your services use:

๐Ÿ“‹ Resources โ€” All services, containers, status, endpoints
๐Ÿ“œ Console Logs โ€” Real-time stdout/stderr from every process
๐Ÿ“Š Structured Logs โ€” Parsed JSON logs, filter by level
๐Ÿ” Traces โ€” Distributed request tracing across services
๐Ÿ“ˆ Metrics โ€” Latency, CPU/memory, custom metrics

Aspire sets OTEL_EXPORTER_OTLP_ENDPOINT automatically โ€” add OpenTelemetry to your service and traces flow to the dashboard.

OpenTelemetry Setup by Language

Add OpenTelemetry once per service โ€” Aspire handles the rest:

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 { 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();

Aspire sets OTEL_EXPORTER_OTLP_ENDPOINT automatically โ€” add this boilerplate once per service and traces flow to the dashboard.

Architecture Overview

AppHost orchestrates everything โ€” DCP manages processes, Dashboard collects telemetry

center

How It Works

The patterns that make polyglot orchestration possible

Service Discovery

Aspire injects service endpoints as environment variables:

# Pattern: services__<name>__<protocol>__<index>
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:

# Pattern: CONNECTIONSTRINGS__<resource>
CONNECTIONSTRINGS__cache=localhost:6379
CONNECTIONSTRINGS__db=Host=localhost;Port=5432;Username=postgres;Password=...
CONNECTIONSTRINGS__messaging=localhost:9092

Same pattern in every language:

# Python โ€” Redis
client = redis.from_url(f"redis://{os.environ['CONNECTIONSTRINGS__cache']}")
// Node.js โ€” Kafka
const kafka = new Kafka({ brokers: [process.env.CONNECTIONSTRINGS__messaging] });
// Java โ€” PostgreSQL via env vars
String url = "jdbc:postgresql://" + System.getenv("PG_HOST") + ":" + System.getenv("PG_PORT") + "/" + System.getenv("PG_DB");

Aspire injects environment variables โ€” your service reads them using its language's standard env var mechanism.

Resource Lifecycle Management

center

Dependency Order: Infrastructure โ†’ Backend Services โ†’ Frontend
Health Monitoring: WithHttpHealthCheck("/health") โ€” automatic restarts on failure
Graceful Shutdown: Clean termination of all processes

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 PREVIEW

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 EXPERIMENTAL

api = builder.add_dockerfile("api", "./src")
api.with_http_endpoint(target_port=8080, env="PORT")

โ˜• Java โ€” AppHost.java EXPERIMENTAL

builder.addDockerfile("api", "./src", null, null)
  .withExternalHttpEndpoints();

๐ŸŸข Go โ€” apphost.go EXPERIMENTAL

api, _ := builder.AddDockerfile("api", "./src", nil, nil)
api.WithHttpEndpoint(nil, float64Ptr(8080), stringPtr("http"), nil, nil)

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()
  • Go โ†’ AddGolangApp() (Community Toolkit)
  • Java / Spring Boot โ†’ AddSpringApp() (Community Toolkit)
  • Any executable โ†’ AddExecutable()

Common Patterns

.WithReference(redis)
.WaitFor(postgres)
.WithHttpEndpoint(env: "PORT")
.WithExternalHttpEndpoints()
.WithUv()
.WithNpm()
.WithBun()
.WithBuildSecret("key", secret)
.WithRunScript("dev")
.WithHttpHealthCheck("/health")
.WithMcpServer("mcp")

Infrastructure: AddRedis("name") ยท AddPostgres("name").AddDatabase("db") ยท AddKafka("name") ยท AddAzureCosmosDB("name").RunAsEmulator()

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" },
  "channel": "stable",
  "features": { "polyglotSupportEnabled": true },
  "profiles": {
    "default": {
      "applicationUrl": "https://localhost:17000"
    }
  }
}

What it does:

  • appHost.path + appHost.language โ€” declares your stack
  • 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")

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.

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    # Run / background
aspire ps / aspire stop      # List / stop
aspire describe --follow     # Watch resources
aspire doctor                # Environment check
aspire otel / aspire logs    # Telemetry & logs
aspire export                # Capture to zip

Demos

8 samples โ€” live with the Aspire dashboard

8 Live Demos

Simple โ†’ Full-stack

  1. ts-starter โ€” Express + React (TS AppHost)
  2. flask-markdown-wiki โ€” Flask + Redis (Python AppHost)
  3. vite-react-api โ€” FastAPI + React + Redis (TS AppHost)
  4. 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

center

Demo: Flask Markdown Wiki

Python AppHost โ†’ Flask Wiki App + Redis Cache

center

Demo: Vite React + FastAPI

TypeScript AppHost โ†’ React Frontend + Python FastAPI + Redis

center

Demo: Django HTMX Polls

Python AppHost โ†’ Django + HTMX + PostgreSQL

center

Demo: Spring Boot Notes

Java AppHost โ†’ Spring Boot API + PostgreSQL

center

Demo: Svelte + Go Bookmarks

Go AppHost โ†’ Go REST API + Svelte Frontend + PostgreSQL

center

Demo: Angular + .NET + CosmosDB

C# AppHost โ†’ Angular Frontend + .NET API + Azure CosmosDB

center

Demo: Polyglot Event Stream

C# AppHost โ†’ .NET Producer + Python Consumer + Node.js Dashboard + Kafka

center

From Dev to Production

Same AppHost model for local, staging, and production.

aspire run       # Local development
aspire deploy    # Deploy to target (Preview)
aspire publish   # Generate artifacts (Preview)
aspire do        # Pipeline step (Preview)

What Aspire generates:

  • Container images for all languages
  • Azure Container Apps / Kubernetes manifests
  • Infrastructure wiring (Redis, Postgres, Kafkaโ€ฆ)
  • Service connections + environment variables

Wrap-Up

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

Questions?

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.

Transition from problem to solution

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.

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 is how you get your service into that dashboard we just saw. Add the OTel SDK, point at the env var Aspire injects, and you're done.

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. 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.

Aspire manages startup ordering automatically based on WithReference and WaitFor dependencies. Infrastructure comes up first, then backends, then frontends.

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.

Keep this slide handy as a quick reference for everything we just covered!

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.

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.

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.

The same AppHost that runs locally can drive your deployment pipeline. No separate config needed.

If your team uses multiple languages, Aspire gives you a single place to define, run, observe, and deploy your entire stack.