diff --git a/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx b/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx index 183e8dabc..854913c50 100644 --- a/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx +++ b/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx @@ -5,6 +5,7 @@ description: Learn how session, persistent, resource-scoped, and parent-process import { Tabs, TabItem } from '@astrojs/starlight/components'; import { Image } from 'astro:assets'; +import LearnMore from '@components/LearnMore.astro'; import persistentContainer from '@assets/whats-new/aspire-9/persistent-container.png'; import persistentContainerDocker from '@assets/whats-new/aspire-9/persistent-container-docker-desktop.png'; @@ -40,7 +41,11 @@ Persistent resources are automatically recreated when the AppHost detects meanin Persistent containers use proxied endpoints by default, just like session containers. The proxy runs only while the AppHost is running, so the proxy address isn't reachable after the AppHost stops. Persistent executables and projects default to proxyless endpoints so their direct addresses stay stable and reachable even after the AppHost stops. -You can still configure endpoint proxy behavior explicitly on any persistent resource. Set `isProxied: false` on an individual endpoint, or call `WithEndpointProxySupport(false)` to make every endpoint on a resource proxyless. Persistent executable endpoints must have a concrete `port` or `targetPort`; automatically persisted random executable ports aren't supported. For proxyless container endpoints with only a `targetPort`, Aspire immediately uses the target port as the allocated host port. +You can still configure endpoint proxy behavior explicitly on any persistent resource. Set `isProxied: false` on an individual endpoint, or call `WithEndpointProxySupport(false)` to make every endpoint on a resource proxyless. When a proxyless endpoint doesn't specify a public `port`, Aspire allocates one before the resource is created. For persistent resources, Aspire stores the allocated port in user secrets when user secrets are available and reuses it on later AppHost runs. + + + For more information, see [Allocate ports for dynamic proxyless endpoints](/fundamentals/networking-overview/#allocate-ports-for-dynamic-proxyless-endpoints). + :::danger[Persistent container ≠ persistent data] Persistent container lifetime doesn't guarantee data durability. For details, see [Container lifetime vs. data durability](#container-lifetime-vs-data-durability). @@ -157,7 +162,7 @@ await builder.build().run(); -Configure a concrete `port` or `targetPort` for persistent executable endpoints; automatically persisted random executable ports aren't supported. +The preceding example configures a concrete `port` so the endpoint address is explicit. If you omit the public `port`, Aspire allocates one before the executable starts and reuses it from user secrets on later AppHost runs when user secrets are available. ## Configure a persistent project diff --git a/src/frontend/src/content/docs/fundamentals/networking-overview.mdx b/src/frontend/src/content/docs/fundamentals/networking-overview.mdx index 5e7d3e6e3..148806a87 100644 --- a/src/frontend/src/content/docs/fundamentals/networking-overview.mdx +++ b/src/frontend/src/content/docs/fundamentals/networking-overview.mdx @@ -55,7 +55,7 @@ The inner loop is the process of developing and testing your app locally before - **Endpoints/Endpoint configurations**: Endpoints are the connections between your app and the services it depends on, such as databases, message queues, or APIs. Endpoints provide information such as the service name, host port, scheme, and environment variable. Aspire can create endpoints automatically from resource configuration, and you can also add them explicitly by calling `WithEndpoint`. - **Proxies**: Aspire automatically launches a proxy for each proxied service binding you add to your app, and assigns a port for the proxy to listen on. The proxy then forwards the requests to the port that your app listens on, which might be different from the proxy port. This way, you can avoid port conflicts and access your app and services using consistent and predictable URLs. -- **Proxyless endpoints**: Endpoints with `IsProxied` set to `false` connect directly to the resource instead of routing through an Aspire-managed proxy. Starting in Aspire 13.4, endpoints on persistent executables and projects are proxyless by default. Persistent containers use proxied endpoints by default, the same as session containers. Proxyless endpoints must specify a target port so Aspire knows which port the resource listens on. +- **Proxyless endpoints**: Endpoints with `IsProxied` set to `false` connect directly to the resource instead of routing through an Aspire-managed proxy. Starting in Aspire 13.4, endpoints on persistent executables and projects are proxyless by default. Persistent containers use proxied endpoints by default, the same as session containers. - **Container networks**: Aspire creates and manages dedicated networks for container resources so containers can discover and communicate with each other during local development. ## How endpoints work @@ -232,9 +232,7 @@ Most endpoints are proxied by default. A proxyless endpoint skips the Aspire-man Starting in Aspire 13.4, endpoints on persistent executables and projects are proxyless by default. This lets the persistent resource keep using its direct endpoint across AppHost runs instead of depending on a new proxy instance each time. This also ensures that the ports your service is reachable on stay stable whether the AppHost is running or not. Persistent containers use proxied endpoints by default, the same as session containers, so integrations that depend on endpoint allocation before startup continue to work. -For proxyless container endpoints, Aspire resolves the host port immediately when only a `targetPort` is specified — there is no deferred allocation. For example, `.WithEndpoint(targetPort: 6379, isProxied: false)` allocates host port `6379` right away. - -Because there's no proxy to listen on one port and forward to another, every proxyless endpoint must include a target port. The target port tells Aspire which port the resource listens on. You can also set `port` when you need a specific host port, but `targetPort` is still required for proxyless endpoints. +The following example configures a proxyless HTTP endpoint for an nginx container: @@ -262,6 +260,50 @@ await web.withHttpEndpoint({ targetPort: 80, isProxied: false }); +### Allocate ports for dynamic proxyless endpoints + +Proxyless endpoints can omit the public host `port`. Starting in Aspire 13.5, Aspire allocates a public host port for each dynamic proxyless endpoint before workload resources are created. The allocated port is then available to configuration that resolves endpoint properties, such as `GetEndpoint(...).Property(EndpointProperty.Port)`. + +If you set `port`, Aspire uses that value as the public host port. If you omit `port`, Aspire allocates one from the proxyless endpoint port range. The default range is `10000-32767`, and you can override it with the `ASPIRE_PROXYLESS_ENDPOINT_PORT_RANGE` environment variable in `start-end` format. Container endpoints still require `targetPort` so Aspire knows which port inside the container receives traffic. + +This lets you write patterns like the following, where a container exposes its allocated public port in an environment variable: + + + + +```csharp title="AppHost.cs" +var database = builder.AddContainer("database", "image") + .WithEndpoint(name: "tcp", targetPort: 5432, isProxied: false); + +database.WithEnvironment("PUBLIC_PORT", database.GetEndpoint("tcp").Property(EndpointProperty.Port)); +``` + + + + +```typescript title="apphost.mts" twoslash +import { EndpointProperty, createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const database = await builder.addContainer('database', { + image: 'image', + tag: 'latest', +}); +await database.withEndpoint({ name: 'tcp', targetPort: 5432, isProxied: false }); + +await database.withEnvironment('PUBLIC_PORT', database.getEndpoint('tcp').property(EndpointProperty.Port)); +``` + + + + +In this example, `PUBLIC_PORT` is set to the endpoint's allocated public host port before the container is created. Because the endpoint is proxyless and doesn't specify `port`, Aspire allocates a port from the proxyless endpoint port range. + +:::note +For persistent resources, Aspire stores allocated proxyless endpoint ports in user secrets when user secrets are available and reuses them on later AppHost runs. If Aspire can't persist the allocated port, configure a fixed public `port` or use a proxied endpoint to avoid recreating the persistent resource on each run. +::: + ## Omit the host port For proxied endpoints, when you omit the host port, Aspire generates a random port for both host and service port. This is useful when you want to avoid port conflicts and don't care about the host or service port. Consider the following code: diff --git a/src/frontend/src/content/docs/whats-new/aspire-13-5.mdx b/src/frontend/src/content/docs/whats-new/aspire-13-5.mdx index 080769dc7..36efbeca9 100644 --- a/src/frontend/src/content/docs/whats-new/aspire-13-5.mdx +++ b/src/frontend/src/content/docs/whats-new/aspire-13-5.mdx @@ -45,6 +45,7 @@ This release introduces: - **VS Code extension renamed to "Aspire"** and rebranded for clarity on the Marketplace. - **AppHost discovery efficiency** in VS Code respects exclusion settings and debounces file changes to reduce background scanning. - **Foundry Local integration CLI update** now uses the foundry CLI for lifecycle management, requiring foundry 1.1.0+. +- **Proxyless endpoint port allocation** assigns dynamic public host ports before resources are created, so endpoint property references resolve consistently. - …and much more. ## 🆙 Upgrade to Aspire 13.5 @@ -200,6 +201,8 @@ The following breaking changes are included in Aspire 13.5: 4. **GitHub Models integration deprecated**: The GitHub Models service is no longer available to new customers, so the `Aspire.Hosting.GitHub.Models` integration is sunset as of Aspire 13.5. All public APIs are marked `[Obsolete]`, and the package no longer appears in `aspire add` output. One final obsolete release will ship on NuGet, and the package will be removed entirely in a future version. Migrate to the [Azure AI Foundry integration](/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-get-started/) instead. See [microsoft/aspire#18402](https://github.com/microsoft/aspire/issues/18402) for details. +5. **Proxyless endpoint port allocation timing changed**: Proxyless endpoints without an explicit public `port` now receive one during service preparation, before workload resources are created. Executable proxyless endpoints that previously failed without a public port, and container proxyless endpoints that expected the public port to be assigned later during container startup, should expect Aspire to assign the port earlier. The default allocation range is `10000-32767` and can be overridden with `ASPIRE_PROXYLESS_ENDPOINT_PORT_RANGE=start-end`. Persistent resources reuse allocated ports from user secrets when available. +