Mittelstand Radar: Buying signals from the German mid-market — secure your first report edition.Join the waitlist
Colorful lines of programming code on a dark screen
Back to blog
aspirepythonjavascript

Aspire Goes Polyglot: Python, JavaScript, Go, and TypeScript

Sven HennessenDevOps

Aspire was long called .NET Aspire – that's history now. Since version 13, Python, JavaScript, Go, and Bun are equal citizens in the AppHost. What this means for heterogeneous microservice landscapes.

In Aspire 9, "distributed .NET application" meant exactly that: .NET. Python services, Node.js backends, or Go binaries could be included as containers, but without real integration – no automatic service discovery, no connection string injection, no debugging support. From Aspire 13, that changed.

What "First Class" Means

The difference between a container and a first-class service in the AppHost is not trivial. A container runs, and that's it. A first-class service gets:

  • Automatic connection strings – references are injected as typed environment variables
  • Service discovery – other services find it by name
  • Local development without Docker – the process starts directly on the host, with the native runtime
  • Automatic Dockerfile generation – for deployment
  • Dashboard integration – logs and traces appear in the Aspire Dashboard

This applies to Python, JavaScript, Go, and Bun since Aspire 13.

Python

Python services are registered via AddPythonApp. Aspire automatically detects whether the project uses uv, pip, or venv, and sets up the runtime environment accordingly:

var ml = builder.AddPythonApp("inference", "../Inference")
    .WithReference(db)
    .WithHttpEndpoint(port: 8000);

For FastAPI and Uvicorn-based services, there's AddUvicornApp:

var api = builder.AddUvicornApp("fastapi", "../FastApiService", "main:app")
    .WithReference(cache)
    .WithExternalHttpEndpoints();

Connection strings are passed as environment variables, read on the Python side via os.environ as usual. The SSL certificate for HTTPS connections between services is automatically set as SSL_CERT_FILE.

Aspire automatically generates a Dockerfile from the Python project during aspire publish – no manual writing required.

JavaScript and Node.js

AddJavaScriptApp replaces the earlier AddNpmApp and supports npm, Yarn, and pnpm – detection happens automatically based on the lockfiles in the project directory:

var frontend = builder.AddJavaScriptApp("frontend", "../Frontend", "npm run dev")
    .WithReference(api)
    .WithExternalHttpEndpoints();

For Vite-based frontends:

var frontend = builder.AddViteApp("frontend", "../Frontend")
    .WithReference(api);

From Aspire 13.4, Bun is available as a runtime:

var worker = builder.AddBunApp("worker", "../Worker", "src/index.ts")
    .WithReference(queue);

Node-specific: the SSL certificate for internal HTTPS connections is injected as NODE_EXTRA_CA_CERTS, so fetch and HTTPS clients can communicate with other Aspire services without additional configuration.

Go

Go support arrived in Aspire 13.4 with the Aspire.Hosting.Go package:

var service = builder.AddGoApp("goservice", "../GoService")
    .WithReference(db)
    .WithHttpEndpoint(port: 8080);

In local development, Aspire starts the service via go run. Debugging is integrated through the dlv-dap adapter for VS Code. For deployment, Aspire generates a Dockerfile.

TypeScript AppHost – Orchestration Without .NET

Anyone orchestrating a landscape of Python, Node, and Go services no longer needs .NET in the AppHost toolchain with Aspire 13.4. The AppHost can be written entirely in TypeScript:

import { createBuilder } from "@aspire/hosting";
import { addPostgres } from "@aspire/hosting-postgresql";
import { addPythonApp } from "@aspire/hosting-python";

const builder = await createBuilder(process.argv);

const db = addPostgres(builder, "db");
const inference = addPythonApp(builder, "inference", "../Inference")
    .withReference(db);
const frontend = builder.addJavaScriptApp("frontend", "../Frontend", "npm run dev")
    .withReference(inference);

await builder.build().run();

The TypeScript API mirrors the C# surface 1:1 – generated from the same XML documentation comments. That means: anyone who knows the C# API will immediately feel at home in the TypeScript version.

What's Still in Development

Java is on the roadmap as another deployment type but is not yet available. Rust is discussed but not planned. The existing languages – Python, JavaScript, Go, TypeScript – are stable as of 13.4; Blazor WebAssembly is still marked as preview.

Known Pitfalls

TypeScript AppHost: Parameter Without Default Hangs CI Pipelines

builder.addParameter('name') without an explicit default value blocks aspire run in non-interactive environments – such as CI pipelines or when stdin is not a TTY. The process hangs with no error message and no timeout. This is particularly insidious because it works fine locally with a terminal window.

// Hangs in CI:
const secret = await builder.addParameter('db-password');

// Correct: set a default
const secret = await builder.addParameter('db-password', { value: process.env.DB_PASSWORD ?? '' });

The issue (microsoft/aspire#17678) is still open. Until it's resolved: always set a default value, even if it's empty.

Python/JavaScript on Kubernetes: PublishAsDockerFile Deletes Container Args

Adding a PublishAsDockerFile callback to a Uvicorn or JavaScript service – even an empty one – causes the generated args to disappear from the Helm manifest. The container then starts without the configured startup parameters. No error, no warning.

The Aspire team has marked the issue as silent-failure (microsoft/aspire#16874). Workaround: set the args explicitly in the callback, or omit the callback entirely and provide a manual Dockerfile instead.

Need support?

Want to integrate Python, JavaScript, or Go services into your Aspire AppHost, but unsure how to cleanly orchestrate and deploy a heterogeneous microservice landscape? We're happy to help! Get in touch and we'll work together to set up your polyglot stack with Aspire.

Get in touch