Mittelstand Radar: Kaufsignale aus dem deutschen Mittelstand — sichern Sie sich Ihre erste Report-Ausgabe.Zur Warteliste
Mehrere Frachtschiffe auf See von oben fotografiert, die in verschiedene Richtungen fahren
Zurück zum Blog
dotnetaspiredeployment

aspire deploy – Was passiert wirklich?

Sven HennessenDevOps

Aspire 13 brachte echte Plattformbreite beim Deployment: Docker Compose, Kubernetes, AKS und Azure App Service als vollständig unterstützte Ziele – mit Helm unter der Haube.

In Aspire 9 gab es einen einzigen vollständig unterstützten Deployment-Pfad: Azure Container Apps via azd. Ab Aspire 13 änderte sich das grundlegend. Dieser Artikel beschreibt, was aspire deploy für jedes unterstützte Target tatsächlich tut – und wo die aktuellen Grenzen liegen.

Das gemeinsame Fundament: der Pipeline-Schritt

Unabhängig vom Deployment-Target läuft jedes aspire deploy durch dieselbe Pipeline-Architektur. Jede Ressource im AppHost trägt Schritte bei, die Abhängigkeiten untereinander deklarieren. Aspire baut daraus einen Ausführungsgraphen und führt unabhängige Schritte parallel aus. Was früher eine sequenzielle Abfolge von azd-Aufrufen war, ist jetzt ein sichtbarer, inspektierbarer Prozess:

aspire deploy --list-steps   # Schritte anzeigen ohne auszuführen
aspire deploy                # Deployment ausführen
aspire destroy               # Deployment rückgängig machen

Azure Container Apps

Der etablierte Weg aus Aspire 9 blieb erhalten und läuft jetzt über die Pipeline. Neu ist, dass das Azure Container Registry nicht mehr implizit an die Container Apps Environment gebunden ist, sondern als eigene Ressource provisioniert wird. Das ermöglicht Wiederverwendung über Environments hinweg und parallele Image-Pushes während der Provisionierung.

var registry = builder.AddAzureContainerRegistry("acr");
var env = builder.AddAzureContainerAppEnvironment("prod")
    .WithAcrPullIdentity(registry);

Container App Jobs – für batch-artige Workloads und Hintergrundprozesse – sind ab 13.4 stabil:

builder.AddProject<Projects.Worker>("worker")
    .PublishAsAzureContainerAppJob();

Azure App Service

Azure App Service wurde in 13.0 als vollständig unterstütztes Deployment-Target aufgenommen. Zielgruppe sind Teams, die bestehende App-Service-Infrastruktur weiternutzen wollen, ohne auf Container Apps zu migrieren.

builder.AddAzureAppServiceEnvironment("staging");

Ab 13.1 kamen Deployment Slots dazu. Zero-Downtime-Deployments lassen sich damit direkt aus dem AppHost konfigurieren: Aspire deployt auf den Staging-Slot und führt den Swap gegen Production automatisch durch.

builder.AddProject<Projects.Api>("api")
    .WithDeploymentSlot("staging");

Das Aspire Dashboard wird bei App Service Deployments automatisch als Application Insights-Integration eingebunden.

Docker Compose

Docker Compose durchlief in der 13.x-Reihe einen klaren Reifeweg:

  • 13.1: Erstmals end-to-end Deploy-Unterstützung – aspire deploy funktionierte vollständig
  • 13.2: Preview-Status verlassen, stable

Der AppHost beschreibt die Anwendungsstruktur wie gewohnt. Aspire generiert beim Publish eine docker-compose.yaml und führt beim Deploy docker compose up aus. aspire destroy fährt den Stack wieder herunter.

builder.AddDockerComposeEnvironment("local");

Docker Compose ist damit der einzige vollständig unterstützte Deployment-Pfad ohne Cloud-Abhängigkeit – relevant für On-Premises-Szenarien, Kundenumgebungen ohne Azure-Zugang oder einfache Staging-Setups.

Kubernetes (generisch)

Ab Aspire 13.3 unterstützt aspire deploy auch generische Kubernetes-Cluster end-to-end. Aspire generiert Helm-Charts aus dem AppHost und führt helm install bzw. helm upgrade gegen das konfigurierte Cluster aus.

builder.AddKubernetesEnvironment("k8s")
    .WithHelm(helm => helm.ReleaseName("myapp").Namespace("production"));

aspire destroy führt helm uninstall aus und entfernt Namespaces. Die generierten Charts enthalten für jede Ressource eigene Template-Dateien – Deployments, Services, ConfigMaps, Secrets und PersistentVolumeClaims.

Routing und TLS sind ab 13.3 direkt im AppHost modellierbar:

builder.AddIngress("api-ingress")
    .WithPath("/api", api)
    .WithTls();

Aspire generiert daraus die entsprechenden Kubernetes Ingress-Ressourcen. Wenn WithTls() ohne expliziten Hostnamen verwendet wird, erkennt Aspire den vom Cluster zugewiesenen FQDN automatisch.

Ab 13.4 ist cert-manager mit Let's Encrypt als typisierte API integrierbar:

env.AddCertManager()
   .WithLetsEncrypt(staging: false);

Ebenfalls in 13.4: externe Helm-Charts lassen sich als Deployment-Schritte einbinden, sodass Aspire-Ressourcen und Standard-Ecosystem-Charts in einer Pipeline laufen:

builder.AddHelmChart("nginx-ingress", "ingress-nginx/ingress-nginx", "4.10.0");

Azure Kubernetes Service (AKS)

AKS erhielt in 13.3 eine eigene Hosting-Integration (Aspire.Hosting.Azure.Kubernetes), die Cluster-Provisionierung und Helm-Deployment in einer Pipeline kombiniert:

builder.AddAzureKubernetesEnvironment("prod-aks")
    .WithHelm();

Aspire generiert Bicep-Templates für den Cluster und führt danach den Helm-Deployment-Schritt aus – ohne separate Terraform-Konfiguration oder manuell erstellte Cluster. Node Pools, SKU-Tiers und private Cluster sind konfigurierbar.

Ab 13.4 ist Azure Application Gateway for Containers (AGC) direkt integrierbar. Aspire weist der Controller-Identity automatisch die erforderliche Network Contributor-Rolle zu:

env.AddLoadBalancer("agc");

Kubernetes: Stolperfallen in der Praxis

PublishAsDockerFile und das fehlende args-Problem

Eine stille Falle betrifft Python- und JavaScript-Services: Wer bei einer Uvicorn-App .PublishAsDockerFile(configure => { }) aufruft, verliert damit die generierten args im Helm-Deployment-Manifest – ohne Fehler, ohne Warnung. Der Container startet mit dem falschen Einstiegspunkt.

Das Aspire-Team hat das Issue mit dem Label silent-failure markiert: Das Verhalten ist beabsichtigt (das Callback signalisiert „ich übernehme die vollständige Kontrolle"), aber die Konsequenz ist nicht dokumentiert. Wer PublishAsDockerFile mit einem leeren Callback nutzt, muss die args explizit setzen:

builder.AddUvicornApp("app", "./app", "main:app")
    .WithUv()
    .PublishAsDockerFile(configure =>
    {
        configure.WithArgs("main:app", "--host", "0.0.0.0", "--port", "8000");
    });

Bis das behoben ist (microsoft/aspire#16874): Immer nach dem Publish die generierten templates/*/deployment.yaml auf vorhandene args-Blöcke prüfen.

Sidecar-Container und PersistentVolumes

Sidecar-Container werden aktuell nicht korrekt in Kubernetes-Manifeste übernommen – sie landen als separate Pods, was gemeinsame Volumes unmöglich macht. Wer Sidecars im AppHost definiert und auf Kubernetes deployt, bekommt kein Fehler, aber auch kein korrektes Manifest.

WithPersistentVolume existiert seit 13.4, ist aber als [Experimental("ASPIRECOMPUTE002")] markiert. Stateful-Workloads (Datenbanken, Message Broker) auf Kubernetes sind damit grundsätzlich möglich – mit dem Hinweis, dass die API-Oberfläche noch nicht stabil ist. Ein praktischer Nebeneffekt: Aspire promoted das Deployment automatisch von Deployment zu StatefulSet, sobald ein PVC gebunden ist. Das ist korrekt, kann aber bestehende helm upgrade-Läufe brechen, wenn ein Workload zuvor als Deployment deployed wurde.

Das Zwei-Deploys-Problem

Ein strukturelles Limit betrifft Services, die beim ersten Start Bootstrap-Daten produzieren, die ihre Consumer brauchen – Keycloak (Client-Secrets), Vault (Tokens), MinIO (Access Keys), Grafana (API-Tokens). Im lokalen Betrieb über aspire run funktioniert das über ResourceReadyEvent reibungslos. Im Deploy-Modus gibt es keinen äquivalenten Mechanismus.

Der aktuelle Workaround ist ein Zwei-Deploys-Pattern: Beim ersten aspire deploy läuft der Bootstrap-Service an, danach führt ein eigener Pipeline-Step die Provisioning-API auf und speichert die Werte in IDeploymentStateManager. Beim nächsten aspire deploy sind die Werte als gespeicherter State vorhanden und fließen in die abhängigen Services. Das funktioniert – ist aber nicht idempotent beim ersten Lauf und nirgends offiziell dokumentiert.

Das zugrunde liegende Feature-Request (microsoft/aspire#18097) beschreibt ein AddDeferredValue-Konzept, das genau das lösen würde. Stand Juni 2026 ist es noch offen.

Was noch fehlt

AWS, GCP und vollständig selbstverwaltete On-Premises-Umgebungen jenseits von Docker Compose haben weiterhin keine offiziellen Aspire-Integrations. Das Pipeline-Modell ist über WithPipelineStepFactory erweiterbar – eigene Provider müssen jedoch vollständig selbst implementiert werden. Kubernetes-generisch unterstützt aspire deploy die Cluster-Provisionierung nicht; der Cluster muss bereits existieren.

Unterstützung benötigt?

Du willst aspire deploy in eurer Produktionsumgebung einsetzen – ob auf Kubernetes, AKS oder Docker Compose – aber weißt nicht, wo du anfangen sollst oder welche Fallstricke euch erwarten? Wir helfen dir gerne dabei! Melde dich einfach über unsere Kontaktseite und wir schauen gemeinsam, wie wir euren Deployment-Workflow mit Aspire 13 aufsetzen.