In Aspire 9 bedeutete "verteilte .NET-Anwendung" genau das: .NET. Python-Services, Node.js-Backends oder Go-Binaries ließen sich zwar als Container einbinden, aber ohne echte Integration – keine automatische Service Discovery, keine Verbindungsstring-Injektion, kein Debugging-Support. Ab Aspire 13 ist das anders.
Was "erster Klasse" bedeutet
Der Unterschied zwischen einem Container und einem first-class Service im AppHost ist nicht trivial. Ein Container läuft, mehr nicht. Ein first-class Service bekommt:
- Automatische Verbindungsstrings – Referenzen werden als typisierte Umgebungsvariablen injiziert
- Service Discovery – andere Services finden ihn über seinen Namen
- Lokale Entwicklung ohne Docker – der Prozess startet direkt auf dem Host, mit dem nativen Runtime
- Automatische Dockerfile-Generierung – für das Deployment
- Dashboard-Integration – Logs und Traces erscheinen im Aspire Dashboard
Das gilt seit Aspire 13 für Python, JavaScript, Go und Bun.
Python
Python-Services werden über AddPythonApp eingebunden. Aspire erkennt automatisch, ob das Projekt uv, pip oder venv verwendet, und richtet die Laufzeitumgebung entsprechend ein:
var ml = builder.AddPythonApp("inference", "../Inference")
.WithReference(db)
.WithHttpEndpoint(port: 8000);
Für FastAPI- und Uvicorn-basierte Services gibt es AddUvicornApp:
var api = builder.AddUvicornApp("fastapi", "../FastApiService", "main:app")
.WithReference(cache)
.WithExternalHttpEndpoints();
Verbindungsstrings werden als Umgebungsvariablen übergeben, die Python-Seite liest sie wie gewohnt über os.environ. Das SSL-Zertifikat für HTTPS-Verbindungen zwischen Services wird automatisch als SSL_CERT_FILE gesetzt.
Aspire generiert beim aspire publish automatisch ein Dockerfile aus dem Python-Projekt – kein manuelles Schreiben nötig.
JavaScript und Node.js
AddJavaScriptApp ersetzt das frühere AddNpmApp und unterstützt npm, Yarn und pnpm – Erkennung erfolgt automatisch anhand der Lockfiles im Projektverzeichnis:
var frontend = builder.AddJavaScriptApp("frontend", "../Frontend", "npm run dev")
.WithReference(api)
.WithExternalHttpEndpoints();
Für Vite-basierte Frontends:
var frontend = builder.AddViteApp("frontend", "../Frontend")
.WithReference(api);
Ab Aspire 13.4 ist Bun als Runtime verfügbar:
var worker = builder.AddBunApp("worker", "../Worker", "src/index.ts")
.WithReference(queue);
Node-spezifisch: Das SSL-Zertifikat für interne HTTPS-Verbindungen wird als NODE_EXTRA_CA_CERTS injiziert, sodass fetch und HTTPS-Clients ohne zusätzliche Konfiguration gegen andere Aspire-Services kommunizieren können.
Go
Go-Support kam in Aspire 13.4 mit dem Paket Aspire.Hosting.Go:
var service = builder.AddGoApp("goservice", "../GoService")
.WithReference(db)
.WithHttpEndpoint(port: 8080);
In der lokalen Entwicklung startet Aspire den Service via go run. Debugging ist über den dlv-dap-Adapter für VS Code integriert. Für das Deployment generiert Aspire ein Dockerfile.
TypeScript AppHost – Orchestrierung ohne .NET
Wer eine Landschaft aus Python-, Node- und Go-Services orchestriert, braucht mit Aspire 13.4 kein .NET mehr im Toolchain des AppHost selbst. Der AppHost lässt sich vollständig in TypeScript schreiben:
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();
Die TypeScript-API spiegelt die C#-Oberfläche 1:1 – generiert aus denselben XML-Dokumentationskommentaren. Das bedeutet: wer die C#-API kennt, findet sich in der TypeScript-Version sofort zurecht.
Was noch in Entwicklung ist
Java ist als weiterer Deployment-Typ auf der Roadmap, aber noch nicht verfügbar. Rust ist diskutiert, aber nicht geplant. Die bestehenden Sprachen – Python, JavaScript, Go, TypeScript – sind in 13.4 als stabil einzustufen; Blazor WebAssembly ist noch als Preview markiert.
Bekannte Stolperfallen
TypeScript AppHost: Parameter ohne Default-Wert hängt CI-Pipelines
builder.addParameter('name') ohne expliziten Default-Wert blockiert aspire run in nicht-interaktiven Umgebungen – etwa in CI-Pipelines oder wenn stdin kein TTY ist. Der Prozess hängt ohne Fehlermeldung, ohne Timeout. Das ist besonders tückisch, weil es lokal mit einem Terminal-Fenster funktioniert.
// Hängt in CI:
const secret = await builder.addParameter('db-password');
// Korrekt: Default setzen
const secret = await builder.addParameter('db-password', { value: process.env.DB_PASSWORD ?? '' });
Das Issue (microsoft/aspire#17678) ist noch offen. Bis zur Lösung: Immer einen Default-Wert setzen, auch wenn er leer ist.
Python/JavaScript auf Kubernetes: PublishAsDockerFile löscht Container-Args
Wer für einen Uvicorn- oder JavaScript-Service einen PublishAsDockerFile-Callback hinzufügt – auch einen leeren –, verliert damit die generierten args im Helm-Manifest. Der Container startet dann ohne die konfigurierten Startparameter. Kein Fehler, kein Warning.
Das Aspire-Team hat das Issue mit silent-failure markiert (microsoft/aspire#16874). Workaround: Die args explizit im Callback setzen oder den Callback weglassen und stattdessen ein manuelles Dockerfile bereitstellen.
