.NET10の公開に合わせて、Aspire Ver 13がリリースされました。1
それに伴い AIが適当に作った感が満載の ドキュメントサイトも公開されています。
適当に眺めていたら地味に欲しかったdocker-compose生成機能が追加されていたので、試してみました。
前段
今回は以下のようなアーキテクチャを想定しています。
特に言う事の無いWeb3層構成ですが、ここに2つのWorkerServiceがくっついています。それぞれMigration処理と適当にデータを追記処理するBotとして動作します。
ベースとなるAspireのAppHost.csは以下のような感じです。
(各プロジェクトの詳細は割愛。別のことをやろうとしてたついでに試したので、雑です)
// postgresql
var postgres = builder
.AddPostgres("postgres")
.WithImage("postgres:16-alpine")
.WithPgWeb()
.WithDataVolume();
var database = postgres.AddDatabase("appdb");
// migration worker
var dbMigration = builder
.AddProject<Projects.DbMigration>("dbmigration")
.WithReference(database)
.WaitFor(database);
// 以後、マイグレーションが完了次第起動する
// bot worker
builder
.AddProject<Projects.BotWorker>("botworker")
.WithReference(database)
.WaitForCompletion(dbMigration);
// API
var apiService = builder
.AddProject<Projects.ApiService>("apiservice")
.WithHttpHealthCheck("/health")
.WithReference(database)
.WaitForCompletion(dbMigration);
// Web Frontend
builder
.AddProject<Projects.Web>("webfrontend")
.WithExternalHttpEndpoints()
.WithHttpHealthCheck("/health")
.WithReference(apiService)
.WithReference(database)
.WaitFor(apiService);
builder.Build().Run();
下準備
Aspire CLIのセットアップ
.NET10でAspireを使うのは地味に始めてだったので、変わってるポイントから抑えていきます。
まず、専用のCLIが提供されるようになりました。
というわけで早速これをインストールします。導入には.NET10 SDKが必要です。
今回はPowerShellを使ってインストールしました。
irm https://aspire.dev/install.ps1 | iex
インストールが完了したらPowershellを再起動して、初期設定を行います。
aspire init
実行すると .aspire/settings.json が生成されます。
{
"appHostPath": "../AppHost.csproj"
}
.NET9以前とはcsprojの記載が結構変わっています。詳細は公式ドキュメントを参照。
Docker Integrationのインストール
これを導入して検証をしてみます。コードはここ。
まずはインストールです。めちゃくちゃ簡単になりました。
aspire add docker
これだけでcsprojが更新されます。中身を見てみると、26/01/21時点でまだプレビュー版のようですね。2
<PackageReference Include="Aspire.Hosting.Docker" Version="13.1.0-preview.1.25616.3" />
aspire publish
docker-compose.ymlの生成
ドキュメントが微妙に嘘で悲しいのですが、有効化するだけなら以下の行を追加するだけでOKです。
var builder = DistributedApplication.CreateBuilder(args);
// これだけ
var compose = builder.AddDockerComposeEnvironment("compose");
var cache = builder.AddRedis("cache")
// これは要らない(というか引数が必要で動かせない)
// .PublishAsDockerComposeService();
そして以下のコマンドを実行します。
aspire publish -o docker
-oはdocker composeファイルをどこに出力するか指定しています。
指定しないとAppHostフォルダ配下に出力されますが、起動スクリプトは専用のフォルダにあったほうが便利ですよね?
ビルド結果の確認
ビルドが成功すると以下のようなファイルが生成されます。いつものファイル達ですね。
docker/
├── docker-compose.yml
└── .env
まずはdocker-compose.ymlを見てみましょう。長いので一部省略しています。
services:
# aspireのダッシュボードが標準で付属
compose-dashboard:
image: "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest"
ports:
- "18888"
expose:
- "18889"
- "18890"
networks:
- "aspire"
restart: "always"
# PostgreSQLコンテナ
postgres:
image: "docker.io/postgres:16-alpine"
environment:
POSTGRES_HOST_AUTH_METHOD: "scram-sha-256"
POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256 --auth-local=scram-sha-256"
POSTGRES_USER: "postgres"
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
expose:
- "5432"
volumes:
- type: "volume"
target: "/var/lib/postgresql/data"
source: "aspiresimpledbapp.apphost-785014c213-postgres-data"
read_only: false
networks:
- "aspire"
dbmigration:
image: "${DBMIGRATION_IMAGE}"
environment:
# 一部略
ConnectionStrings__appdb: "Host=postgres;Port=5432;Username=postgres;Password=${POSTGRES_PASSWORD};Database=appdb"
APPDB_HOST: "postgres"
APPDB_PORT: "5432"
APPDB_USERNAME: "postgres"
APPDB_PASSWORD: "${POSTGRES_PASSWORD}"
APPDB_URI: "postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/appdb"
APPDB_JDBCCONNECTIONSTRING: "jdbc:postgresql://postgres:5432/appdb"
APPDB_DATABASENAME: "appdb"
depends_on:
postgres:
condition: "service_started"
networks:
- "aspire"
apiservice:
image: "${APISERVICE_IMAGE}"
expose:
- "${APISERVICE_PORT}"
# 略
webfrontend:
image: "${WEBFRONTEND_IMAGE}"
ports:
- "${WEBFRONTEND_PORT}"
# 略
botworker:
image: "${BOTWORKER_IMAGE}"
# 略
networks:
aspire:
driver: "bridge"
volumes:
aspiresimpledbapp.apphost-785014c213-postgres-data:
driver: "local"
要点を抑えてみましょう。
まず、ApiServerやDbMigrationには、コンテナを対象としたConnectionStringが正しく設定されています。
唯一パスワードだけは環境変数を参照する形になっています(正しいですね!)。
ConnectionStrings__appdb: "Host=postgres;Port=5432;Username=postgres;Password=${POSTGRES_PASSWORD};Database=appdb"
また、ポート番号も環境変数参照になっています。
ports:
- "${APISERVICE_PORT}"
その他、イメージ名は環境変数参照になっています。
image: "${APISERVICE_IMAGE}"
また、aspireのいつものダッシュボードもついてますね。これはOffにもできます。3
compose-dashboard:
image: "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest"
aspire deploy
さて、この状態だとdocker-compose.ymlは出来上がりましたが、環境変数が設定されていないため、そのままでは動きません。
そこでaspire deployコマンドを使ってみます。
ちなみに、aspire publishとaspire deployは以下のような違いがあります(名前もうちょっとどうにかしてほしいが)

aspire deploy -o docker
このコマンドを実行すると、コンテナビルドが開始され、.env.Productionファイルが生成されます。
docker/
├── docker-compose.yml
└── .env.Production
中身を見ると以下のようになっています。
# Container image name for apiservice
APISERVICE_IMAGE=apiservice:aspire-deploy-20260122151704
# Default container port for apiservice
APISERVICE_PORT=8080
# Container image name for botworker
BOTWORKER_IMAGE=botworker:aspire-deploy-20260122151704
# Container image name for dbmigration
DBMIGRATION_IMAGE=dbmigration:aspire-deploy-20260122151704
# Parameter postgres-password
POSTGRES_PASSWORD=jAptzTb7y_praPH++Rmpv{
# Container image name for webfrontend
WEBFRONTEND_IMAGE=webfrontend:aspire-deploy-20260122151704
# Default container port for webfrontend
WEBFRONTEND_PORT=8080
見ての通り、必要な変数がすべて設定されています。
また、必要なイメージもビルドされていることが確認できます。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
botworker aspire-deploy-20260122153900 ae3c9011297b 13 minutes ago 240MB
webfrontend aspire-deploy-20260122153900 60dfd6b90819 13 minutes ago 245MB
dbmigration aspire-deploy-20260122153900 b8b96a6c3fea 13 minutes ago 241MB
apiservice aspire-deploy-20260122153900 5e312cdb8aa7 14 minutes ago 241MB
最後にコンテナが起動します。
$ docker ps
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
25966369847b webfrontend:aspire-deploy-20260122153900 "dotnet /app/AspireS…" 7 minutes ago Up 7 minutes 0.0.0.0:50213->8080/tcp, [::]:50213->8080/tcp aspire-compose-a27a017f-webfrontend-1
63bcf5c13c95 apiservice:aspire-deploy-20260122153900 "dotnet /app/AspireS…" 7 minutes ago Up 7 minutes 8080/tcp aspire-compose-a27a017f-apiservice-1
4d086229d29d mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest "dotnet /app/Aspire.…" 7 minutes ago Up 7 minutes 0.0.0.0:51094->18888/tcp, [::]:51094->18888/tcp aspire-compose-a27a017f-compose-dashboard-1
8bd797e3b9ff postgres:16-alpine "docker-entrypoint.s…" 7 minutes ago Up 7 minutes 5432/tcp aspire-compose-a27a017f-postgres-1
…のですが、素のままだとdb_migrationの起動条件がdb: service_startedになっているため、うまく動作しません!
リモートにデプロイしたい
今のところAzureしか対応してません。

オンプレ環境でも何かしらで動かせると嬉しいですが、さすがにやってくれないですかね。。
docker composeファイルのカスタマイズ
dockerなのでローカルで起動する(deploy)ことはあんまりしないですよね。
なので実際にはdocker-compose.ymlをカスタマイズしてから運用することになると思います。
私はCoolifyを使っているので、素のcomposeからいくつか変更したいポイントがあります。
これがAspire上でどこまでできるのかを見てみます。
ソースコードを見てみる
内部的にはAspire.Hosting.DockerのDockerComposeServiceResourceクラスがdocker-compose.ymlを生成しています。
見てみるとカスタマイズの幅があまり無く、ちょっと残念な感じです。
imageの代わりにbuildを使う
イメージを生成→それを参照するのではなく、直接ビルドする形にすることでStaging評価を楽にする目的。
.PublishAsDockerFileでいけるのかなと思いきや、compose側に反映されず。
起動条件をservice_healthyにする
DBの起動を待ってからMigrationを開始したい場合などは、service_healthyにしたい。
残念ながら26/01/24時点では利用できないようです。
https://github.com/dotnet/aspire/blob/67de0c860b0b515c19f83387f31cf1b1c5f5ba35/src/Aspire.Hosting.Docker/DockerComposeServiceResource.cs#L195-L202
PORTSの開放をやめる/ラベルを付与する/外部NWに所属させる
これらも簡易的に対応することはできず。。
ではどうするのか
PublishAsDockerComposeServiceで素朴に調整します。
// 全体に対する設定の場合
var compose = builder.AddDockerComposeEnvironment("compose")
.ConfigureComposeFile(conf =>
{
// 全サービスのport開放をやめる
foreach(var service in conf.Services.Values)
{
service.Ports = [];
}
});
// 例えばDbMigrationの場合
var dbMigration = builder
.AddProject<Projects.AspireSimpleDbApp_DbMigration>("dbmigration")
.WithReference(database)
.WaitFor(database)
// ここを追加
.PublishAsDockerComposeService(
(conf, service) =>
{
// build指定に変更
service.Image = null;
service.Build = new()
{
Context = "./",
Dockerfile = "./DbMigration/Dockerfile",
};
// ラベルを追加
service.Labels = new()
{
["sampleLabel"] = "Sample",
};
// 既存のネットワークを使用
service.Networks = ["external-network"];
// 完了したら再起動しない
service.Restart = "no";
// 起動条件をservice_healthyに変更
service.DependsOn = new()
{
// 起動対象リソース名はpostgresのNameプロパティを参照できる
[postgres.Resource.Name] = new() {
Condition = "service_healthy"
},
};
}
);
うーん……
これなら最初から素のdocker-composeを書いたほうが良い気がします。
まとめ
個人的にはまだまだ発展途上感が否めない雰囲気があります。もうちょっと様子見です。