Aspire ile Deploy: publish, deploy ve do; Docker Compose, Kubernetes ve Azure Hedefleri
aspire publish AppHost'tan yer tutuculu artifact (Compose dosyası, Helm chart'ı) üretir, aspire deploy değerleri çözüp image'ları build ederek hedefe uygular, aspire do tek bir pipeline adımını çalıştırır. Yazı Bookstore'u Docker Compose'a deploy ederken dışarı açılmayan gateway'i, production'a sızan seed verisini, https olmayan Keycloak Authority'sini ve Angular çıktı klasörünü düzeltiyor; Helm chart'ını, registry ihtiyacını ve Azure hedeflerini anlatıyor.
Sürüm notu: Bu yazıdaki kodlar .NET 10, Aspire 13.6 (Aspire.Hosting.Docker 13.6.5, Aspire.Hosting.Kubernetes 13.6.5-preview), Helm 4.3 ve kind 0.33 ile derlenip denenmiştir. Azure hedefleri dokümandan aktarılmıştır, çalıştırılmamıştır.
Önce Tahmin Et
02'de Angular uygulamasına (web) WithExternalHttpEndpoints() verdik ve bunun deploy sırasında "bu endpoint dışarıya açık olacak" demek olduğunu söyledik. 03'te de publish sırasında Angular'ın build çıktısının gateway image'ına kopyalanacağını, ayrı bir web sunucusu olmayacağını anlattık. Bu ikisini birleştirelim: Bookstore'u Docker Compose için publish ettiğimizde dışarıdan hangi service'e erişebiliriz? Web artık ayrı bir service değil; gateway ise hiçbir zaman "dışarıya açık" işaretlenmedi. Cevabı ilk publish çıktısında göreceğiz.
publish, deploy ve do: Üç Komut, Tek Pipeline
Aspire 13'te deploy bir pipeline: AppHost'taki resource'lar ve hedef ortam, çalıştırılacak adımları kendileri ekliyor. Üç komut bu pipeline'ın farklı noktalarına kadar gidiyor:
aspire publish: hedefin anlayacağı artifact'leri (Compose dosyası, Helm chart'ı, Bicep) diske yazar. Parameter'lar yer tutucu olarak kalır; hiçbir şey build edilmez, hiçbir yere uygulanmaz.aspire deploy: parameter değerlerini çözer, image'ları build edip gerekiyorsa registry'ye gönderir ve hedefe uygular. Daha önce publish edilmiş dosyaları kullanmaz, her seferinde kendisi üretir.aspire do <step>: tek bir adımı bağımlılıklarıyla birlikte çalıştırır;aspire do buildyalnız image'ları üretir.aspire destroydeploy edileni kaldırır.

Adımları görmek için --list-steps var; Bookstore'un Compose deploy'u 25 adımdan oluşuyor, sondan bir kısmı:
aspire do deploy --list-steps
17. prepare-compose
└─ Depends on: validate-compute-environments, publish, build
...
24. docker-compose-up-compose
├─ Depends on: prepare-compose, push-migrations, push-catalog, push-ordering, push-inventory,
│ push-gateway, build-migrations, build-catalog, build-ordering, build-inventory,
│ build-gateway, build-web
└─ Tags: docker-compose-up
25. deploy
└─ Depends on: docker-compose-up-compose, build-migrations, build-catalog, build-ordering,
build-inventory, build-gateway, build-web
Pipeline API'leri 13.0'da preview olarak geldi; komutların davranışı 13.6'da oturmuş durumda ama kendi adımını yazacaksan (WithPipelineStepFactory) API'nin değişebileceğini hesaba kat.
Compute Environment: Hedefi AppHost'ta Seçmek
Bir resource'un nereye gideceğini compute environment resource'u söylüyor. Docker Compose için Aspire.Hosting.Docker paketi ve builder.AddDockerComposeEnvironment("compose"), Kubernetes için Aspire.Hosting.Kubernetes ve builder.AddKubernetesEnvironment("k8s"). Docker Compose hedefi 13.2'den beri stable; Kubernetes paketi 13.6.5'te hâlâ -preview sürüm ekiyle yayınlanıyor. İkisini birden ekleyip publish ettik:
Step 'validate-compute-environments' failed: Compute resource(s) 'sql', 'redis', 'messaging', 'keycloak', 'migrations',
'catalog', 'ordering', 'inventory', 'gateway', 'mailpit', 'web' are not assigned to a compute environment, but the model
contains multiple compute environments ('compose', 'k8s'). Specify which environment each resource should target by
calling 'WithComputeEnvironment' on the resource builder.
Birden çok environment, uygulamanın farklı parçaları farklı yerlere gidecekse anlamlı: örneğin API'ler Container Apps'e, bir batch işi Kubernetes'e. O durumda her resource'a WithComputeEnvironment(...) verilir. Bizim istediğimiz ise aynı uygulamayı bazen Compose'a, bazen Kubernetes'e çıkarmak. Bunu configuration'dan okunan bir değerle seçtik:
// Deploy hedefi: varsayılan Docker Compose, "-- --DeployTarget=k8s" ile Kubernetes
if (builder.Configuration["DeployTarget"] == "k8s")
{
// Cluster image'ları bir registry'den çekiyor; adresi deploy anında verilen bir parameter (API'ler preview)
#pragma warning disable ASPIRECOMPUTE003
IResourceBuilder<ContainerRegistryResource> registry = builder.AddContainerRegistry("registry", builder.AddParameter("registry-endpoint"));
builder.AddKubernetesEnvironment("k8s").WithContainerRegistry(registry);
#pragma warning restore ASPIRECOMPUTE003
}
else
{
builder.AddDockerComposeEnvironment("compose");
}
aspire publish -- --DeployTarget=k8s ile --'dan sonraki argümanlar AppHost'a gidiyor. Registry kısmına Kubernetes bölümünde döneceğiz.
İlk Publish: Lokal Ayarlar Production'a Sızıyor
Sitedeki serilerin ortak örnek uygulaması Bookstore'u (bir online kitapçı) aspire publish -o deploy/compose ile ilk kez publish ettiğimizde pipeline başarılı oldu ama çıktıda üç sorun vardı. Birincisi Önce Tahmin Et'in cevabı:
gateway:
image: "${GATEWAY_IMAGE}"
...
expose:
- "${GATEWAY_PORT}"
Gateway yalnız expose ediliyor, yani Compose ağının içinden erişilebilir; host'a açılan bir ports satırı yok. Dışarıya açık işaretli tek resource olan web ise publish'te ayrı bir service değil, gateway'in içine giriyor. Sonuç: hiçbir şeye dışarıdan ulaşılamıyor. Düzeltme, işareti trafiği gerçekten karşılayan resource'a taşımak: gateway'e .WithExternalHttpEndpoints(). İkincisi, migration worker'ın environment'ı:
migrations:
environment:
Seed__GeneratedBooks: "200000"
04'te performans denemesi için eklediğimiz 200.000 sahte kitap production veritabanına da yazılacaktı. Üçüncüsü bir uyarıydı:
[WRN] Resource 'keycloak' has a bind mount with host-specific source path '.../Bookstore.AppHost/Realms'.
The source has been replaced with an environment variable placeholder 'KEYCLOAK_BINDMOUNT_0' which may make
the application less portable across different machines.
05'te belirttiğimiz gibi WithRealmImport lokal geliştirme için. Üç sorunun ortak sebebi aynı: AppHost'u lokal çalıştırma için yazdık ve bazı satırlar yalnız orada anlamlı. Aspire bunun için builder.ExecutionContext.IsRunMode'u veriyor:
if (builder.ExecutionContext.IsRunMode)
{
// 200.000 üretilmiş kitap yalnız lokal performans denemeleri için
migrations.WithEnvironment("Seed__GeneratedBooks", "200000");
IResourceBuilder<ParameterResource> backofficeSecret = builder.AddParameter("backoffice-client-secret", secret: true);
// Realm import yalnız lokal geliştirme için; publish/deploy'un parçası değil
IResourceBuilder<KeycloakResource> keycloak = builder.AddKeycloak("keycloak", 8080)
.WithContainerName("aspire-keycloak")
.WithRealmImport("./Realms")
.WithEnvironment("BOOKSTORE_BACKOFFICE_SECRET", backofficeSecret);
catalog.WithReference(keycloak);
// Sipariş e-postalarını lokalde yakalar; yalnız gerektiğinde dashboard'dan başlatılır
builder.AddMailpit("mailpit")
.WithContainerName("aspire-mailpit")
.WithExplicitStart();
}
else
{
// Deploy edilen ortamlar mevcut bir Keycloak kullanıyor; https Authority'si deploy anında verilen bir parameter
catalog.WithEnvironment("Keycloak__Authority", builder.AddParameter("keycloak-authority"));
}
Angular tarafında da 03'te söz verdiğimiz adım: gateway.PublishWithContainerFiles(web, "./wwwroot");. Publish sırasında Aspire, Angular'ı bir container içinde build ediyor ve çıktısını gateway image'ının wwwroot klasörüne kopyalıyor. Kopyalamak yetmiyor, gateway'in bu dosyaları sunması gerekiyor:
// Publish edilen image'da wwwroot, Angular'ın build çıktısını taşıyor; lokalde klasör yok
app.UseDefaultFiles();
app.UseStaticFiles();
app.MapDefaultEndpoints();
app.MapReverseProxy();
app.MapFallbackToFile("index.html");
Düzeltmelerden sonra publish edilen .env dosyası deploy anında doldurulacak her şeyi listeliyor; parameter'lar 02'de söylediğimiz gibi boş yer tutucu:
CATALOG_IMAGE=
CATALOG_PORT=
GATEWAY_IMAGE=
GATEWAY_PORT=
INVENTORY_IMAGE=
KEYCLOAK_AUTHORITY=
MESSAGING_PASSWORD=
MIGRATIONS_IMAGE=
ORDERING_IMAGE=
ORDERING_PORT=
REDIS_PASSWORD=
SQL_PASSWORD=
Compose dosyasında bir de bizim eklemediğimiz compose-dashboard service'i var: Aspire dashboard'unun container'ı (mcr.microsoft.com/dotnet/nightly/aspire-dashboard:13.6) ve bütün service'lerin OTEL_EXPORTER_OTLP_ENDPOINT'i ona bakıyor. Gateway'in ports satırı da artık yerinde.
Docker Compose'a Deploy
aspire deploy'un ilk denemesi image'ları build etti, sonra durdu:
(prepare-compose) ✗ Parameter resource could not be used because configuration key 'Parameters:sql-password'
is missing and the Parameter has no default value.
Parola user secrets'ta duruyordu, ama deploy varsayılan olarak Production environment'ında çalışıyor ve user secrets yalnız Development'ta okunuyor. Redis ve RabbitMQ parolaları sorun çıkarmadı: onlar Aspire'ın ürettiği parameter'lar, deploy bunları üretip deployment state'ine kaydetti. Bizim verdiğimiz parameter'ları environment variable olarak geçtik (CI'da da yol bu):
Parameters__sql-password=<password> \
Parameters__keycloak-authority=https://identity.example.com/realms/bookstore \
aspire deploy -o deploy/compose-out --non-interactive
(build-catalog) ✓ build-catalog completed successfully
(push-catalog) ✓ Successfully tagged catalog as catalog:aspire-deploy-20261114172233
...
(docker-compose-up-compose) → Running compose up for compose using Docker
(print-gateway-summary) i [INF] Successfully deployed gateway to http://localhost:32770.
(print-compose-dashboard-summary) i [INF] Successfully deployed compose-dashboard to http://localhost:32769.
✓ 39/39 steps succeeded • Total time: 38.16s
Hiçbir projeye Dockerfile yazmadık: Aspire .NET projelerinin image'larını .NET SDK'nın container desteğiyle, Angular'ınkini kendi ürettiği bir build tanımıyla oluşturuyor. Image'lar aspire-deploy-<zaman> tag'ini alıyor ve Compose bir registry gerektirmediği için yerelde kalıyor. Çalışan container'lar:
NAMES IMAGE STATUS
aspire-compose-09eb12ae-ordering-1 ordering:<tag> Up 27 seconds
aspire-compose-09eb12ae-catalog-1 catalog:<tag> Up 27 seconds
aspire-compose-09eb12ae-gateway-1 gateway:<tag> Up 34 seconds
aspire-compose-09eb12ae-migrations-1 migrations:<tag> Exited (0) 27 seconds ago
aspire-compose-09eb12ae-inventory-1 inventory:<tag> Up 35 seconds
aspire-rabbitmq rabbitmq:4.3-management Up 2 minutes
aspire-redis redis:8.6 Up 2 minutes
aspire-compose-09eb12ae-compose-dashboard-1 mcr.microsoft.com/dotnet/nightly/aspire-dashboard:13.6 Up 2 minutes
aspire-sql mcr.microsoft.com/mssql/server:2025-latest Up 2 minutes
Migration worker Exited (0): Compose dosyasında Catalog'un depends_on'u condition: "service_completed_successfully"; 02'deki WaitForCompletion Compose'a böyle çevrilmiş. Ama bu çıktıya ulaşmak iki deneme daha aldı. İlk başarılı deploy'da kitap listesi 500, ana sayfa 404 döndü. Catalog'un log'u:
System.InvalidOperationException: The MetadataAddress or Authority must use HTTPS unless disabled for development
by setting RequireHttpsMetadata=false.
AddKeycloakJwtBearer Authority'yi service discovery adresinden (https+http://keycloak/...) kuruyor; bu bir https adresi değil ve production'da RequireHttpsMetadata açık. JWT (JSON Web Token; imzalı token biçimi) handler'ı ilk istekte option'ları kurarken hata fırlatıyor ve bu, anonim endpoint'ler dahil her isteği düşürüyor. Keycloak dokümanının da önerdiği gibi production'da Authority'yi açıkça verdik:
// Deploy edilen ortamlar gerçek https Authority'yi veriyor; lokalde service discovery'den http ile geliyor
if (builder.Configuration["Keycloak:Authority"] is { Length: > 0 } authority)
{
options.Authority = authority;
}
else if (builder.Environment.IsDevelopment())
{
options.RequireHttpsMetadata = false;
}
404'ün sebebi Angular'ın varsayılan çıktı klasörü: dist/bookstore-web/browser. Aspire dist'i kopyaladığı için dosyalar gateway'de wwwroot/bookstore-web/browser altına düşmüştü. angular.json'da "outputPath": { "base": "dist", "browser": "" } ile dosyaları doğrudan dist'e aldık. Üçüncü deploy'dan sonra gateway üzerinden:
GET /api/books?page=1 200 [{"isbn":"9780060934347","title":"Don Quixote",...
GET / 200 <!doctype html> ... <title>BookstoreWeb</title>
GET /catalog 200 <!doctype html>
POST /api/orders/ 201
GET /api/orders/{id} 200 ..."status":"Confirmed"}
POST /api/books/ 401
Liste 200.000 sahte kitabı değil, yalnız seed'deki klasikleri gösteriyor; dosya karşılığı olmayan yollar (/catalog gibi) fallback ile index.html'e düşüyor, böylece Angular route'ları sayfa yenilendiğinde de çalışıyor; sipariş RabbitMQ üzerinden onaylanıyor. Token'sız kitap ekleme 401: Authority artık geçerli bir https adresi, ama gerçek bir Keycloak'a bağlanmadığımız için token'lı yolu bu ortamda denemedik.
İki uyarı. Deploy, çözdüğü değerleri deploy/compose-out/.env.Production dosyasına yazıyor ve SQL, Redis ve RabbitMQ parolaları orada açık metin olarak duruyor; bu klasör repoya girmemeli. Compose'taki dashboard'un OTLP (OpenTelemetry Protocol; telemetry'yi taşıyan protokol) endpoint'i ise korumasız (container log'unda OTLP server is unsecured uyarısı var); dashboard'un port'unu internete açma.
Kaldırmak için aspire destroy --yes: container'ları ve ağı kaldırdı ("Compose shutdown complete", toplam 1 dakika 7 saniye). Dokümana göre volume'lar da silinir; bizim denememizde SQL Server'ın named volume'u (aspire-compose-09eb12ae_aspire-sql-data) yerinde kaldı. Veri için iyi, ama "her şey gitti" diye varsayma; docker volume ls ile kontrol et.
Kubernetes: Helm Chart ve kind
aspire publish -o deploy/k8s -- --DeployTarget=k8s bir Helm chart'ı üretiyor:
Chart.yaml
values.yaml
templates/catalog/config.yaml
templates/catalog/deployment.yaml
templates/catalog/secrets.yaml
templates/catalog/service.yaml
...
templates/k8s-dashboard/deployment.yaml
templates/migrations/deployment.yaml
templates/sql/statefulset.yaml
helm lint temiz geçti, helm template 7 ConfigMap, 6 Deployment, 7 Secret, 7 Service ve 3 StatefulSet üretti. Projeler Deployment'a, SQL Server, Redis ve RabbitMQ StatefulSet'e, environment variable'lar ConfigMap ve Secret'a dönüşüyor; values.yaml'daki secrets bölümü boş, deploy sırasında dolduruluyor. Chart'ı okurken iki şey dikkatimizi çekti. Gateway'in Service'i type: "ClusterIP": Kubernetes'te WithExternalHttpEndpoints kendi başına cluster dışına bir yol açmıyor, bunun için Ingress ya da Gateway API ayarları gerekiyor (13.x'te preview). İkincisi migration worker bir Deployment: Kubernetes işi biten container'ı da yeniden başlatıyor ve chart'ta Catalog'u migration'a bekleten bir init container yok. Migration'ı Job olarak çalıştırmanın gerekçesi Kubernetes serisinin Job, CronJob ve Worker'lar yazısında.
aspire deploy chart'ı mevcut kubectl context'ine kuruyor. İlk deneme registry istedi:
Step 'push-prereq' failed: Resource 'migrations' requires image push but no container registry is available.
Compose'ta image'lar yerelde kalabiliyordu; cluster'ın node'ları ise image'ı bir registry'den çekmek zorunda. Yukarıdaki AddContainerRegistry satırları bu yüzden var (API'ler preview, ASPIRECOMPUTE003 uyarısını bilerek kapattık). Lokal bir kind cluster'ı ve yanına bir registry container'ı açıp Parameters__registry-endpoint=localhost:5001 ile tekrar denedik:
(push-catalog) ✓ Successfully pushed catalog to localhost:5001/catalog:aspire-deploy-20261114172920
...
(helm-deploy-k8s) → Deploying k8s to Kubernetes namespace default as Helm release production
Release adı environment'ın adı (production) ve Aspire helm upgrade --install'ı pod'ların hazır olmasını bekleyerek çalıştırıyor. Catalog, Ordering, Inventory, Redis, RabbitMQ ve dashboard pod'ları Running oldu. SQL Server pod'u ise test makinemizdeki kind cluster'ında kararlı çalışmadı ve tekrar tekrar yeniden başladı; Helm'in 5 dakikalık beklemesi context deadline exceeded ile doldu. Kubernetes hedefini bu yüzden uçtan uca doğrulayamadık: chart üretimi, image push ve Helm kurulumu çalıştı, uygulamanın cluster'da sipariş alması denenmedi.
Azure Hedefleri
Azure'a deploy bir Azure aboneliği gerektirdiği için bu bölümü çalıştırmadık, aspire.dev dokümanından aktarıyoruz. Azure Container Apps için Aspire.Hosting.Azure.AppContainers paketi ve builder.AddAzureContainerAppEnvironment("aca-env"). aspire deploy Container Apps environment'ını, bir Azure Container Registry'yi ve image çekmek için bir managed identity'yi oluşturuyor, image'ları build edip registry'ye gönderiyor. Burada WithExternalHttpEndpoints'in karşılığı açık: dışarıya açık bir HTTP endpoint public ingress, diğerleri environment içinde kalan internal ingress oluyor. Ölçek ayarı gibi özelleştirmeler PublishAsAzureContainerApp(...) ile yapılıyor. App Service ve AKS (Azure Kubernetes Service; Azure'un yönetilen Kubernetes'i) için de benzer environment resource'ları var; AKS, Helm kurulumunu yukarıdaki Kubernetes environment'ına devrediyor. Önemli bir fark: Azure'da aspire destroy bütün resource group'u siliyor, içinde Aspire'ın oluşturmadığı resource'lar olsa bile. Azure'un service'leri ve maliyet modeli Cloud Temelleri serisinin konusu.
CI'da Deploy
CI'da iki kural var: komutlar --non-interactive ile çalışmalı (eksik bir parameter'da soru sormak yerine hata versin) ve parameter'lar Parameters__<ad> environment variable'larıyla, değerleri CI'ın secret store'undan gelmeli. Yer tutuculu artifact üretmek ise hiçbir secret istemiyor; bunu her push'ta yapan bir job:
name: Publish Docker Compose artifacts
on:
push:
branches: [main]
jobs:
publish:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- uses: actions/setup-dotnet@v6
with:
global-json-file: global.json
- name: Install the Aspire CLI
run: dotnet tool install --global Aspire.Cli --version 13.6.5
# Yalnız yer tutucular: publish için secret gerekmiyor
- name: Publish
run: aspire publish --apphost src/Bookstore.AppHost -o aspire-output --non-interactive
- uses: actions/upload-artifact@v7
with:
name: compose
path: aspire-output
include-hidden-files: true
Workflow'u actionlint ile doğruladık, GitHub'da çalıştırmadık. include-hidden-files satırı .env dosyası için: upload-artifact nokta ile başlayan dosyaları varsayılan olarak atlıyor. Gerçek bir deploy job'u aynı adımlara aspire deploy --non-interactive'ı ve parameter'ları secret'lardan ekler; Azure hedefinde kimlik doğrulama OIDC (OpenID Connect; burada CI'ın kısa ömürlü token ile Azure'a giriş yapması) ile yapılır, ayrıntısı GitHub Actions serisinde.
Üretimde Dikkat
Aspire'ın ürettiği artifact'leri okumadan production'a çıkma. Bu yazıda her sorunu publish çıktısında gördük: açılmayan port, production'a giden test verisi, lokal bir klasöre bağlı bind mount, Job yerine Deployment. aspire publish'i CI'da çalıştırıp çıktıyı pull request'te gözden geçirmek, deploy'dan önce bunları yakalamanın kolay yolu.
Production veritabanını ve kimlik sağlayıcısını genelde Aspire'ın model ettiği container'lar olarak değil, yönetilen servisler olarak istersin. Keycloak için yaptığımız IsRunMode ayrımı bunun örneği; aynı şeyi SQL Server için connection string parameter'ı ile yapabilirsin.
Aspire deploy'u ne zaman kullanmamalı? Ekibin zaten Helm chart'ları, Kustomize overlay'leri ya da GitOps (Argo CD gibi) ile işleyen bir hattı varsa, Aspire'ın chart'ını doğrudan uygulamak yerine publish çıktısını bir başlangıç noktası olarak al ve kendi hattına taşı. Kubernetes serisinin Kustomize yazısı bu tarafı anlatıyor. Aspire deploy'un en çok işe yaradığı yer, ayrı bir platform ekibi olmayan küçük ekipler ve Compose ya da Container Apps gibi basit hedefler.
Kubernetes integration'ı ve registry API'leri preview. Sürüm yükseltmelerinde üretilen chart'ın farkını (helm template çıktısını) karşılaştır.
Sık Düşülen Tuzaklar
WithExternalHttpEndpoints'i publish'te kaybolan bir resource'a koymak. Trafiği karşılayan resource'u işaretle; Kubernetes'te ayrıca Ingress ya da Gateway ayarı gerekir.
Lokal ayarları IsRunMode ile ayırmamak. Seed verisi, realm import, Mailpit gibi araçlar publish çıktısına da giriyor.
Deploy'da user secrets'ın okunacağını sanmak. Deploy Production'da çalışıyor; parameter'ları environment variable ya da interaktif prompt ile ver.
Service discovery adresini production Authority'si olarak kullanmak. JWT handler'ı https istiyor ve hata, anonim istekler dahil her isteği düşürüyor.
Deploy çıktısını repoya eklemek. .env.Production parolaları açık tutuyor.
Küçük Bir Deneme
★☆☆ Gateway'den WithExternalHttpEndpoints()'i kaldırıp tekrar publish et ve Compose dosyasındaki ports ile expose satırlarını karşılaştır.
★★☆ aspire do build ile yalnız image'ları üret ve docker images çıktısına bak. Angular build'i gateway image'ının boyutuna ne kadar ekliyor?
★★★ Kubernetes hedefinde migration worker'ı bir Job'a çevir: chart'ı publish et, migrations/deployment.yaml'ı Job olarak yeniden yaz ve Catalog'a migration'ı bekleyen bir init container ekle. Bu değişikliği Aspire'ın bir sonraki publish'inde nasıl koruyacağını düşün.
Aspire'ın yerelde çalıştırmadan deploy'a kadar uzanan yolunu tamamladık. Serinin son yazısında geriye bakıyoruz: .NET Aspire 8'den 13.6'ya hangi API'lerin değiştiğini, workload'dan Aspire CLI'a ve manifest'ten aspire do'ya geçişi ve eski bir projeyi aspire update ile taşımayı gerçek bir taşıma üzerinde göreceğiz.