Die meisten sichtbaren Änderungen in Aspire 13 sind Features: neue Deployment-Targets, neue Sprachen, neue Integrationen. Weniger offensichtlich, aber fundamentaler, sind die Änderungen darunter: Wie Aspire intern Deployment-Schritte koordiniert, wie der AppHost selbst aussieht, und wie die CLI-Oberfläche aufgebaut ist. Dieser Artikel beschreibt die architektonischen Entscheidungen, die alles andere ermöglichen.
Das Ende des Publisher-Modells
In Aspire 9 gab es ein Erweiterungsmodell für Deployment: das IDistributedApplicationPublisher-Interface. Wer einen eigenen Deployment-Pfad bauen wollte, implementierte dieses Interface, registrierte es per WithPublishingCallback und bekam einen PublishingContext übergeben. Das Modell war sequenziell, schwer testbar und eng an das Manifest-Format gebunden.
Aspire 13 entfernte diese APIs vollständig. Ersatz ist das Pipeline-Modell.
Das Pipeline-Modell: PipelineStep und Dependency-Graph
Statt eines zentralen Publishers tragen Ressourcen im AppHost jetzt eigene PipelineStep-Objekte bei. Jeder Schritt deklariert explizit, von welchen anderen Schritten er abhängt. Aspire baut daraus einen gerichteten Graphen und führt unabhängige Schritte parallel aus.
resource.WithPipelineStepFactory((context, steps) =>
{
var build = steps.AddStep("build-image", async ctx => {
// Container-Image bauen
});
var push = steps.AddStep("push-image", async ctx => {
// Image in Registry pushen
}).DependsOn(build);
var deploy = steps.AddStep("deploy", async ctx => {
// Auf Cluster deployen
}).DependsOn(push);
});
Das Ergebnis ist ein Deployment-Prozess, der von Aspire selbst parallelisiert wird, wo immer es die Abhängigkeiten erlauben. Images für unabhängige Services werden gleichzeitig gebaut und gepusht; Provisionierung und Build laufen parallel, solange kein Schritt das Ergebnis des anderen braucht.
Die Pipeline ist inspektierbar, bevor sie ausgeführt wird:
aspire deploy --list-steps # Alle Schritte und Abhängigkeiten anzeigen
aspire deploy --pipeline-log-level debug # Verbose-Output pro Schritt
aspire deploy --environment staging # Gezielt auf eine Umgebung deployen
Deployment State Persistence
Ein einfacher aber wirksamer Wechsel: Aspire 13 merkt sich zwischen Deployments, was bereits eingegeben wurde.
In Aspire 9 fragte azd up bei jedem Durchlauf nach Subscription, Region und Ressourcengruppe. In Aspire 13 werden diese Angaben pro Projekt und Umgebung im Benutzerprofil gespeichert:
~/.aspire/deployments/<project-hash>/<environment>.json
Der nächste aspire deploy-Aufruf belegt die Werte vor. --clear-cache setzt den gespeicherten Zustand zurück. Wer zwischen mehreren Umgebungen wechselt, gibt den Namen per --environment an:
aspire deploy --environment production
aspire deploy --environment staging --clear-cache
Single-File AppHost
Bisher erforderte jedes Aspire-Projekt ein eigenes C#-Projektfile mit Paketverweisen. Ab Aspire 13 lässt sich ein AppHost als einzelne Datei schreiben, ohne separates .csproj:
#:sdk Aspire.AppHost.Sdk@13.0.0
#:package Aspire.Hosting.PostgreSQL@13.0.0
#:package Aspire.Hosting.Redis@13.0.0
var builder = DistributedApplication.CreateBuilder(args);
var cache = builder.AddRedis("cache");
var db = builder.AddPostgres("db");
var api = builder.AddProject<Projects.Api>("api")
.WithReference(cache)
.WithReference(db);
builder.Build().Run();
Die #:sdk- und #:package-Direktiven ersetzen das Projektfile. Das SDK lädt die angegebenen Pakete beim ersten Start automatisch herunter. Für kleinere Setups, Prototypen oder Demos entfällt damit die Projektstruktur als Einstiegshürde.
TypeScript AppHost
Ab Aspire 13.4 (GA) lässt sich der AppHost auch in TypeScript schreiben. Statt eines C#-Projekts ist der Einstiegspunkt eine .mts-Datei:
import { createBuilder } from "@aspire/hosting";
import { addPostgres } from "@aspire/hosting-postgresql";
import { addRedis } from "@aspire/hosting-redis";
const builder = await createBuilder(process.argv);
const cache = addRedis(builder, "cache");
const db = addPostgres(builder, "db");
builder.addProject("api", "../Api/Api.csproj")
.withReference(cache)
.withReference(db);
await builder.build().run();
Die TypeScript-API spiegelt die C#-Oberfläche: dieselben Konzepte, dieselben Methoden, generiert aus denselben XML-Dokumentationskommentaren. Aspire validiert den TypeScript-Code vor dem Start.
Wer ein TypeScript-Frontend oder einen Python-Backend-Service orchestriert, braucht damit kein .NET im Toolchain des AppHost selbst – nur die Aspire CLI.
Die neue Aspire CLI
Parallel zur Pipeline-Architektur wurde die CLI-Oberfläche neu strukturiert. In 9.x war der primäre Weg azd up; ab Aspire 13.4 sind folgende Befehle stabil:
| Befehl | Funktion |
|---|---|
aspire deploy | Deployment ausführen |
aspire publish | Deployment-Artefakte generieren (Helm-Charts, Compose-Files) |
aspire destroy | Deployment rückgängig machen |
aspire run | AppHost lokal starten |
aspire doctor | Umgebungsdiagnose (Versionen, Konflikte) |
aspire integration list | Verfügbare Hosting-Integrationen auflisten |
aspire logs --search | Logs mit serverseitiger Filterung abrufen |
aspire ls | Alle AppHosts im aktuellen Verzeichnis auflisten |
aspire doctor ist besonders nützlich in Umgebungen, in denen mehrere Aspire-Versionen koexistieren: Es prüft CLI-Version, SDK-Version, installierte Tools (Helm, Docker) und meldet Konflikte.
Bekannte Lücke: aspire doctor prüft aktuell, ob Docker läuft – aber nicht, ob das docker buildx-Plugin installiert ist. Auf frischen Ubuntu-Installationen mit docker.io (ohne docker-buildx) schlägt der Image-Build-Schritt mit einer irreführenden Fehlermeldung fehl:
Container runtime 'Docker' is not running or is unhealthy.
Die eigentliche Ursache ist das fehlende Buildx-Plugin. apt-get install -y docker-buildx löst das. Das Issue (microsoft/aspire#16118) ist bekannt und noch offen – aspire doctor wird diesen Check künftig abdecken.
Was das Pipeline-Modell noch nicht löst
Das Pipeline-Modell macht Deployment-Schritte sichtbar und parallelisierbar – eine echte Verbesserung gegenüber dem sequenziellen Publisher-Modell. Eine strukturelle Lücke bleibt aber: Werte, die ein Service erst nach dem Start produziert (Keycloak-Client-Secrets, Vault-Tokens, MinIO-Access-Keys), können nicht in derselben Pipeline-Ausführung an abhängige Services weitergegeben werden.
Lokal funktioniert das über ResourceReadyEvent nahtlos. Im Deploy-Modus gibt es dafür noch kein Äquivalent. Der aktuelle Workaround ist ein Zwei-Deploys-Pattern: Erster Deploy startet den Service, ein eigener Pipeline-Step provisioniert ihn und speichert die Werte im Deployment-State. Zweiter Deploy liest den State und injiziert die Werte. Wer Services dieser Art deployt, sollte das beim ersten aspire deploy einplanen (microsoft/aspire#18097).
