Summary
aspire run --isolated does not randomize the AppHost-process ports (dashboard frontend, OTLP endpoint, resource service). Running two AppHosts in parallel — even with --isolated — fails because both bind the same fixed ports from the launch profile.
Repro
-
Have an AppHost with a launch profile that pins the standard dashboard URLs. This is the default for both C# AppHosts (Properties/launchSettings.json) and TypeScript/polyglot AppHosts (aspire.config.json profiles.https.environmentVariables):
-
From two different worktrees of the same repo, run aspire run --isolated (or aspire start --isolated) concurrently.
-
Second instance crashes:
System.IO.IOException: Failed to bind to address https://127.0.0.1:22064: address already in use.
Capability Aspire.Hosting/run failed with InvalidOperationException
Observed on Aspire CLI 13.4.0, Windows. Reproduces with both TS and C# AppHosts.
Expected
--isolated advertises "randomized ports and isolated user secrets, allowing multiple instances to run simultaneously." That should include the AppHost-process ports, not only DCP-allocated resource endpoints. Two --isolated runs of the same AppHost should coexist on the same machine without manual port juggling.
Why this happens
ConfigureIsolatedModeAsync in src/Aspire.Cli/Projects/DotNetAppHostProject.cs only does two things:
env["DcpPublisher__RandomizePorts"] = "true"; // randomizes DCP child-resource ports
env["DOTNET_USER_SECRETS_ID"] = isolatedUserSecretsId;
It never overrides the launch-profile env vars (ASPNETCORE_URLS, ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL, ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL), which are subsequently applied verbatim from the profile by the same class.
Inside the AppHost process, DashboardServiceHost.ConfigureKestrel already supports random ports — it binds port: 0 when no URL is configured — but the env var from the launch profile short-circuits that path:
var uri = configuration.GetUri(KnownConfigNames.ResourceServiceEndpointUrl) ?? ...;
if (uri is null) { kestrelOptions.Listen(IPAddress.Loopback, port: 0, ConfigureListen); }
else { kestrelOptions.Listen(ip, uri.Port, ConfigureListen); } // ← collides
Same logic governs the dashboard frontend (ASPNETCORE_URLS) and OTLP endpoint.
This is language-agnostic — the TS polyglot path and the C# path both end up in the same DashboardServiceHost running inside the AppHost process, both bound by the launch-profile URLs.
Suggested fix
In ConfigureIsolatedModeAsync (and the equivalent polyglot/single-file path), when isolated mode is on, either:
- unset
ASPNETCORE_URLS, ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL, ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL after the launch profile is applied, so the Kestrel port: 0 fallback kicks in, OR
- replace them with
:0 URIs that still drive the same code path but yield ephemeral ports.
The dashboard front-end URL would then need to be surfaced back to the CLI (it already is, via the backchannel for the dashboard login URL print-out) so the user still gets a clickable link.
Workaround today
Strip the four pinned URLs out of the launch profile (or maintain a per-worktree profile), so isolated mode actually has free ports to grab.
Summary
aspire run --isolateddoes not randomize the AppHost-process ports (dashboard frontend, OTLP endpoint, resource service). Running two AppHosts in parallel — even with--isolated— fails because both bind the same fixed ports from the launch profile.Repro
Have an AppHost with a launch profile that pins the standard dashboard URLs. This is the default for both C# AppHosts (
Properties/launchSettings.json) and TypeScript/polyglot AppHosts (aspire.config.jsonprofiles.https.environmentVariables):From two different worktrees of the same repo, run
aspire run --isolated(oraspire start --isolated) concurrently.Second instance crashes:
Observed on Aspire CLI 13.4.0, Windows. Reproduces with both TS and C# AppHosts.
Expected
--isolatedadvertises "randomized ports and isolated user secrets, allowing multiple instances to run simultaneously." That should include the AppHost-process ports, not only DCP-allocated resource endpoints. Two--isolatedruns of the same AppHost should coexist on the same machine without manual port juggling.Why this happens
ConfigureIsolatedModeAsyncinsrc/Aspire.Cli/Projects/DotNetAppHostProject.csonly does two things:It never overrides the launch-profile env vars (
ASPNETCORE_URLS,ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL,ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL), which are subsequently applied verbatim from the profile by the same class.Inside the AppHost process,
DashboardServiceHost.ConfigureKestrelalready supports random ports — it bindsport: 0when no URL is configured — but the env var from the launch profile short-circuits that path:Same logic governs the dashboard frontend (
ASPNETCORE_URLS) and OTLP endpoint.This is language-agnostic — the TS polyglot path and the C# path both end up in the same
DashboardServiceHostrunning inside the AppHost process, both bound by the launch-profile URLs.Suggested fix
In
ConfigureIsolatedModeAsync(and the equivalent polyglot/single-file path), when isolated mode is on, either:ASPNETCORE_URLS,ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL,ASPIRE_RESOURCE_SERVICE_ENDPOINT_URLafter the launch profile is applied, so the Kestrelport: 0fallback kicks in, OR:0URIs that still drive the same code path but yield ephemeral ports.The dashboard front-end URL would then need to be surfaced back to the CLI (it already is, via the backchannel for the dashboard login URL print-out) so the user still gets a clickable link.
Workaround today
Strip the four pinned URLs out of the launch profile (or maintain a per-worktree profile), so isolated mode actually has free ports to grab.