diff --git a/.github/agents/community-toolkit-integration-doc-writer.agent.md b/.github/agents/community-toolkit-integration-doc-writer.agent.md
index 44ba27988..ab1a52b26 100644
--- a/.github/agents/community-toolkit-integration-doc-writer.agent.md
+++ b/.github/agents/community-toolkit-integration-doc-writer.agent.md
@@ -1,6 +1,19 @@
---
-description: 'Turns a README from a Community Toolkit integration into a docs page in the repo.'
-tools: ['read/terminalSelection', 'read/terminalLastCommand', 'read/getNotebookSummary', 'read/problems', 'read/readFile', 'edit/createDirectory', 'edit/createFile', 'edit/editFiles', 'search', 'web', 'todo']
+description: "Turns a README from a Community Toolkit integration into a docs page in the repo."
+tools:
+ [
+ "read/terminalSelection",
+ "read/terminalLastCommand",
+ "read/getNotebookSummary",
+ "read/problems",
+ "read/readFile",
+ "edit/createDirectory",
+ "edit/createFile",
+ "edit/editFiles",
+ "search",
+ "web",
+ "todo",
+ ]
name: Community Toolkit Integration Doc Writer
---
@@ -10,41 +23,41 @@ Documentation should match the style and format of existing documentation in the
## Repo structure
-* Documentation will be created in the `src/frontend/src/content/docs/integrations` folder.
-* Each integration is placed in the relevant subfolder based on its category:
- * `ai` - AI and machine learning integrations
- * `caching` - Caching solutions (Redis, Garnet, Valkey, etc.)
- * `cloud` - Cloud-specific integrations (Azure, AWS, etc.)
- * `compute` - Compute platforms (Docker, Kubernetes, etc.)
- * `databases` - Database systems
- * `frameworks` - Language and framework integrations (Python, Rust, .NET MAUI, Orleans, etc.)
- * `messaging` - Message brokers and queues
- * `observability` - Logging, monitoring, and diagnostics tools
- * `reverse-proxies` - Reverse proxy solutions
- * `security` - Security and identity management
-* Documentation is written using the Astro framework and uses `.mdx` files.
+- Documentation will be created in the `src/frontend/src/content/docs/integrations` folder.
+- Each integration is placed in the relevant subfolder based on its category:
+ - `ai` - AI and machine learning integrations
+ - `caching` - Caching solutions (Redis, Garnet, Valkey, etc.)
+ - `cloud` - Cloud-specific integrations (Azure, AWS, etc.)
+ - `compute` - Compute platforms (Docker, Kubernetes, etc.)
+ - `databases` - Database systems
+ - `frameworks` - Language and framework integrations (Python, Rust, .NET MAUI, Orleans, etc.)
+ - `messaging` - Message brokers and queues
+ - `observability` - Logging, monitoring, and diagnostics tools
+ - `reverse-proxies` - Reverse proxy solutions
+ - `security` - Security and identity management
+- Documentation is written using the Astro framework and uses `.mdx` files.
## Requirements
-* A Community Toolkit integration is to be provided to generate the documentation for.
-* Should no integration be provided, do not continue, instead request the user to provide one.
-* The Community Toolkit is found at https://github.com/CommunityToolkit/Aspire
-* README files for integrations are in the `src` folder of the Community Toolkit repo, under the relevant integration subfolder (using the name of the integration, such as `CommunityToolkit.Aspire.Hosting.Ollama`, `CommunityToolkit.Aspire.Hosting.Rust`, `CommunityToolkit.Aspire.SurrealDB`, etc.).
-* The README file of the integration should be used as the primary source of information for generating the documentation.
+- A Community Toolkit integration is to be provided to generate the documentation for.
+- Should no integration be provided, do not continue, instead request the user to provide one.
+- The Community Toolkit is found at https://github.com/CommunityToolkit/Aspire
+- README files for integrations are in the `src` folder of the Community Toolkit repo, under the relevant integration subfolder (using the name of the integration, such as `CommunityToolkit.Aspire.Hosting.Ollama`, `CommunityToolkit.Aspire.Hosting.Rust`, `CommunityToolkit.Aspire.SurrealDB`, etc.).
+- The README file of the integration should be used as the primary source of information for generating the documentation.
## Creating a plan
1. Review the README file of the provided Community Toolkit integration.
2. Identify the correct subfolder in the `src/frontend/src/content/docs/integrations` folder based on the integration's category.
3. Review existing integration documentation from this repo such as:
- * `src/frontend/src/content/docs/integrations/ai/ollama.mdx`
- * `src/frontend/src/content/docs/integrations/databases/kurrentdb.mdx`
- * `src/frontend/src/content/docs/integrations/frameworks/python.mdx`
- * `src/frontend/src/content/docs/integrations/frameworks/rust.mdx`
+ - `src/frontend/src/content/docs/integrations/ai/ollama.mdx`
+ - `src/frontend/src/content/docs/integrations/databases/kurrentdb.mdx`
+ - `src/frontend/src/content/docs/integrations/frameworks/python.mdx`
+ - `src/frontend/src/content/docs/integrations/frameworks/rust.mdx`
4. Create a plan for generating the documentation that includes:
- * The target subfolder for the documentation.
- * The filename for the documentation file (should be the name of the integration in lowercase, with spaces replaced by hyphens, and a `.mdx` extension).
- * A breakdown of sections to include in the documentation, based on the README content and existing documentation style.
+ - The target subfolder for the documentation.
+ - The filename for the documentation file (should be the name of the integration in lowercase, with spaces replaced by hyphens, and a `.mdx` extension).
+ - A breakdown of sections to include in the documentation, based on the README content and existing documentation style.
5. Implement the plan by creating the necessary directories and files, and writing the documentation content in Astro format.
## Update the sidebar navigation
@@ -53,12 +66,13 @@ After creating the documentation file, update `src/frontend/config/sidebar/sideb
1. Locate the relevant integration category section in the sidebar (e.g., "Frameworks & runtimes", "Data & databases", "Caching & state", etc.)
2. Add a new entry to the `items` array for that category:
- * For simple entries: `{ label: "Integration Name", slug: "integrations/category/integration-name" }`
- * For entries with subsections: Use a `collapsed` structure if the integration has multiple related docs
+ - For simple entries: `{ label: "Integration Name", slug: "integrations/category/integration-name" }`
+ - For entries with subsections: Use a `collapsed` structure if the integration has multiple related docs
3. Place the entry in alphabetical order within the category
4. Ensure the `slug` matches the documentation file path
Example entry:
+
```typescript
{ label: "My Integration", slug: "integrations/databases/my-integration" }
```
@@ -69,95 +83,104 @@ After completing all documentation writing and sidebar updates:
1. Execute the "Update Integration Documentation Links" prompt located at `.github/prompts/update-integrations.prompt.md`
2. This prompt will:
- * Synchronize package names from the NuGet catalog with their corresponding documentation URLs
- * Update `src/frontend/src/data/integration-docs.json` with mappings for the newly created documentation
- * Ensure the new integration is discoverable through the integration gallery and search
+ - Synchronize package names from the NuGet catalog with their corresponding documentation URLs
+ - Update `src/frontend/src/data/integration-docs.json` with mappings for the newly created documentation
+ - Ensure the new integration is discoverable through the integration gallery and search
This step ensures that the documentation you've created is properly indexed and linked in the Aspire documentation site.
## Documentation style and requirements
### Frontmatter
-* **Title**: Should be concise and include "integration" (e.g., "Python integration", "Ollama integration")
-* **Description**: A brief summary of what the integration does and its purpose. Required for all docs except framework integrations.
- * For hosting integrations: Describe what the integration orchestrates/configures
- * For client integrations: Describe what the client connects to and its purpose
- * For combined integrations: Describe both aspects
-* **Next**: Set to `false` for framework integrations that don't have natural next steps
+
+- **Title**: Should be concise and include "integration" (e.g., "Python integration", "Ollama integration")
+- **Description**: A brief summary of what the integration does and its purpose. Required for all docs except framework integrations.
+ - For hosting integrations: Describe what the integration orchestrates/configures
+ - For client integrations: Describe what the client connects to and its purpose
+ - For combined integrations: Describe both aspects
+- **Next**: Set to `false` for framework integrations that don't have natural next steps
### Required components and imports
-* Import `Badge` from '@astrojs/starlight/components' and add `` at the top
-* Import and use the `Aside` component for notes, tips, cautions, and warnings
-* Import and use `AppHostLangPivot` for AppHost examples when both C# and TypeScript AppHost APIs are available
-* Import and use `InstallPackage` for hosting packages
-* Import and use `InstallDotNetPackage` for client packages
-* Import `Image` from 'astro:assets' for icons
+
+- Import `Badge` from '@astrojs/starlight/components' and add `` at the top
+- Import and use the `Aside` component for notes, tips, cautions, and warnings
+- Import and use synced `Tabs` and `TabItem` from `@astrojs/starlight/components` for AppHost examples when both C# and TypeScript AppHost APIs are available. Use `syncKey='aspire-lang'` and tab IDs `csharp` and `typescript`.
+- Import and use `InstallPackage` for hosting packages
+- Import and use `InstallDotNetPackage` for client packages
+- Import `Image` from 'astro:assets' for icons
### Icons and images
-* Include an icon/logo at the top using the `Image` component (or `ThemeImage` if light/dark variants exist)
-* Icon should be 100x100 pixels, float left, and have `data-zoom-off` attribute
-* Image should include alt text describing the logo
-* Icon files should be placed in `src/frontend/src/assets/icons/`
-* If no official icon is available, the integration may proceed without one (do not use placeholder images)
+
+- Include an icon/logo at the top using the `Image` component (or `ThemeImage` if light/dark variants exist)
+- Icon should be 100x100 pixels, float left, and have `data-zoom-off` attribute
+- Image should include alt text describing the logo
+- Icon files should be placed in `src/frontend/src/assets/icons/`
+- If no official icon is available, the integration may proceed without one (do not use placeholder images)
### Structure and sections
#### For hosting-only integrations (e.g., Rust, Python):
+
1. **Introduction paragraph**: Brief description of what the integration is and what it enables
2. **Prerequisites** (if any): Use an `Aside` with type="note" for requirements like installed tools
3. **Hosting integration section**:
- * Package installation with `InstallPackage`
- * "Add [Technology] resource" subsection with basic example
- * Configuration subsections (endpoints, arguments, volumes, bind mounts, etc.)
- * Working directory explanation if relevant
+ - Package installation with `InstallPackage`
+ - "Add [Technology] resource" subsection with basic example
+ - Configuration subsections (endpoints, arguments, volumes, bind mounts, etc.)
+ - Working directory explanation if relevant
4. **See also section**: Links to official docs, Community Toolkit repo, and related Aspire resources
#### For hosting + client integrations (e.g., Ollama, KurrentDB):
+
1. **Introduction paragraph**: Brief description with link to the technology's website
2. **Hosting integration section**:
- * Package installation with `InstallPackage`
- * "Add [Technology] resource" subsection
- * Configuration subsections (volumes, bind mounts, parameters, etc.)
- * "Hosting integration health checks" subsection (if applicable)
+ - Package installation with `InstallPackage`
+ - "Add [Technology] resource" subsection
+ - Configuration subsections (volumes, bind mounts, parameters, etc.)
+ - "Hosting integration health checks" subsection (if applicable)
3. **Client integration section**:
- * Package installation with `InstallDotNetPackage`
- * "Add [Technology] client" subsection
- * "Add keyed [Technology] client" subsection (if supported)
- * Include example of keyed services with reference to Microsoft docs
- * **Configuration subsection** covering:
- * Connection strings
- * Configuration providers with `appsettings.json` example
- * Inline delegates
- * "Client integration health checks" subsection (if applicable)
- * "Observability and telemetry" subsection (if applicable) with logging and tracing details
+ - Package installation with `InstallDotNetPackage`
+ - "Add [Technology] client" subsection
+ - "Add keyed [Technology] client" subsection (if supported)
+ - Include example of keyed services with reference to Microsoft docs
+ - **Configuration subsection** covering:
+ - Connection strings
+ - Configuration providers with `appsettings.json` example
+ - Inline delegates
+ - "Client integration health checks" subsection (if applicable)
+ - "Observability and telemetry" subsection (if applicable) with logging and tracing details
4. **See also section**
### Code blocks
-* Use proper syntax highlighting: `csharp`, `json`, `sql`, etc.
-* Include descriptive titles: `title="C# — AppHost.cs"`, `title="JSON — appsettings.json"`
-* For C# code in AppHost, always end with `// After adding all resources, run the app...` comment
-* Show complete, runnable examples
-* Use proper formatting and indentation
+
+- Use proper syntax highlighting: `csharp`, `json`, `sql`, etc.
+- Include descriptive titles: `title="C# — AppHost.cs"`, `title="JSON — appsettings.json"`
+- For C# code in AppHost, always end with `// After adding all resources, run the app...` comment
+- Show complete, runnable examples
+- Use proper formatting and indentation
### AppHost language parity
-* Follow the `doc-writer` skill's AppHost language parity guidance for all AppHost and hosting-integration examples.
-* Always show both C# AppHost (`AppHost.cs`) and TypeScript AppHost (`apphost.ts`) variants inside `AppHostLangPivot` unless the feature is genuinely language-specific or TypeScript AppHost support does not exist yet.
-* Before writing a TypeScript AppHost example, verify the API exists in the TypeScript AppHost SDK. Do not invent TypeScript samples.
-* If TypeScript AppHost support is not available, show only the C# example without `AppHostLangPivot` and add a note that TypeScript AppHost support for the integration is not yet available.
-* Use language-neutral prose around AppHost examples, such as "Add a resource to your AppHost" instead of C#-specific method instructions.
+
+- Follow the `doc-writer` skill's AppHost language parity guidance for all AppHost and hosting-integration examples.
+- Always show both C# AppHost (`AppHost.cs`) and TypeScript AppHost (`apphost.ts`) variants inside synced `Tabs` (with `syncKey='aspire-lang'`) unless the feature is genuinely language-specific or TypeScript AppHost support does not exist yet.
+- Before writing a TypeScript AppHost example, verify the API exists in the TypeScript AppHost SDK. Do not invent TypeScript samples.
+- If TypeScript AppHost support is not available, show only the C# example without language tabs and add a note that TypeScript AppHost support for the integration is not yet available.
+- Use language-neutral prose around AppHost examples, such as "Add a resource to your AppHost" instead of C#-specific method instructions.
### Writing style
-* Use imperative mood for instructions ("call the method", not "you call the method")
-* Be concise but complete
-* Use `Aside` components for important notes, tips, cautions, and warnings
-* Include explanations of what code does, especially for non-obvious patterns
-* For required parameters, list them with bullet points and descriptions
-* Reference NuGet packages with the 📦 emoji and link format: `[📦 PackageName](https://nuget.org/packages/PackageName)`
+
+- Use imperative mood for instructions ("call the method", not "you call the method")
+- Be concise but complete
+- Use `Aside` components for important notes, tips, cautions, and warnings
+- Include explanations of what code does, especially for non-obvious patterns
+- For required parameters, list them with bullet points and descriptions
+- Reference NuGet packages with the 📦 emoji and link format: `[📦 PackageName](https://nuget.org/packages/PackageName)`
### Common patterns
-* **Connection names**: Explain that the connection name in client must match the resource name in AppHost
-* **Keyed services**: When showing keyed services, include link to Microsoft docs: `[.NET dependency injection: Keyed services](https://learn.microsoft.com/dotnet/core/extensions/dependency-injection#keyed-services)`
-* **Data volumes vs bind mounts**: Explain when data volumes are used to persist data outside container lifecycle
-* **Health checks**: Mention what the health check verifies
-* **Environment variables**: Explain common patterns for port configuration
-* **WithReference**: Show how resources are referenced in other projects
+
+- **Connection names**: Explain that the connection name in client must match the resource name in AppHost
+- **Keyed services**: When showing keyed services, include link to Microsoft docs: `[.NET dependency injection: Keyed services](https://learn.microsoft.com/dotnet/core/extensions/dependency-injection#keyed-services)`
+- **Data volumes vs bind mounts**: Explain when data volumes are used to persist data outside container lifecycle
+- **Health checks**: Mention what the health check verifies
+- **Environment variables**: Explain common patterns for port configuration
+- **WithReference**: Show how resources are referenced in other projects
diff --git a/.github/skills/doc-writer/SKILL.md b/.github/skills/doc-writer/SKILL.md
index 17c876973..53b49a8b0 100644
--- a/.github/skills/doc-writer/SKILL.md
+++ b/.github/skills/doc-writer/SKILL.md
@@ -71,6 +71,7 @@ description: A brief summary of the page content (required for SEO)
```
Optional frontmatter fields:
+
- `next: false` - Disable "Next page" link for terminal pages
- Custom metadata as needed by Starlight theme
@@ -79,23 +80,30 @@ Optional frontmatter fields:
Import Starlight components at the top of your MDX file:
```tsx
-import { CardGrid, LinkCard, Steps, Tabs, TabItem, Icon, FileTree } from '@astrojs/starlight/components';
+import {
+ CardGrid,
+ LinkCard,
+ Steps,
+ Tabs,
+ TabItem,
+ Icon,
+ FileTree,
+} from "@astrojs/starlight/components";
```
Additional commonly used imports:
```tsx
-import { Kbd } from 'starlight-kbd/components';
-import LearnMore from '@components/LearnMore.astro';
-import AppHostLangPivot from '@components/AppHostLangPivot.astro';
-import PivotSelector from '@components/PivotSelector.astro';
-import Pivot from '@components/Pivot.astro';
-import ThemeImage from '@components/ThemeImage.astro';
-import InstallPackage from '@components/InstallPackage.astro';
-import InstallDotNetPackage from '@components/InstallDotNetPackage.astro';
-import AsciinemaPlayer from '@components/AsciinemaPlayer.astro';
-import Badge from '@astrojs/starlight/components/Badge.astro';
-import Image from 'astro:assets';
+import { Kbd } from "starlight-kbd/components";
+import LearnMore from "@components/LearnMore.astro";
+import PivotSelector from "@components/PivotSelector.astro";
+import Pivot from "@components/Pivot.astro";
+import ThemeImage from "@components/ThemeImage.astro";
+import InstallPackage from "@components/InstallPackage.astro";
+import InstallDotNetPackage from "@components/InstallDotNetPackage.astro";
+import AsciinemaPlayer from "@components/AsciinemaPlayer.astro";
+import Badge from "@astrojs/starlight/components/Badge.astro";
+import Image from "astro:assets";
```
### Component Usage
@@ -135,33 +143,34 @@ Critical warning - this could cause data loss or security issues.
Use for sequential instructions:
-```mdx
+````mdx
1. First step with explanation
- ```bash title="Run this command"
- aspire new aspire-starter
- ```
+ ```bash title="Run this command"
+ aspire new aspire-starter
+ ```
2. Second step
3. Third step
-```
+````
#### Tabs/TabItem
Use for language or platform-specific content:
-```mdx
+````mdx
```bash
aspire run
```
+````
@@ -176,25 +185,23 @@ If a heading should appear in the **On this page** table of contents, keep that
#### Pivot/PivotSelector
-Use for programming language selection that persists across page navigations (for example, site-wide via query string and local storage). For Aspire AppHost C# and TypeScript content, use `AppHostLangPivot` instead; see [AppHost Language Parity (C# and TypeScript)](#apphost-language-parity-c-and-typescript).
+Use `Pivot` and `PivotSelector` sparingly, only for **key landing-page-style articles** where the choice should persist across page navigations and where sharing the page through a URL should land the reader on a specific variant. Pivots support query string values to set the selected option (for example, `?aspire-lang=typescript`). Examples in use today include the [Build your first Aspire app](/get-started/first-app/) and [Deploy your first Aspire app](/get-started/deploy-first-app/) tutorials.
+
+For most pages — including AppHost C# and TypeScript code samples within a guide — prefer synced `Tabs` / `TabItem` blocks at the snippet level instead. See [AppHost Language Parity (C# and TypeScript)](#apphost-language-parity-c-and-typescript).
```mdx
-
-C# specific content here.
-
+C# specific content here.
-
-Python specific content here.
-
+Python specific content here.
```
If a heading needs to appear in the **On this page** table of contents, keep the heading outside the `Pivot` content and put only the variant-specific body content inside each `Pivot`.
@@ -205,22 +212,36 @@ When a page shows the **On this page** table of contents (the default behavior u
If your opening section is truly introductory, keep it as body copy without an `Overview` heading. If that section has a more specific purpose, use a descriptive heading such as `Key concepts`, `Prerequisites`, or another topic-specific label.
+For Aspire AppHost code examples, use synced `Tabs` / `TabItem` blocks with `syncKey='aspire-lang'` at each code snippet. Do **not** add a page-level `PivotSelector` just to switch AppHost code samples between C# and TypeScript. Readers should be able to switch the language at the specific snippet they are reading.
+
+```mdx
+
+
+C# example content here.
+
+
+
+TypeScript example content here.
+
+
+```
+
#### CardGrid and LinkCard
Use for navigation and feature highlights:
```mdx
-
-
+
+ />
```
@@ -229,12 +250,13 @@ Use for navigation and feature highlights:
Use the `Kbd` component from `starlight-kbd` to display keyboard shortcuts with OS-specific variants. This renders styled `` elements and automatically shows the correct shortcut for the reader's operating system.
```mdx
-import { Kbd } from 'starlight-kbd/components';
+import { Kbd } from "starlight-kbd/components";
Open the Command Palette ()
```
**Props**:
+
- `windows` — The shortcut for Windows (also used as the default/Linux fallback)
- `mac` — The shortcut for macOS
- `linux` — (optional) The shortcut for Linux, if different from Windows
@@ -256,10 +278,10 @@ Press to start debugging.
Use the `LearnMore` component to add a styled "learn more" link with an open-book icon. It provides a consistent visual pattern for directing readers to related documentation.
```mdx
-import LearnMore from '@components/LearnMore.astro';
+import LearnMore from "@components/LearnMore.astro";
-For more information, see [Service Defaults](/fundamentals/service-defaults/).
+ For more information, see [Service Defaults](/fundamentals/service-defaults/).
```
@@ -274,7 +296,8 @@ aspire config set features:polyglotSupportEnabled true --global
```
-For more information, see [aspire config command reference](/reference/cli/commands/aspire-config-set/)
+ For more information, see [aspire config command
+ reference](/reference/cli/commands/aspire-config-set/)
:::
````
@@ -308,7 +331,7 @@ builder.Build().Run();
````mdx
```typescript title="apphost.ts"
-import { createBuilder } from './.modules/aspire.js';
+import { createBuilder } from "./.modules/aspire.js";
const builder = await createBuilder();
@@ -355,15 +378,15 @@ Aspire supports both **C# AppHosts** (`AppHost.cs`) and **TypeScript AppHosts**
3. **Neither language is the default**: Don't present C# first as the "real" example and TypeScript as an afterthought. Both tabs are equal peers.
4. **Verify TypeScript APIs exist**: Before writing a TypeScript example, confirm the API exists in the TypeScript AppHost SDK. Do not invent TypeScript samples — if you are unsure whether an API is available, flag it for review.
-### AppHostLangPivot Pattern for AppHost Content
+### AppHost tabs pattern for AppHost content
-Use `AppHostLangPivot` for AppHost-specific content that changes between C# and TypeScript. The component is controlled by the site-wide AppHost selector in the sidebar, so it should be the default choice for AppHost walkthroughs, code samples, and prose that should switch together.
+Use synced `Tabs` for AppHost-specific content that changes between C# and TypeScript. Each AppHost code snippet should provide its own language tabs and use `syncKey='aspire-lang'` so the user's language choice stays synchronized across snippets on the page.
````mdx
-import AppHostLangPivot from '@components/AppHostLangPivot.astro';
+import { Tabs, TabItem } from "@astrojs/starlight/components";
-
-
-
+
+
````
-Use `AppHostLangPivot` for more than code blocks when needed. Entire paragraphs, lists, asides, or multi-step sections can live inside the `csharp` and `typescript` slots when the workflows differ. The pivot itself is the shared wrapper; the slot names are the language discriminator.
+Use the same synced tabs pattern for more than code blocks when needed. Entire paragraphs, lists, asides, or multi-step sections can live inside the `csharp` and `typescript` tab items when the workflows differ.
-Use `Tabs` for other concerns such as CLI vs IDE, deployment targets, or platform choices. Do not use `Tabs syncKey="apphost-lang"` for new AppHost content.
+Use different `syncKey` values for other concerns such as CLI vs IDE, deployment targets, platform choices, or package managers. For AppHost language tabs, use exactly `syncKey='aspire-lang'`.
-If a section heading should appear in the **On this page** table of contents, keep that heading outside `AppHostLangPivot`. Headings inside the `csharp` and `typescript` slots can be missed by the TOC generator, so the recommended pattern is a shared heading followed by an `AppHostLangPivot` containing only the language-specific body content.
+If a section heading should appear in the **On this page** table of contents, keep that heading outside `Tabs`. Headings inside `TabItem` content may be skipped by the TOC generator, so the recommended pattern is a shared heading followed by tabs containing only the language-specific body content.
### Conventions
-| Aspect | C# | TypeScript |
-|---|---|---|
-| File title | `title="AppHost.cs"` | `title="apphost.ts"` |
-| Pivot wrapper | Shared `AppHostLangPivot` container | Shared `AppHostLangPivot` container |
-| Language slot | `slot="csharp"` | `slot="typescript"` |
-| Builder creation | `DistributedApplication.CreateBuilder(args)` | `import { createBuilder } from './.modules/aspire.js';` then newline for space followed by `await createBuilder();` |
-| Method casing | PascalCase (`AddRedis`) | camelCase (`addRedis`) |
-| Async pattern | Synchronous fluent calls | `await` each builder call |
-| Build & run | `builder.Build().Run()` | `await builder.build().run()` |
+| Aspect | C# | TypeScript |
+| ---------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
+| File title | `title="AppHost.cs"` | `title="apphost.ts"` |
+| Tab wrapper | Shared `` container | Shared `` container |
+| Tab item | `` | `` |
+| Builder creation | `DistributedApplication.CreateBuilder(args)` | `import { createBuilder } from './.modules/aspire.js';` then newline for space followed by `await createBuilder();` |
+| Method casing | PascalCase (`AddRedis`) | camelCase (`addRedis`) |
+| Async pattern | Synchronous fluent calls | `await` each builder call |
+| Build & run | `builder.Build().Run()` | `await builder.build().run()` |
### Prose Guidelines
@@ -423,19 +446,19 @@ When writing narrative text around AppHost examples:
- ❌ "Call `builder.AddRedis()` in your _Program.cs_" (C#-specific)
- ❌ "Add the following C# code to your AppHost" (when both languages should be shown)
-When a concept differs between languages (e.g., configuration files, async patterns), explain both within the AppHostLangPivot slots or in language-neutral prose above the pivot.
+When a concept differs between languages (e.g., configuration files, async patterns), explain both within the AppHost language tabs or in language-neutral prose above the tabs.
### When TypeScript Is Not Yet Supported
-If a hosting integration does not yet have TypeScript AppHost support, show only the C# example without `AppHostLangPivot` and add a note:
+If a hosting integration does not yet have TypeScript AppHost support, show only the C# example without language tabs and add a note:
```mdx
```
-Do **not** wrap a single language in `AppHostLangPivot` or a single-language `` component — that creates a misleading UI suggesting another option exists.
+Do **not** wrap a single language in a single-language `` component — that creates a misleading UI suggesting another option exists.
## Integration Documentation
@@ -443,18 +466,18 @@ Do **not** wrap a single language in `AppHostLangPivot` or a single-language `
+
Brief description of the technology and what the integration enables.
@@ -483,8 +512,8 @@ Brief description of the technology and what the integration enables.
### Add [Technology] resource
-
-
+
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -495,11 +524,11 @@ var tech = builder.AddTechnology("tech");
builder.Build().Run();
```
-
-
+
+
### Hosting integration health checks
@@ -625,7 +654,7 @@ The connection name must match the resource name defined in the AppHost.
For integrations from the [Aspire Community Toolkit](https://github.com/CommunityToolkit/Aspire), add the badge at the top:
```mdx
-import Badge from '@astrojs/starlight/components/Badge.astro';
+import Badge from "@astrojs/starlight/components/Badge.astro";
```
@@ -681,12 +710,12 @@ After adding integration documentation, run the update-integrations prompt to en
Use consistent terminology throughout:
-| Preferred | Avoid |
-|-----------|-------|
-| Aspire | .NET Aspire (except in formal/legal contexts) |
-| AppHost | App Host, app host |
-| resource | component (for AppHost resources) |
-| integration | connector, plugin |
+| Preferred | Avoid |
+| ----------- | --------------------------------------------- |
+| Aspire | .NET Aspire (except in formal/legal contexts) |
+| AppHost | App Host, app host |
+| resource | component (for AppHost resources) |
+| integration | connector, plugin |
### Inclusive Language
@@ -715,30 +744,30 @@ Place icons in `src/frontend/src/assets/icons/`
### Icon Usage
```mdx
-import Image from 'astro:assets';
+import Image from "astro:assets";
import techIcon from "@assets/icons/technology.svg";
```
For light/dark theme variants:
```mdx
-import ThemeImage from '@components/ThemeImage.astro';
+import ThemeImage from "@components/ThemeImage.astro";
```
@@ -891,6 +920,7 @@ Install the [📦 Aspire.Hosting.Redis](https://nuget.org/packages/Aspire.Hostin
### See Also Sections
End pages with a "See also" section linking to:
+
- Official technology documentation
- Related Aspire documentation
- NuGet package pages
@@ -910,25 +940,23 @@ The aspire.dev site supports multiple languages. When creating new content:
```mdx
```
### Version-Specific Information
```mdx
-
+
```
### Feature Flags or Experimental Features
```mdx
```
@@ -965,15 +993,17 @@ The following rules are derived from common feedback patterns in documentation P
### Code Example Rules
1. **Use standard indentation**: For fluent APIs on newlines, use standard 4-space indentation, NOT alignment with the method call above
-
+
✅ Correct:
+
```csharp
builder.AddProject("api")
.WithReference(redis)
.WithExternalHttpEndpoints();
```
-
+
❌ Incorrect (aligned indentation):
+
```csharp
builder.AddProject("api")
.WithReference(redis)
diff --git a/.github/skills/update-integrations/SKILL.md b/.github/skills/update-integrations/SKILL.md
index 443b05df4..b02d9f8ae 100644
--- a/.github/skills/update-integrations/SKILL.md
+++ b/.github/skills/update-integrations/SKILL.md
@@ -33,7 +33,7 @@ This skill keeps the documentation mappings in sync with the package catalog and
- On `release/*` branches, official packages (`Aspire.*`) must resolve from the branch-specific public Azure Artifacts feed instead of nuget.org
- Community Toolkit packages (`CommunityToolkit.Aspire.*`) always resolve from nuget.org
- The branch name in this repo is expected to match the corresponding branch in `microsoft/aspire` (for example `release/13.2`)
-- The release feed name follows `darc-pub-dotnet-aspire-{shortSha}` where `{shortSha}` is the first 8 characters of the head commit SHA for the matching Aspire release branch
+- The release feed name follows `darc-pub-microsoft-aspire-{shortSha}` where `{shortSha}` is the first 8 characters of the head commit SHA for the matching Aspire release branch
- The automation derives the NuGet service index from that feed name as `https://pkgs.dev.azure.com/dnceng/public/_packaging/{feed-name}/nuget/v3/index.json`
#### Release feed resolution
@@ -41,7 +41,7 @@ This skill keeps the documentation mappings in sync with the package catalog and
The scripts use this precedence on `release/*` branches:
1. `ASPIRE_RELEASE_FEED_URL` — accepts the full NuGet service index URL, the Azure DevOps feed page URL, or the raw feed name
-2. `ASPIRE_RELEASE_FEED_NAME` — explicit feed name such as `darc-pub-dotnet-aspire-aad16017`
+2. `ASPIRE_RELEASE_FEED_NAME` — explicit feed name such as `darc-pub-microsoft-aspire-aad16017`
3. `ASPIRE_RELEASE_COMMIT` / `ASPIRE_RELEASE_COMMIT_SHA` — derives the feed name from the commit SHA prefix
4. Automatic lookup of the matching branch head via `git ls-remote` against `https://github.com/microsoft/aspire`
diff --git a/src/apphost/Aspire.Dev.AppHost/Aspire.Dev.AppHost.csproj b/src/apphost/Aspire.Dev.AppHost/Aspire.Dev.AppHost.csproj
index 40b6e17c6..590ee861d 100644
--- a/src/apphost/Aspire.Dev.AppHost/Aspire.Dev.AppHost.csproj
+++ b/src/apphost/Aspire.Dev.AppHost/Aspire.Dev.AppHost.csproj
@@ -1,4 +1,4 @@
-
+Exe
@@ -9,8 +9,8 @@
-
-
+
+
diff --git a/src/frontend/config/redirects.mjs b/src/frontend/config/redirects.mjs
index ee61ec5bb..2a5a42ddb 100644
--- a/src/frontend/config/redirects.mjs
+++ b/src/frontend/config/redirects.mjs
@@ -101,4 +101,6 @@ export const redirects = {
'/deployment/azure/aca-deployment-aspire-cli/': '/deployment/azure/container-apps/',
'/deployment/azure/customize-container-apps/': '/deployment/azure/container-apps/',
'/fundamentals/app-lifecycle/': '/deployment/app-lifecycle/',
+ '/dashboard/copilot/': '/dashboard/ai-coding-agents/',
+ '/dashboard/mcp-server/': '/get-started/aspire-mcp-server/',
};
diff --git a/src/frontend/config/sidebar/dashboard.topics.ts b/src/frontend/config/sidebar/dashboard.topics.ts
index 97870ca1f..a108599c3 100644
--- a/src/frontend/config/sidebar/dashboard.topics.ts
+++ b/src/frontend/config/sidebar/dashboard.topics.ts
@@ -107,49 +107,66 @@ export const dashboardTopics: StarlightSidebarTopicsUserConfig = {
],
},
{
- label: 'AI features',
+ label: 'Data features',
translations: {
- da: 'AI-funktioner',
- de: 'KI-Funktionen',
- en: 'AI features',
- es: 'Funciones de IA',
- fr: "Fonctionnalités d'IA",
- hi: 'एआई सुविधाएँ',
- id: 'Fitur AI',
- it: "Funzionalità dell'IA",
- ja: 'AI機能',
- ko: 'AI 기능',
- 'pt-BR': 'Recursos de IA',
- ru: 'Функции ИИ',
- tr: 'Yapay zeka özellikleri',
- uk: 'Функції ШІ',
- 'zh-CN': '人工智能功能',
+ da: 'Datafunktioner',
+ de: 'Datenfunktionen',
+ en: 'Data features',
+ es: 'Funciones de datos',
+ fr: 'Fonctionnalités de données',
+ hi: 'डेटा सुविधाएँ',
+ id: 'Fitur data',
+ it: 'Funzionalità dati',
+ ja: 'データ機能',
+ ko: '데이터 기능',
+ 'pt-BR': 'Recursos de dados',
+ ru: 'Функции данных',
+ tr: 'Veri özellikleri',
+ uk: 'Функції даних',
+ 'zh-CN': '数据功能',
},
items: [
{
- label: 'GitHub Copilot',
- slug: 'dashboard/copilot',
+ label: 'AI coding agents',
+ translations: {
+ da: 'AI-kodningsagenter',
+ de: 'KI-Coding-Agenten',
+ en: 'AI coding agents',
+ es: 'Agentes de codificación de IA',
+ fr: "Agents de codage IA",
+ hi: 'एआई कोडिंग एजेंट',
+ id: 'Agen pengkodean AI',
+ it: 'Agenti di codifica AI',
+ ja: 'AIコーディングエージェント',
+ ko: 'AI 코딩 에이전트',
+ 'pt-BR': 'Agentes de codificação de IA',
+ ru: 'Агенты кодирования ИИ',
+ tr: 'Yapay zeka kodlama aracıları',
+ uk: 'Агенти кодування ШІ',
+ 'zh-CN': 'AI 编码代理',
+ },
+ slug: 'dashboard/ai-coding-agents',
},
{
- label: 'Aspire MCP server',
+ label: 'APIs and data access',
translations: {
- da: 'Aspire MCP-server',
- de: 'Aspire MCP-Server',
- en: 'Aspire MCP server',
- es: 'Servidor Aspire MCP',
- fr: 'Serveur Aspire MCP',
- hi: 'Aspire MCP सर्वर',
- id: 'Server Aspire MCP',
- it: 'Server Aspire MCP',
- ja: 'Aspire MCPサーバー',
- ko: 'Aspire MCP 서버',
- 'pt-BR': 'Servidor Aspire MCP',
- ru: 'Сервер Aspire MCP',
- tr: 'Aspire MCP sunucusu',
- uk: 'Сервер Aspire MCP',
- 'zh-CN': 'Aspire MCP 服务器',
+ da: 'API\'er og dataadgang',
+ de: 'APIs und Datenzugriff',
+ en: 'APIs and data access',
+ es: 'APIs y acceso a datos',
+ fr: 'APIs et accès aux données',
+ hi: 'APIs और डेटा एक्सेस',
+ id: 'API dan akses data',
+ it: 'API e accesso ai dati',
+ ja: 'APIとデータアクセス',
+ ko: 'API 및 데이터 액세스',
+ 'pt-BR': 'APIs e acesso a dados',
+ ru: 'API и доступ к данным',
+ tr: 'API\'ler ve veri erişimi',
+ uk: 'API та доступ до даних',
+ 'zh-CN': 'API 和数据访问',
},
- slug: 'dashboard/mcp-server',
+ slug: 'dashboard/apis',
},
],
},
diff --git a/src/frontend/config/sidebar/deployment.topics.ts b/src/frontend/config/sidebar/deployment.topics.ts
index 46f7c54ce..c2c5a9c0b 100644
--- a/src/frontend/config/sidebar/deployment.topics.ts
+++ b/src/frontend/config/sidebar/deployment.topics.ts
@@ -110,10 +110,57 @@ export const deploymentTopics: StarlightSidebarTopicsUserConfig = {
},
{
label: 'Kubernetes',
- slug: 'deployment/kubernetes',
+ collapsed: false,
+ items: [
+ {
+ label: 'Overview',
+ slug: 'deployment/kubernetes',
+ },
+ {
+ label: 'Kubernetes clusters',
+ slug: 'deployment/kubernetes/kubernetes',
+ },
+ {
+ label: 'Azure Kubernetes Service (AKS)',
+ slug: 'deployment/kubernetes/aks',
+ },
+ {
+ label: 'Ingress & Gateway API',
+ slug: 'deployment/kubernetes-ingress',
+ },
+ {
+ label: 'Gateway API on AKS',
+ slug: 'deployment/kubernetes-gateway-aks',
+ },
+ {
+ label: 'Ingress on AKS',
+ slug: 'deployment/kubernetes-ingress-aks',
+ },
+ ],
+ },
+ {
+ label: 'Custom deployment pipelines',
+ translations: {
+ da: 'Brugerdefinerede implementeringspipelines',
+ de: 'Benutzerdefinierte Bereitstellungspipelines',
+ en: 'Custom deployment pipelines',
+ es: 'Canalizaciones de despliegue personalizadas',
+ fr: 'Pipelines de déploiement personnalisés',
+ hi: 'कस्टम तैनाती पाइपलाइन',
+ id: 'Pipeline penyebaran kustom',
+ it: 'Pipeline di distribuzione personalizzate',
+ ja: 'カスタム デプロイ パイプライン',
+ ko: '사용자 지정 배포 파이프라인',
+ 'pt-BR': 'Pipelines de implantação personalizados',
+ ru: 'Пользовательские конвейеры развертывания',
+ tr: 'Özel dağıtım işlem hatları',
+ uk: 'Користувацькі конвеєри розгортання',
+ 'zh-CN': '自定义部署管道',
+ },
+ slug: 'deployment/custom-deployments',
},
{
- label: 'Azure',
+ label: 'Deploy to Azure',
collapsed: false,
items: [
{
diff --git a/src/frontend/config/sidebar/docs.topics.ts b/src/frontend/config/sidebar/docs.topics.ts
index 34778aaaf..53fa0a32c 100644
--- a/src/frontend/config/sidebar/docs.topics.ts
+++ b/src/frontend/config/sidebar/docs.topics.ts
@@ -90,6 +90,10 @@ export const docsTopics: StarlightSidebarTopicsUserConfig = {
label: "What's new",
collapsed: true,
items: [
+ {
+ label: 'Aspire 13.3',
+ slug: 'whats-new/aspire-13-3',
+ },
{
label: 'Aspire 13.2',
slug: 'whats-new/aspire-13-2',
diff --git a/src/frontend/config/sidebar/integrations.topics.ts b/src/frontend/config/sidebar/integrations.topics.ts
index e76c3e651..1fb6789de 100644
--- a/src/frontend/config/sidebar/integrations.topics.ts
+++ b/src/frontend/config/sidebar/integrations.topics.ts
@@ -473,6 +473,10 @@ export const integrationTopics: StarlightSidebarTopicsUserConfig = {
},
],
},
+ {
+ label: 'Azure Kubernetes Service (AKS)',
+ slug: 'integrations/cloud/azure/aks',
+ },
{
label: 'Azure Cosmos DB',
collapsed: true,
@@ -513,6 +517,10 @@ export const integrationTopics: StarlightSidebarTopicsUserConfig = {
},
],
},
+ {
+ label: 'Azure Front Door',
+ slug: 'integrations/cloud/azure/azure-front-door',
+ },
{
label: 'Azure Functions',
collapsed: true,
@@ -1553,6 +1561,27 @@ export const integrationTopics: StarlightSidebarTopicsUserConfig = {
'zh-CN': '开发工具和扩展',
},
items: [
+ {
+ label: 'Browser logs',
+ translations: {
+ da: 'Browserlogfiler',
+ de: 'Browserprotokolle',
+ en: 'Browser logs',
+ es: 'Registros del navegador',
+ fr: 'Journaux du navigateur',
+ hi: 'ब्राउज़र लॉग',
+ id: 'Log browser',
+ it: 'Log del browser',
+ ja: 'ブラウザーログ',
+ ko: '브라우저 로그',
+ 'pt-BR': 'Logs do navegador',
+ ru: 'Журналы браузера',
+ tr: 'Tarayıcı günlükleri',
+ uk: 'Журнали браузера',
+ 'zh-CN': '浏览器日志',
+ },
+ slug: 'integrations/devtools/browser-logs',
+ },
{ label: 'Data API Builder', slug: 'integrations/devtools/dab' },
{ label: 'Dev Tunnels', slug: 'integrations/devtools/dev-tunnels' },
{
diff --git a/src/frontend/config/sidebar/reference.topics.ts b/src/frontend/config/sidebar/reference.topics.ts
index 4a7b4af5b..38c83f06a 100644
--- a/src/frontend/config/sidebar/reference.topics.ts
+++ b/src/frontend/config/sidebar/reference.topics.ts
@@ -332,6 +332,20 @@ export const referenceTopics: StarlightSidebarTopicsUserConfig[number] = {
},
],
},
+ {
+ label: 'aspire dashboard',
+ collapsed: true,
+ items: [
+ {
+ label: 'aspire dashboard',
+ slug: 'reference/cli/commands/aspire-dashboard',
+ },
+ {
+ label: 'aspire dashboard run',
+ slug: 'reference/cli/commands/aspire-dashboard-run',
+ },
+ ],
+ },
{
label: 'aspire deploy',
slug: 'reference/cli/commands/aspire-deploy',
@@ -340,6 +354,10 @@ export const referenceTopics: StarlightSidebarTopicsUserConfig[number] = {
label: 'aspire describe',
slug: 'reference/cli/commands/aspire-describe',
},
+ {
+ label: 'aspire destroy',
+ slug: 'reference/cli/commands/aspire-destroy',
+ },
{ label: 'aspire do', slug: 'reference/cli/commands/aspire-do' },
{
label: 'aspire docs',
@@ -361,6 +379,28 @@ export const referenceTopics: StarlightSidebarTopicsUserConfig[number] = {
label: 'aspire docs search',
slug: 'reference/cli/commands/aspire-docs-search',
},
+ {
+ label: 'aspire docs api',
+ collapsed: true,
+ items: [
+ {
+ label: 'aspire docs api',
+ slug: 'reference/cli/commands/aspire-docs-api',
+ },
+ {
+ label: 'aspire docs api get',
+ slug: 'reference/cli/commands/aspire-docs-api-get',
+ },
+ {
+ label: 'aspire docs api list',
+ slug: 'reference/cli/commands/aspire-docs-api-list',
+ },
+ {
+ label: 'aspire docs api search',
+ slug: 'reference/cli/commands/aspire-docs-api-search',
+ },
+ ],
+ },
],
},
{
@@ -536,6 +576,10 @@ export const referenceTopics: StarlightSidebarTopicsUserConfig[number] = {
label: 'ASPIREEXPORT010',
link: '/diagnostics/aspireexport010',
},
+ {
+ label: 'ASPIREEXPORT013',
+ link: '/diagnostics/aspireexport013',
+ },
{
label: 'ASPIRECERTIFICATES001',
link: '/diagnostics/aspirecertificates001',
@@ -564,6 +608,10 @@ export const referenceTopics: StarlightSidebarTopicsUserConfig[number] = {
label: 'ASPIREDOTNETTOOL',
link: '/diagnostics/aspiredotnettool',
},
+ {
+ label: 'ASPIREDURABLETASK001',
+ link: '/diagnostics/aspiredurabletask001',
+ },
{
label: 'ASPIREEXTENSION001',
link: '/diagnostics/aspireextension001',
@@ -576,6 +624,10 @@ export const referenceTopics: StarlightSidebarTopicsUserConfig[number] = {
label: 'ASPIREINTERACTION001',
link: '/diagnostics/aspireinteraction001',
},
+ {
+ label: 'ASPIREJAVASCRIPT001',
+ link: '/diagnostics/aspirejavascript001',
+ },
{
label: 'ASPIREMCP001',
link: '/diagnostics/aspiremcp001',
@@ -661,6 +713,10 @@ export const referenceTopics: StarlightSidebarTopicsUserConfig[number] = {
{ label: 'ASPIREAZURE001', link: '/diagnostics/aspireazure001' },
{ label: 'ASPIREAZURE002', link: '/diagnostics/aspireazure002' },
{ label: 'ASPIREAZURE003', link: '/diagnostics/aspireazure003' },
+ {
+ label: 'ASPIREBROWSERLOGS001',
+ link: '/diagnostics/aspirebrowserlogs001',
+ },
],
},
],
diff --git a/src/frontend/scripts/aspire-package-source.ts b/src/frontend/scripts/aspire-package-source.ts
index 14d6ee453..68b47505d 100644
--- a/src/frontend/scripts/aspire-package-source.ts
+++ b/src/frontend/scripts/aspire-package-source.ts
@@ -58,7 +58,7 @@ function getReleaseFeedNameFromCommit(commit: string | undefined | null): string
return null;
}
- return `darc-pub-dotnet-aspire-${normalizedCommit.slice(0, 8)}`;
+ return `darc-pub-microsoft-aspire-${normalizedCommit.slice(0, 8)}`;
}
function buildReleaseFeedServiceIndex(feedName: string): string {
diff --git a/src/frontend/scripts/update-schemas.ts b/src/frontend/scripts/update-schemas.ts
index 8ca056799..28f9fdf2b 100644
--- a/src/frontend/scripts/update-schemas.ts
+++ b/src/frontend/scripts/update-schemas.ts
@@ -3,8 +3,12 @@
* versioned copy under src/data/schemas/, then updates the version index.
*
* Usage:
- * pnpm update:schemas # fetch latest non-prerelease
- * pnpm update:schemas -- --version 13.2.3 # pin to a specific version tag
+ * pnpm update:schemas # fetch latest non-prerelease
+ * pnpm update:schemas -- --version 13.2.3 # pin to a specific version tag
+ * pnpm update:schemas -- --version 13.3.0 --ref # label as 13.3.0 but fetch from
+ * # the given commit SHA or branch ref
+ * # (used before the v tag is
+ * # published)
*/
import fs from 'fs';
@@ -73,15 +77,15 @@ async function fetchLatestReleaseVersion(): Promise {
return stable.tag_name.replace(/^v/, '');
}
-/** Fetch the raw schema JSON from microsoft/aspire at the given tag. */
-async function fetchSchemaAtTag(tag: string): Promise> {
+/** Fetch the raw schema JSON from microsoft/aspire at the given git ref (tag, branch, or SHA). */
+async function fetchSchemaAtRef(ref: string): Promise> {
const rawUrl =
- `https://raw.githubusercontent.com/${ASPIRE_REPO}/v${tag}/${SCHEMA_SOURCE_PATH}`;
+ `https://raw.githubusercontent.com/${ASPIRE_REPO}/${ref}/${SCHEMA_SOURCE_PATH}`;
const res = await fetch(rawUrl, {
headers: { 'User-Agent': 'aspire-schema-updater' },
});
if (!res.ok) {
- throw new Error(`Failed to fetch schema at tag v${tag}: ${res.status} ${res.statusText}`);
+ throw new Error(`Failed to fetch schema at ref ${ref}: ${res.status} ${res.statusText}`);
}
return (await res.json()) as Record;
}
@@ -128,13 +132,19 @@ async function main(): Promise {
console.log(`✅ Latest stable release: ${version}`);
}
+ const refArg = process.argv.indexOf('--ref');
+ const ref =
+ refArg >= 0 && process.argv[refArg + 1]
+ ? process.argv[refArg + 1]
+ : `v${version}`;
+
const outFile = schemaFilePath(version);
if (fs.existsSync(outFile)) {
console.log(`ℹ️ Schema for v${version} already exists at ${path.relative(FRONTEND_ROOT, outFile)}`);
} else {
- console.log(`⬇️ Fetching schema from microsoft/aspire @ v${version}…`);
- const schema = await fetchSchemaAtTag(version);
+ console.log(`⬇️ Fetching schema from microsoft/aspire @ ${ref}…`);
+ const schema = await fetchSchemaAtRef(ref);
// Update $id to point to the versioned aspire.dev URL
schema['$id'] = `${SITE_ORIGIN}${SCHEMA_BASE_PATH}/${version}.json`;
diff --git a/src/frontend/src/assets/dashboard/explore/notification-center-bell.png b/src/frontend/src/assets/dashboard/explore/notification-center-bell.png
new file mode 100644
index 000000000..589fb6d19
Binary files /dev/null and b/src/frontend/src/assets/dashboard/explore/notification-center-bell.png differ
diff --git a/src/frontend/src/assets/dashboard/explore/notification-center-open.png b/src/frontend/src/assets/dashboard/explore/notification-center-open.png
new file mode 100644
index 000000000..d1e384e29
Binary files /dev/null and b/src/frontend/src/assets/dashboard/explore/notification-center-open.png differ
diff --git a/src/frontend/src/assets/dashboard/explore/notification-center-response.png b/src/frontend/src/assets/dashboard/explore/notification-center-response.png
new file mode 100644
index 000000000..5c90eea97
Binary files /dev/null and b/src/frontend/src/assets/dashboard/explore/notification-center-response.png differ
diff --git a/src/frontend/src/assets/icons/azure-front-door-icon.png b/src/frontend/src/assets/icons/azure-front-door-icon.png
new file mode 100644
index 000000000..8b0f00c07
Binary files /dev/null and b/src/frontend/src/assets/icons/azure-front-door-icon.png differ
diff --git a/src/frontend/src/assets/integrations/cloud/azure/azure-ai-foundry/agent-responses-result-light.png b/src/frontend/src/assets/integrations/cloud/azure/azure-ai-foundry/agent-responses-result-light.png
new file mode 100644
index 000000000..96b5f548b
Binary files /dev/null and b/src/frontend/src/assets/integrations/cloud/azure/azure-ai-foundry/agent-responses-result-light.png differ
diff --git a/src/frontend/src/assets/integrations/cloud/azure/azure-ai-foundry/agent-responses-result.png b/src/frontend/src/assets/integrations/cloud/azure/azure-ai-foundry/agent-responses-result.png
new file mode 100644
index 000000000..948fee383
Binary files /dev/null and b/src/frontend/src/assets/integrations/cloud/azure/azure-ai-foundry/agent-responses-result.png differ
diff --git a/src/frontend/src/assets/integrations/cloud/azure/azure-ai-foundry/agent-send-message-light.png b/src/frontend/src/assets/integrations/cloud/azure/azure-ai-foundry/agent-send-message-light.png
new file mode 100644
index 000000000..bf432d241
Binary files /dev/null and b/src/frontend/src/assets/integrations/cloud/azure/azure-ai-foundry/agent-send-message-light.png differ
diff --git a/src/frontend/src/assets/integrations/cloud/azure/azure-ai-foundry/agent-send-message.png b/src/frontend/src/assets/integrations/cloud/azure/azure-ai-foundry/agent-send-message.png
new file mode 100644
index 000000000..73c7cd249
Binary files /dev/null and b/src/frontend/src/assets/integrations/cloud/azure/azure-ai-foundry/agent-send-message.png differ
diff --git a/src/frontend/src/assets/whats-new/aspire-13.3/notification-center-bell.png b/src/frontend/src/assets/whats-new/aspire-13.3/notification-center-bell.png
new file mode 100644
index 000000000..589fb6d19
Binary files /dev/null and b/src/frontend/src/assets/whats-new/aspire-13.3/notification-center-bell.png differ
diff --git a/src/frontend/src/assets/whats-new/aspire-13.3/notification-center-open.png b/src/frontend/src/assets/whats-new/aspire-13.3/notification-center-open.png
new file mode 100644
index 000000000..d1e384e29
Binary files /dev/null and b/src/frontend/src/assets/whats-new/aspire-13.3/notification-center-open.png differ
diff --git a/src/frontend/src/components/AppHostLangPivot.astro b/src/frontend/src/components/AppHostLangPivot.astro
deleted file mode 100644
index a6785aea7..000000000
--- a/src/frontend/src/components/AppHostLangPivot.astro
+++ /dev/null
@@ -1,30 +0,0 @@
----
-interface Props {
- csharp?: string;
- typescript?: string;
-}
-
-const { csharp, typescript } = Astro.props as Props;
-const hasCsharpSlot = Astro.slots.has('csharp');
-const hasTypescriptSlot = Astro.slots.has('typescript');
----
-
-
-
- {hasCsharpSlot ? : csharp}
-
-
- {hasTypescriptSlot ? : typescript}
-
-
-
-
\ No newline at end of file
diff --git a/src/frontend/src/content/docs/app-host/certificate-configuration.mdx b/src/frontend/src/content/docs/app-host/certificate-configuration.mdx
index 15d44f8ac..cfd913f95 100644
--- a/src/frontend/src/content/docs/app-host/certificate-configuration.mdx
+++ b/src/frontend/src/content/docs/app-host/certificate-configuration.mdx
@@ -72,6 +72,37 @@ aspire certs trust
You may need to reload your profile or start a new terminal session for the change to take effect.
+### Developer certificate for DCP communication (Windows)
+
+By default, Aspire's internal Developer Control Plane (DCP) server uses an ephemeral localhost certificate it generates itself for TLS. On Windows, you can opt in to using your trusted Aspire developer certificate for DCP communication instead, which avoids trust issues caused by the ephemeral certificate not being in the system trust store.
+
+Set the `ASPIRE_DCP_USE_DEVELOPER_CERTIFICATE` environment variable to `true` in your AppHost's `launchSettings.json` or as a system/user environment variable:
+
+```json title="Properties/launchSettings.json"
+{
+ "profiles": {
+ "https": {
+ "commandName": "Project",
+ "environmentVariables": {
+ "ASPIRE_DCP_USE_DEVELOPER_CERTIFICATE": "true"
+ }
+ }
+ }
+}
+```
+
+When this setting is enabled:
+
+- Aspire checks for a trusted developer certificate.
+- If a trusted certificate is found, it is used to secure the DCP server.
+- If no trusted certificate is found, Aspire falls back to the DCP-generated ephemeral certificate.
+- This setting is only supported on Windows. On other platforms, a warning is logged and DCP falls back to its default ephemeral certificate.
+
+
+
## HTTPS endpoint configuration
HTTPS endpoint configuration determines which certificate a resource presents when serving HTTPS traffic. This is server-side certificate configuration for resources that host HTTPS/TLS endpoints.
diff --git a/src/frontend/src/content/docs/app-host/configuration.mdx b/src/frontend/src/content/docs/app-host/configuration.mdx
index 5b201341e..4d665aef4 100644
--- a/src/frontend/src/content/docs/app-host/configuration.mdx
+++ b/src/frontend/src/content/docs/app-host/configuration.mdx
@@ -77,9 +77,15 @@ In TypeScript AppHosts, profiles live in `aspire.config.json`:
| Option | Default value | Description |
| ---------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `ASPIRE_ALLOW_UNSECURED_TRANSPORT` | `false` | Allows communication with the AppHost without https. `ASPNETCORE_URLS` (dashboard address) and `ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL` (AppHost resource service address) must be secured with HTTPS unless true. |
-| `ASPIRE_CONTAINER_RUNTIME` | `docker` | Allows the user of alternative container runtimes for resources backed by containers. Possible values are `docker` (default) or `podman`. |
-| `ASPIRE_VERSION_CHECK_DISABLED` | `false` | When set to `true`, Aspire doesn't check for newer versions on startup. |
+| `ASPIRE_ALLOW_UNSECURED_TRANSPORT` | `false` | Allows communication with the AppHost without https. `ASPNETCORE_URLS` (dashboard address) and `ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL` (AppHost resource service address) must be secured with HTTPS unless true. |
+| `ASPIRE_CONTAINER_RUNTIME` | `docker` | Allows the user of alternative container runtimes for resources backed by containers. Possible values are `docker` (default) or `podman`. |
+| `ASPIRE_DCP_USE_DEVELOPER_CERTIFICATE` | `false` | When set to `true`, Aspire uses its trusted developer certificate to secure the internal DCP server instead of an ephemeral certificate generated by DCP. This can help avoid certificate trust issues when the dev cert is already trusted. If no trusted developer certificate is found, Aspire falls back to the DCP-generated ephemeral certificate. Only supported on Windows. For more information, see [Certificate configuration](/app-host/certificate-configuration/). |
+| `ASPIRE_ENVIRONMENT` | `null` | Configures the AppHost environment when no higher-priority environment source is set. If no environment is configured, the AppHost uses `Production`. |
+| `ASPIRE_VERSION_CHECK_DISABLED` | `false` | When set to `true`, Aspire doesn't check for newer versions on startup. |
+
+## AppHost environment
+
+Use `ASPIRE_ENVIRONMENT` to set the environment name used by the AppHost while it evaluates the application model. Precedence is `--environment`, `DOTNET_ENVIRONMENT`, `ASPIRE_ENVIRONMENT`, then `Production`. This doesn't configure the dashboard's `ASPNETCORE_ENVIRONMENT` or automatically flow to child resources; set framework-specific variables on resources as needed. For details, see [Aspire environments](/deployment/environments/).
## Version update notifications
@@ -122,7 +128,7 @@ By default, the dashboard is automatically started by the AppHost. The dashboard
| `ASPIRE_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS`| `false` | Configures the dashboard to not use authentication and accept anonymous access. Sets frontend, OTLP, MCP, and API auth modes to `Unsecured`. |
| `ASPIRE_DASHBOARD_FRONTEND_BROWSERTOKEN` | Automatically generated 128-bit entropy token. | Configures the frontend browser token. This is the value that must be entered to access the dashboard when the auth mode is BrowserToken. If no browser token is specified then a new token is generated each time the AppHost is launched. |
| `ASPIRE_DASHBOARD_TELEMETRY_OPTOUT` | `false` | Configures the dashboard to never send [usage telemetry](/dashboard/microsoft-collected-dashboard-telemetry/). |
-| `ASPIRE_DASHBOARD_AI_DISABLED` | `false` | [GitHub Copilot in the dashboard](/dashboard/copilot/) is available when the AppHost is launched by a supported IDE. When set to `true` Copilot is disabled in the dashboard and no Copilot UI is visible. |
+| `ASPIRE_DASHBOARD_AI_DISABLED` | `true` | Reserved for future use. The in-dashboard GitHub Copilot UI was removed in Aspire 13.3. AI coding agents can still access telemetry data via the [Aspire CLI and MCP server](/dashboard/ai-coding-agents/). |
| `ASPIRE_DASHBOARD_API_ENABLED` | `true` | Enables the dashboard [telemetry API](/dashboard/configuration/#api) (`/api/telemetry/*`) endpoints. The AppHost always sets this to `true`. |
| `ASPIRE_DASHBOARD_FORWARDEDHEADERS_ENABLED` | `false` | Enables the Forwarded headers middleware that replaces the scheme and host values on the Request context with the values coming from the `X-Forwarded-Proto` and `X-Forwarded-Host` headers. |
diff --git a/src/frontend/src/content/docs/app-host/docker-compose-to-apphost-reference.mdx b/src/frontend/src/content/docs/app-host/docker-compose-to-apphost-reference.mdx
index b8a2c7818..999e63780 100644
--- a/src/frontend/src/content/docs/app-host/docker-compose-to-apphost-reference.mdx
+++ b/src/frontend/src/content/docs/app-host/docker-compose-to-apphost-reference.mdx
@@ -27,6 +27,7 @@ Learn more about [Docker Compose services](https://docs.docker.com/compose/compo
| `build: ./path` | `builder.AddDockerfile("name", "./path")` | Build from specific path |
| `build.context: ./app` | `builder.AddDockerfile("name", "./app")` | Build context |
| `build.dockerfile: Custom.dockerfile` | `builder.Add*("name").WithDockerfile("Custom.dockerfile")` | Custom Dockerfile name |
+| Generated Dockerfile | `builder.AddDockerfileBuilder("name", "./app", callback, stage: "runtime")` | Generate the Dockerfile from AppHost code |
Learn more about [Docker Compose build reference](https://docs.docker.com/compose/compose-file/build/) and [WithDockerfile](/app-host/withdockerfile/).
@@ -72,7 +73,8 @@ Learn more about [Docker Compose ports](https://docs.docker.com/compose/compose-
|----------------|-------------|
| `environment: KEY=value` | `.WithEnvironment("KEY", "value")` Static environment variable |
| `environment: KEY=${HOST_VAR}` | `.WithEnvironment(context => context.EnvironmentVariables["KEY"] = hostVar)` Environment variable with callback context |
-| `env_file: .env` | `.ConfigureEnvFile(env => { ... })` Environment file generation (available in 13.1+) |
+| `environment: KEY=${PARAM}` | `.AsEnvironmentPlaceholder(resource)` inside `.PublishAsDockerComposeService(...)` Compose environment variable placeholder |
+| `env_file: .env` | `.ConfigureEnvFile(env => { ... })` Environment file customization (available in 13.1+) |
Learn more about [Docker Compose environment](https://docs.docker.com/compose/compose-file/05-services/#environment) and [external parameters](/fundamentals/external-parameters/).
@@ -107,7 +109,8 @@ Learn more about [Docker Compose depends_on](https://docs.docker.com/compose/com
| Docker Compose | Aspire | Notes |
|----------------|-------------|-------|
| `networks: [backend]` | Automatic | Aspire handles networking automatically |
-| Custom networks | Not needed | Service discovery handles inter-service communication |
+| Service host name on a Compose network | `.GetHostAddressExpression(endpoint)` | Produces the generated Docker Compose service host name for an endpoint |
+| Custom networks | `.ConfigureComposeFile(file => { ... })` | Customize the generated Compose file when automatic networking isn't enough |
Learn more about [Docker Compose networks](https://docs.docker.com/compose/compose-file/05-services/#networks) and [service discovery](/fundamentals/service-discovery/).
@@ -139,8 +142,8 @@ Learn more about [Docker Compose healthcheck](https://docs.docker.com/compose/co
| Docker Compose | Aspire | Notes |
|----------------|-------------|-------|
-| `restart: unless-stopped` | Not supported | Restart policies aren't supported in Aspire |
-| `restart: always` | Not supported | Restart policies aren't supported in Aspire |
+| `restart: unless-stopped` | `.PublishAsDockerComposeService((resource, service) => service.Restart = "unless-stopped")` | Customize the generated Docker Compose service |
+| `restart: always` | `.PublishAsDockerComposeService((resource, service) => service.Restart = "always")` | Customize the generated Docker Compose service |
| `restart: no` | Default | No restart policy |
diff --git a/src/frontend/src/content/docs/app-host/eventing.mdx b/src/frontend/src/content/docs/app-host/eventing.mdx
index 20990fa24..46c88e294 100644
--- a/src/frontend/src/content/docs/app-host/eventing.mdx
+++ b/src/frontend/src/content/docs/app-host/eventing.mdx
@@ -22,7 +22,7 @@ The following events are available in the AppHost and occur in the following ord
### Subscribe to AppHost events
-To subscribe to built-in AppHost events, use the eventing API on the builder:
+To subscribe to built-in AppHost events, use the convenience extension methods directly on the builder. These methods return the same `IDistributedApplicationBuilder` instance so calls can be chained:
@@ -32,8 +32,27 @@ using Microsoft.Extensions.Logging;
var builder = DistributedApplication.CreateBuilder(args);
-var cache = builder.AddRedis("cache");
+builder.OnBeforeStart(static (@event, cancellationToken) =>
+{
+ var logger = @event.Services.GetRequiredService>();
+ logger.LogInformation("BeforeStartEvent");
+ return Task.CompletedTask;
+});
+
+builder.Build().Run();
+```
+
+The following builder-level extension methods are available for AppHost events:
+
+| Method | Event |
+|--------|-------|
+| `OnBeforeStart` | `BeforeStartEvent` — raised before the AppHost starts |
+| `OnBeforePublish` | `BeforePublishEvent` — raised before manifest publishing begins |
+| `OnAfterPublish` | `AfterPublishEvent` — raised after manifest publishing completes |
+If you need to subscribe via `IDistributedApplicationEventing` directly (for example, inside an `IDistributedApplicationEventingSubscriber`), you can use the lower-level `Eventing.Subscribe()` API:
+
+```csharp title="AppHost.cs"
builder.Eventing.Subscribe(
static (@event, cancellationToken) =>
{
@@ -48,7 +67,6 @@ builder.Eventing.Subscribe(
var logger = @event.Services.GetRequiredService>();
logger.LogInformation("AfterResourcesCreatedEvent");
return Task.CompletedTask;
- });
builder.Build().Run();
```
@@ -373,13 +391,13 @@ When publishing your application (generating deployment manifests), these events
| `AfterPublishEvent` | After publishing completes | Perform cleanup or post-publish actions |
```csharp title="AppHost.cs"
-builder.Eventing.Subscribe((@event, ct) =>
+builder.OnBeforePublish((@event, ct) =>
{
// Validate resources before publishing
return Task.CompletedTask;
});
-builder.Eventing.Subscribe((@event, ct) =>
+builder.OnAfterPublish((@event, ct) =>
{
// Post-publish actions
return Task.CompletedTask;
diff --git a/src/frontend/src/content/docs/app-host/executable-resources.mdx b/src/frontend/src/content/docs/app-host/executable-resources.mdx
index 7571e062c..87671888c 100644
--- a/src/frontend/src/content/docs/app-host/executable-resources.mdx
+++ b/src/frontend/src/content/docs/app-host/executable-resources.mdx
@@ -43,7 +43,7 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
+```typescript title="apphost.ts"
import { createBuilder } from './.modules/aspire.js';
const builder = await createBuilder();
@@ -147,16 +147,19 @@ var app = builder.AddExecutable("app", "node", ".", "app.js")
```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+import { createBuilder, EndpointProperty } from './.modules/aspire.js';
const builder = await createBuilder();
const redis = await builder.addRedis("cache");
+const redisEndpoint = await redis.getEndpoint("tcp");
+const redisHost = await redisEndpoint.property(EndpointProperty.Host);
+const redisPort = await redisEndpoint.property(EndpointProperty.Port);
const app = await builder.addExecutable("app", "node", ".", ["app.js"])
.withReference(redis)
- .withEnvironment("REDIS_HOST", redis.host)
- .withEnvironment("REDIS_PORT", redis.port);
+ .withEnvironment("REDIS_HOST", redisHost)
+ .withEnvironment("REDIS_PORT", redisPort);
```
diff --git a/src/frontend/src/content/docs/app-host/migrate-from-docker-compose.mdx b/src/frontend/src/content/docs/app-host/migrate-from-docker-compose.mdx
index bb7d479dc..7d3739b06 100644
--- a/src/frontend/src/content/docs/app-host/migrate-from-docker-compose.mdx
+++ b/src/frontend/src/content/docs/app-host/migrate-from-docker-compose.mdx
@@ -154,8 +154,8 @@ await builder.build().run();
-
-## MCP
-
-The MCP endpoint authentication is configured with `Dashboard:Mcp:AuthMode`. The MCP endpoint can be secured with API key authentication.
-
-API key authentication works by requiring each MCP request to have a valid `x-mcp-api-key` header value. It must match either the primary or secondary key.
-
-| Option | Description |
-|--------|-------------|
-| `Dashboard:Mcp:AuthMode` Default: `Unsecured` | Can be set to `ApiKey` or `Unsecured`. `Unsecured` should only be used during local development. It's not recommended when hosting the dashboard publicly or in other settings. |
-| `Dashboard:Mcp:PrimaryApiKey` Default: `null` | Specifies the primary API key. The API key can be any text, but a value with at least 128 bits of entropy is recommended. This value is required if auth mode is API key. |
-| `Dashboard:Mcp:SecondaryApiKey` Default: `null` | Specifies the secondary API key. The API key can be any text, but a value with at least 128 bits of entropy is recommended. This value is optional. If a second API key is specified, then the incoming `x-mcp-api-key` header value can match either the primary or secondary key. |
-| `Dashboard:Mcp:SuppressUnsecuredMessage` Default: `false` | Suppresses the unsecured message displayed in the dashboard when `Dashboard:Mcp:AuthMode` is `Unsecured`. This message should only be suppressed if an external frontdoor proxy is securing access to the endpoint. |
-| `Dashboard:Mcp:PublicUrl` Default: `null` | Specifies the public URL used to access the MCP server. The public URL is used when constructing links to the MCP server. If a public URL isn't specified, the MCP endpoint is used instead. This setting is important when the dashboard is accessed through a proxy and the dashboard endpoint isn't directly reachable. |
-| `Dashboard:Mcp:Disabled` Default: `false` | Disables the MCP server and remove MCP UI in the dashboard. |
-
## API
-The API section configures authentication for the dashboard's HTTP API endpoints.
+The API section configures the dashboard's Telemetry HTTP API (`/api/telemetry/*`) endpoints. The API is enabled by default and secured with API key authentication. The API key is auto-generated if one isn't provided.
| Option | Description |
|--------|-------------|
-| `Dashboard:Api:Enabled` Default: `false` | Enables the Telemetry HTTP API (`/api/telemetry/*`) endpoints. When `false`, the endpoints are not registered. |
-| `Dashboard:Api:AuthMode` Default: `Unsecured` | Can be set to `ApiKey` or `Unsecured`. `Unsecured` should only be used during local development. If an API key is configured and no auth mode is specified, defaults to `ApiKey`. |
-| `Dashboard:Api:PrimaryApiKey` Default: `null` | Specifies the primary API key. The API key can be any text, but a value with at least 128 bits of entropy is recommended. This value is required if auth mode is API key. |
+| `Dashboard:Api:Disabled` Default: `false` | Disables the Telemetry HTTP API (`/api/telemetry/*`) endpoints. When `true`, the endpoints are not registered. Set `ASPIRE_DASHBOARD_API_DISABLED=true` to disable the API via environment variable. |
+| `Dashboard:Api:Enabled` Default: `true` | **Deprecated.** Use `Dashboard:Api:Disabled` instead. When `false`, disables the Telemetry HTTP API endpoints. |
+| `Dashboard:Api:AuthMode` Default: `ApiKey` | Can be set to `ApiKey` or `Unsecured`. `Unsecured` should only be used during local development. |
+| `Dashboard:Api:PrimaryApiKey` Default: Auto-generated | Specifies the primary API key. The API key can be any text, but a value with at least 128 bits of entropy is recommended. When auth mode is `ApiKey` and no key is provided, a 128-bit key is auto-generated at startup. |
| `Dashboard:Api:SecondaryApiKey` Default: `null` | Specifies the secondary API key. The API key can be any text, but a value with at least 128 bits of entropy is recommended. This value is optional. |
## Resources
@@ -287,13 +272,14 @@ Telemetry limits have different scopes depending upon the telemetry type:
| `Dashboard:TelemetryLimits:MaxAttributeCount` Default: `128` | The maximum number of attributes on telemetry. |
| `Dashboard:TelemetryLimits:MaxAttributeLength` Default: `null` | The maximum length of attributes. |
| `Dashboard:TelemetryLimits:MaxSpanEventCount` Default: `null` | The maximum number of events on span attributes. |
+| `Dashboard:TelemetryLimits:MaxResourceCount` Default: `10,000` | The maximum number of resources tracked by the dashboard. |
## Other
| Option | Description |
|--------|-------------|
| `Dashboard:ApplicationName` Default: `Aspire` | The application name to be displayed in the UI. This applies only when no resource service URL is specified. When a resource service exists, the service specifies the application name. |
-| `Dashboard:AI:Disabled` Default: `false` | Disables AI features in the dashboard. |
+| `Dashboard:AI:Disabled` Default: `true` | Reserved for future use. The in-dashboard GitHub Copilot UI was removed in Aspire 13.3. AI coding agents can still access telemetry data via the [Aspire CLI and MCP server](/dashboard/ai-coding-agents/). |
| `Dashboard:UI:DisableResourceGraph` Default: `false` | Disables displaying the resource graph UI in the dashboard. |
| `Dashboard:UI:DisableImport` Default: `false` | Disables the telemetry import UI in the dashboard. |
diff --git a/src/frontend/src/content/docs/dashboard/copilot.mdx b/src/frontend/src/content/docs/dashboard/copilot.mdx
deleted file mode 100644
index f79ce89ec..000000000
--- a/src/frontend/src/content/docs/dashboard/copilot.mdx
+++ /dev/null
@@ -1,131 +0,0 @@
----
-title: GitHub Copilot in the Aspire dashboard
-description: Learn how to use GitHub Copilot in the Aspire dashboard.
----
-
-import { Aside, Steps } from '@astrojs/starlight/components';
-import { Image } from 'astro:assets';
-import copilotHeaderButton from '@assets/dashboard/copilot/copilot-headerbutton.png';
-import copilotChatQuestion from '@assets/dashboard/copilot/copilot-chatquestion.png';
-import resourcesAskCopilot from '@assets/dashboard/copilot/resources-askcopilot.png';
-import structuredLogsExplainError from '@assets/dashboard/copilot/structuredlogs-explainerror.png';
-import traceDetailsExplainTrace from '@assets/dashboard/copilot/tracedetails-explaintrace.png';
-
-
-
-Introducing GitHub Copilot in the Aspire dashboard! GitHub Copilot is your new AI debugging assistant.
-
-GitHub Copilot supercharges the dashboard's OpenTelemetry debugging and diagnostics experience. With AI, you can:
-
-- Review hundreds of log messages with a single click.
-- Investigate the root cause of errors across multiple apps.
-- Highlight performance issues in traces.
-- Explain obscure error codes using AI's huge knowledge repository.
-
-You'll have access to Copilot in the dashboard when you launch your app from VS Code or Visual Studio.
-
-## Requirements
-
-GitHub Copilot is only available when an Aspire project is run from an IDE.
-
-- VS Code and C# Dev Kit 1.19.63 or later.
-- Visual Studio 17.14 or later.
-
-You must also be logged into a GitHub account in the IDE that has a GitHub Copilot subscription. Copilot interactions in the Aspire dashboard use the GitHub account associated with your IDE.
-
-If you don't have a Copilot subscription yet, you can use Copilot for free by signing up for the [Copilot Free plan](https://aka.ms/dotnet/aspire/copilot-free-signup), which includes a monthly limit of chat interactions.
-
-## Get started
-
-To get started, launch your Aspire solution as usual in your IDE.
-
-
-
-1. Open your Aspire solution in a supported version of Visual Studio or VS Code with the C# Dev Kit extension installed.
-1. Ensure you're logged into the IDE with a GitHub account:
- - For Visual Studio, see [Add your GitHub accounts to your Visual Studio keychain](https://aka.ms/dotnet/aspire/copilot-vs-login).
- - For VS Code and C# Dev Kit, see [Set up GitHub Copilot in VS Code](https://aka.ms/dotnet/aspire/copilot-vscode-login).
-1. Run the Aspire AppHost.
-
-
-
-A GitHub Copilot button appears in the top-right corner of the dashboard. Selecting the button opens the Copilot UI.
-
-
-
-In the Copilot UI, you can either select a suggested question, such as **Summarize resources**, or enter your own question in the chat input field. Press Enter to submit. For example: `What health checks are configured in my app?`
-
-
-
-At the top of the Copilot UI are buttons to start a new conversation, expand the chat, or close the UI.
-
-## Analyze resources, console logs, and telemetry
-
-There are several options for analyzing resources, console logs, and telemetry in your app. Two were discussed above:
-
-- Select a suggested question. Copilot's suggested questions are contextual. For example, questions about resources appear on the resources page, and questions about distributed traces appear on the traces page.
-- Enter your own question in the chat input field, such as `Why did the product cache fail to start?` or `What caused recent errors in the frontend?`
-
-The dashboard also includes a Copilot option in the context menus for resources, structured logs, traces, and spans. Select **Ask GitHub Copilot** to investigate the selected data.
-
-
-
-If your app has structured logs or traces with errors, an **Explain errors** button appears on those pages. Selecting it makes all errors available to Copilot for investigation.
-
-
-
-On the trace details page, the **Explain trace** button is always visible. It provides a quick way to analyze the currently viewed trace.
-
-
-
-## Troubleshooting
-
-### Copilot hangs on "Getting ready"
-
-A known issue can cause the Copilot UI to hang with a "Getting ready" message when it is first accessed. This occurs when the dashboard is not launched from a [supported version](#requirements) of Visual Studio or VS Code, or when no GitHub account is logged into the IDE. Closing and reopening the Copilot UI fixes the issue. Once fixed, the UI correctly displays a message with requirements for using the dashboard with GitHub Copilot.
-
-### Reached monthly limit of Copilot Free plan
-
-The GitHub Copilot Free plan includes a monthly limit on chat usage. When this limit is exceeded, Copilot will no longer function in the dashboard. [Upgrade your Copilot subscription](https://aka.ms/dotnet/aspire/copilot-pro) or wait for the limit to reset.
-
-## Limitations
-
-### Data size
-
-AI models have limits on how much data they can process at once. The Aspire dashboard may limit the amount of data sent to GitHub Copilot when necessary.
-
-- Large data fields (e.g., long exception stack traces) may be truncated.
-- Requests involving large collections of telemetry may be shortened by omitting older items.
-
-## Disable GitHub Copilot in the dashboard
-
-GitHub Copilot only accesses data when you explicitly interact with it. For example, submitting a chat query or selecting **Ask GitHub Copilot** from a context menu.
-
-If you prefer to disable the feature entirely, set the `ASPIRE_DASHBOARD_AI_DISABLED` environment variable to `true` in your AppHost's _launchSettings.json_ file. This hides all Copilot UI elements in the dashboard.
-
-```json title="launchSettings.json" {14}
-{
- "$schema": "https://json.schemastore.org/launchsettings.json",
- "profiles": {
- "https": {
- "commandName": "Project",
- "dotnetRunMessages": true,
- "launchBrowser": true,
- "applicationUrl": "https://localhost:17129;http://localhost:15010",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development",
- "DOTNET_ENVIRONMENT": "Development",
- "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21124",
- "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22206",
- "ASPIRE_DASHBOARD_AI_DISABLED": "true"
- }
- }
- }
-}
-```
-
-## GitHub Copilot frequently asked questions
-
-For more information about GitHub Copilot, such as questions around plans, privacy and responsible AI usage, see the [GitHub Copilot frequently asked questions](https://github.com/features/copilot#faq).
diff --git a/src/frontend/src/content/docs/dashboard/explore.mdx b/src/frontend/src/content/docs/dashboard/explore.mdx
index e83a1b164..ddfafeb56 100644
--- a/src/frontend/src/content/docs/dashboard/explore.mdx
+++ b/src/frontend/src/content/docs/dashboard/explore.mdx
@@ -61,6 +61,9 @@ import removeTelemetryData from '@assets/dashboard/explore/remove-telemetry-data
import manageDataDialog from '@assets/dashboard/explore/manage-data-dialog.png';
import manageDataExpanded from '@assets/dashboard/explore/manage-data-expanded.png';
import settingsManageTelemetry from '@assets/dashboard/explore/settings-manage-telemetry.png';
+import notificationCenterBell from '@assets/dashboard/explore/notification-center-bell.png';
+import notificationCenterOpen from '@assets/dashboard/explore/notification-center-open.png';
+import notificationCenterResponse from '@assets/dashboard/explore/notification-center-response.png';
import themeSelection from '@assets/dashboard/explore/theme-selection.png';
import themeSelectionLight from '@assets/dashboard/explore/theme-selection-light.png';
import dashboardHelp from '@assets/dashboard/explore/dashboard-help.png';
@@ -72,8 +75,6 @@ In the upcoming sections, you discover how to create an Aspire project and embar
The screenshots featured in this article showcase the dark theme. For more information on theme selection, see [Theme selection](#theme-selection).
-The Aspire dashboard also includes **GitHub Copilot**, your AI debugging assistant, which can help you analyze resources, logs, traces, and telemetry data. Copilot is available when you launch your app from VS Code or Visual Studio with a GitHub account that has a Copilot subscription. For complete details, see [GitHub Copilot in the Aspire dashboard](copilot).
-
## Dashboard authentication
When you run an Aspire AppHost, the orchestrator starts up all the app's dependent resources and then opens a browser window to the dashboard. The Aspire dashboard requires token-based authentication for its users because it displays environment variables and other sensitive information.
@@ -171,11 +172,11 @@ When you select **Stop**, the resource stops running, and the **State** column u
For project resources, when the debugger is attached, it's reattached on restart.
-The **Start** button is then enabled, allowing you to start the resource again. Additionally, the dashboard displays a toast notification of the result of the action:
+The **Start** button is then enabled, allowing you to start the resource again. Additionally, the dashboard displays a notification of the result of the action in the [notification center](#notification-center):
-When a resource is in a non-running state, the **Start** button is enabled. Selecting **Start** starts the resource, and the **State** column updates to reflect the change. The **Stop** button is then enabled, allowing you to stop the resource again. The dashboard displays a toast notification of the result of the action:
+When a resource is in a non-running state, the **Start** button is enabled. Selecting **Start** starts the resource, and the **State** column updates to reflect the change. The **Stop** button is then enabled, allowing you to stop the resource again. The dashboard displays a notification of the result of the action in the [notification center](#notification-center):
@@ -202,8 +203,6 @@ The following submenu actions are available:
There might be resources with disabled submenu actions. They're greyed out when they're disabled. For example, the following screenshot shows the submenu actions disabled:
-
-When GitHub Copilot is available (when launching from VS Code or Visual Studio with a Copilot subscription), resource context menus also include an **Ask GitHub Copilot** option. This allows you to investigate resources using AI analysis.
#### Copy or Open in text visualizer
@@ -318,8 +317,6 @@ Consider the following screenshots showing the structured logs filter dialog:
-When GitHub Copilot is available and your app has structured logs with errors, an **Explain errors** button appears on the structured logs page. Selecting it makes all errors available to Copilot for AI-powered investigation. Additionally, structured log entries include an **Ask GitHub Copilot** option in their context menus.
-
### Traces page
Navigate to the **Traces** page to view all of the traces for your app. Aspire automatically configures tracing for the different projects in your app. Distributed tracing is a diagnostic technique that helps engineers localize failures and performance issues within applications, especially those that might be distributed across multiple machines or processes. For more information, see [.NET distributed tracing](https://learn.microsoft.com/dotnet/core/diagnostics/distributed-tracing). This technique tracks requests through an application and correlates work done by different application integrations. Traces also help identify how long different stages of the request took to complete. The traces page displays the following information:
@@ -365,8 +362,6 @@ Within the trace details page, there's a **View Logs** button that takes you to
-When GitHub Copilot is available, the trace details page also includes an **Explain trace** button that provides a quick way to analyze the currently viewed trace using AI. Additionally, traces and individual spans include **Ask GitHub Copilot** options in their context menus for detailed investigation.
-
The structured logs page is discussed in more detail in the [Structured logs page](#structured-logs-page) section.
#### Trace examples
@@ -565,6 +560,33 @@ To remove data for specific resources or data types, select the items you want t
+## Notification center
+
+The dashboard includes a notification center accessible via the bell icon in the top-right corner of the header. When there are unread notifications, a badge with the unread count appears on the icon.
+
+
+
+Selecting the bell icon opens the **Notifications** dialog, which lists all notifications. Notifications are generated when:
+
+- A resource command starts, succeeds, or fails. Both success and failure messages appear as notifications.
+- System events occur that require user attention.
+
+Each notification shows its title, message body, and a relative timestamp. Success and error notifications that include a command result provide a **View response** action button to open the result in the text visualizer.
+
+
+
+Selecting **View response** opens the text visualizer with the command's result content:
+
+
+
+The unread count resets when you open the notification center. To clear all notifications, select the **Dismiss all** button in the dialog.
+
+
+
+For information on returning messages from custom commands, see [Custom resource commands](/fundamentals/custom-resource-commands/).
+
## Settings dialog
@@ -624,24 +646,6 @@ In the dashboard, interaction prompts appear as:
These prompts appear directly in the dashboard interface, making it easy to provide the necessary information without switching to external tools or configuration files.
-## GitHub Copilot in the dashboard
-
-The Aspire dashboard includes GitHub Copilot as your AI debugging assistant when you launch your app from VS Code or Visual Studio with a GitHub account that has a Copilot subscription. Copilot can help you:
-
-- Review hundreds of log messages with a single click.
-- Investigate the root cause of errors across multiple apps.
-- Highlight performance issues in traces.
-- Explain obscure error codes using AI's vast knowledge repository.
-
-Copilot features appear throughout the dashboard interface:
-
-- **Copilot button**: This button is available in the top-right corner to open the Copilot chat interface.
-- **Context menus**: "Ask GitHub Copilot" options appear in resource, log, trace, and span context menus.
-- **Explain buttons**: "Explain errors" buttons appear on structured logs and "Explain trace" buttons appear on trace details pages.
-- **Suggested questions**: Contextual AI suggestions appear based on the current page.
-
-For comprehensive information about GitHub Copilot features, requirements, troubleshooting, and configuration options, see [GitHub Copilot in the Aspire dashboard](/dashboard/copilot/).
-
## Next steps
[Standalone Aspire dashboard](/dashboard/standalone/)
diff --git a/src/frontend/src/content/docs/dashboard/index.mdx b/src/frontend/src/content/docs/dashboard/index.mdx
index b87633919..3058400f0 100644
--- a/src/frontend/src/content/docs/dashboard/index.mdx
+++ b/src/frontend/src/content/docs/dashboard/index.mdx
@@ -24,8 +24,6 @@ import projectsImage from '@assets/dashboard/explore/resources-filtered-containe
import tracesImage from '@assets/dashboard/explore/trace-span-details.png';
import metricsImage from '@assets/dashboard/explore/metrics-view.png';
import structuredLogsImage from '@assets/dashboard/explore/structured-logs-errors-view.png';
-import copilotChatImage from '@assets/dashboard/copilot/copilot-chatquestion.png';
-import copilotExplainImage from '@assets/dashboard/copilot/structuredlogs-explainerror.png';
import mcpDialogImage from '@assets/dashboard/mcp-server/mcp-dialog.png';
-
-
-
## Key capabilities
@@ -185,7 +166,7 @@ Navigate to `http://localhost:18888` to open the dashboard, and point your apps'
title: 'MCP server',
description:
'Expose dashboard data to AI agents and coding assistants via the Model Context Protocol. Your AI tools get full observability context.',
- href: '/dashboard/mcp-server/',
+ href: '/get-started/aspire-mcp-server/',
label: 'Configure MCP',
accent: 'purple',
},
diff --git a/src/frontend/src/content/docs/dashboard/mcp-server.mdx b/src/frontend/src/content/docs/dashboard/mcp-server.mdx
deleted file mode 100644
index 5906e55fc..000000000
--- a/src/frontend/src/content/docs/dashboard/mcp-server.mdx
+++ /dev/null
@@ -1,179 +0,0 @@
----
-title: Manual MCP server configuration
-description: Learn how to manually configure the Aspire MCP server for older Aspire versions (9.0 - 13.0).
----
-
-import { Image } from 'astro:assets';
-import { Aside, Steps } from '@astrojs/starlight/components';
-import mcpDialogImage from '@assets/dashboard/mcp-server/mcp-dialog.png';
-
-
-
-This page describes manual MCP configuration for Aspire versions 9.0 through 13.0, or for situations where you need more control over the configuration.
-
-The Aspire MCP server is a local [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server. Aspire MCP integrates Aspire into your development AI eco-system. With Aspire MCP, you can:
-
-- Query resources, including resource states, endpoints, health status and commands.
-- Debug with real-time console logs.
-- Investigate development time telemetry, such as structured logs and distributed tracing across resources.
-- Execute resource commands.
-
-## Get started
-
-There are two ways to configure your AI assistant to use Aspire MCP:
-
-### Option 1: Using the Aspire CLI (recommended)
-
-The simplest way to get started is using the Aspire CLI, which can automatically detect and configure your AI assistant:
-
-
-
-1. Open a terminal in your Aspire project directory.
-1. Run the [`aspire agent init`](/reference/cli/commands/aspire-agent-init/) command:
-
- ```bash
- aspire agent init
- ```
-
-1. Follow the prompts to select which AI assistants to configure.
-1. The CLI will automatically set up the MCP server configuration for your selected environments.
-
-
-
-For more information, see the [aspire agent](/reference/cli/commands/aspire-agent/) command documentation.
-
-### Option 2: Manual configuration
-
-You can also manually configure your AI assistant to use Aspire MCP:
-
-
-
-1. Run your Aspire app.
-1. Open the Aspire dashboard and click on the MCP button in the top right corner of the dashboard. This launches a dialog that contains instructions for using Aspire MCP.
-1. Use the specified details in the dialog to configure your local AI assistant.
-
-
-
-Important settings required to use Aspire MCP:
-
-- `url` - Aspire MCP address.
-- `type` - `http` to indicate Aspire MCP is a [streamable-HTTP MCP server](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http).
-- `x-mcp-api-key` - An HTTP header with a key to secure access to the MCP server.
-
-
-
-There isn't a standard way to configure AI assistants with new MCP servers. Configuration varies depending on your local AI assistant:
-
-- [Claude Code](https://docs.claude.com/en/docs/claude-code/mcp)
-- [Codex](https://developers.openai.com/codex/mcp/)
-- [Copilot CLI](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/use-copilot-cli#add-an-mcp-server)
-- [Cursor](https://cursor.com/docs/context/mcp#installing-mcp-servers)
-- [Visual Studio](https://learn.microsoft.com/visualstudio/ide/mcp-servers?view=visualstudio#options-for-adding-an-mcp-server)
-- [VS Code](https://code.visualstudio.com/docs/copilot/customization/mcp-servers)
-
-The `x-mcp-api-key` value is used to secure access to the MCP, but the AI assistant must have access to it. Use features in the AI assistant to securely store the value. For example, if you're using VS Code then [input variables](https://code.visualstudio.com/docs/copilot/customization/mcp-servers#_input-variables-for-sensitive-data) can be used to configure the API key and avoid adding the API key to source control in an *mcp.json* file.
-
-## Your first prompts
-
-After configuration, try asking your AI assistant:
-
-> "Are all my resources running?"
-
-> "Analyze HTTP requests performance for RESOURCE_NAME."
-
-> "Restart unhealthy resources."
-
-## Tools
-
-The Aspire MCP server provides the following tools:
-
-### Resource management
-
-- `list_resources` - Lists all resources, including their state, health status, source, endpoints, and commands.
-- `execute_resource_command` - Executes a resource command. This tool accepts parameters for the resource name and command name.
-
-### Logging and telemetry
-
-- `list_console_logs` - Lists console logs for a resource.
-- `list_structured_logs` - Lists structured logs, optionally filtered by resource name.
-- `list_traces` - Lists distributed traces. Traces can be filtered using an optional resource name parameter.
-- `list_trace_structured_logs` - Lists structured logs for a trace.
-
-### Integration discovery
-
-- `list_integrations` - Lists all available Aspire hosting integrations with their package IDs and versions.
-- `get_integration_docs` - Retrieves documentation for a specific Aspire hosting integration package.
-
-### AppHost management
-
-- `list_apphosts` - Lists all available AppHosts in the workspace.
-- `select_apphost` - Selects a specific AppHost to work with.
-
-By default all resources, console logs and telemetry is accessible by Aspire MCP. Resources and associated telemetry can be excluded from MCP results by annotating the resource in the app host with `ExcludeFromMcp()`.
-
-```csharp title="C# — AppHost.cs"
-var builder = DistributedApplication.CreateBuilder(args);
-
-var apiservice = builder.AddProject("apiservice")
- .ExcludeFromMcp();
-
-builder.AddProject("webfrontend")
- .WithExternalHttpEndpoints()
- .WithReference(apiService);
-
-builder.Build().Run();
-```
-
-## Troubleshooting
-
-Aspire MCP is designed to work seamlessly with AI assistants, but you may encounter some setup challenges depending on your environment.
-
-### MCP connection secured with self-signed HTTPS certificate not supported by some AI assistants
-
-An MCP connection secured with HTTPS is recommended for security. However, some AI assistants currently don't support calling endpoints secured with a trusted, self-signed certificate. This includes the Aspire MCP, which is secured using [a self-signed certificate](https://learn.microsoft.com/dotnet/core/additional-tools/self-signed-certificates-guide).
-
-Currently the only work around for using Aspire MCP with these AI assistants is to configure an `http` MCP endpoint.
-
-- If you already launch your Aspire app with [the `http` launch profile](/integrations/dotnet/launch-profiles/) then your app isn't using HTTPS and you don't need to do anything.
-- If you use HTTPS everywhere, you can configure just the MCP endpoint to use `http` by updating *launchSettings.json*.
- - Set `ASPIRE_DASHBOARD_MCP_ENDPOINT_URL` to an `http` address.
- - Add `ASPIRE_ALLOW_UNSECURED_TRANSPORT` set to `true`.
-
-```json title="JSON — launchSettings.json" {14-15}
-{
- "profiles": {
- "https": {
- "commandName": "Project",
- "launchBrowser": true,
- "dotnetRunMessages": true,
- "applicationUrl": "https://localhost:15887;http://localhost:15888",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development",
- "DOTNET_ENVIRONMENT": "Development",
- "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:16037",
- "ASPIRE_DASHBOARD_OTLP_HTTP_ENDPOINT_URL": "https://localhost:16038",
- "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:17037",
- "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "http://localhost:16036",
- "ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true"
- }
- }
- }
-}
-```
-
-
-
-## Limitations
-
-Aspire MCP is a powerful tool, but there are a few things to keep in mind when using it.
-
-### Data size
-
-AI models have limits on how much data they can process at once. Aspire MCP may limit the amount of data returned from tools when necessary.
-
-- Large data fields (e.g., long exception stack traces) may be truncated.
-- Requests involving large collections of telemetry may be shortened by omitting older items.
diff --git a/src/frontend/src/content/docs/dashboard/overview.mdx b/src/frontend/src/content/docs/dashboard/overview.mdx
index 1cb5eeda1..580ba2347 100644
--- a/src/frontend/src/content/docs/dashboard/overview.mdx
+++ b/src/frontend/src/content/docs/dashboard/overview.mdx
@@ -19,7 +19,7 @@ Key features of the dashboard include:
- Real-time tracking of logs, traces, and environment configurations.
- User interface to [stop, start, and restart resources](/dashboard/explore/#resource-actions).
- Collects and displays logs and telemetry; [view structured logs, traces, and metrics](/dashboard/explore/#monitoring-pages) in an intuitive UI.
-- Enhanced debugging with [GitHub Copilot](/dashboard/copilot/), your AI-powered assistant built into the dashboard.
+- Enhanced debugging with [AI coding agents](/dashboard/ai-coding-agents/) that use the Aspire CLI and MCP server to fetch logs and telemetry from the dashboard.
## Use the dashboard with Aspire projects
diff --git a/src/frontend/src/content/docs/dashboard/standalone.mdx b/src/frontend/src/content/docs/dashboard/standalone.mdx
index b322e0e23..c3069bbe4 100644
--- a/src/frontend/src/content/docs/dashboard/standalone.mdx
+++ b/src/frontend/src/content/docs/dashboard/standalone.mdx
@@ -23,6 +23,8 @@ The [Aspire dashboard](/dashboard/overview/) provides a great UI for viewing tel
## Start the dashboard
+### Docker
+
The dashboard is started using the Docker command line.
@@ -52,6 +54,24 @@ The preceding Docker command:
- Mapping the dashboard's OTLP/gRPC port `18889` to the host's port `4317`. Port `4317` receives OpenTelemetry data from apps using [OTLP/gRPC](https://opentelemetry.io/docs/specs/otlp/#otlpgrpc).
- Mapping the dashboard's OTLP/HTTP port `18890` to the host's port `4318`. Port `4318` receives OpenTelemetry data from apps using [OTLP/HTTP](https://opentelemetry.io/docs/specs/otlp/#otlphttp).
+### Aspire CLI
+
+If you have the [Aspire CLI](/reference/cli/overview/) installed, you can start the dashboard with a single command:
+
+```bash title="Aspire CLI"
+aspire dashboard run
+```
+
+The dashboard starts with the following defaults:
+
+- **Frontend UI** at `http://localhost:18888`
+- **OTLP/gRPC** endpoint at `http://localhost:4317`
+- **OTLP/HTTP** endpoint at `http://localhost:4318`
+
+The dashboard is secured with a browser token by default. A login URL with the token is displayed in the terminal output. Use `--allow-anonymous` to disable authentication during local development.
+
+For all available options, see the [`aspire dashboard run`](/reference/cli/commands/aspire-dashboard-run/) command reference.
+
## Login to the dashboard
Data displayed in the dashboard can be sensitive. By default, the dashboard is secured with authentication that requires a token to login.
@@ -115,7 +135,7 @@ Although there is no restriction on where the dashboard is run, the dashboard is
The dashboard has functionality for viewing Aspire resources. The dashboard resource features are disabled when it is run in standalone mode. To enable the resources UI, [add configuration for a resource service](/dashboard/configuration/#resources).
-[GitHub Copilot functionality](/dashboard/copilot/) isn't available in the standalone dashboard. Copilot uses a connection to your IDE to communicate with GitHub Copilot and that connection isn't available when the dashboard is in standalone mode.
+AI coding agents can still use the standalone dashboard via the Aspire CLI and MCP server. See [Dashboard and AI coding agents](/dashboard/ai-coding-agents/#standalone-mode) for details.
## Send telemetry to the dashboard
@@ -145,6 +165,32 @@ To configure applications:
Additional configuration may be required to authenticate with the dashboard if you have configured the dashboard to secure the telemetry endpoint. For more information, see [Securing the telemetry endpoint](/dashboard/security-considerations/#secure-telemetry-endpoint).
+## Query telemetry with the Aspire CLI
+
+The [Aspire CLI](/reference/cli/overview/) can query telemetry data directly from the dashboard's API, allowing you to view logs, traces, and spans from the terminal without opening the dashboard UI.
+
+The dashboard's telemetry API is secured with API key authentication by default. When using the CLI commands, you can pass the dashboard's login URL (including the browser token) via `--dashboard-url` and the token is automatically exchanged for an API key. Alternatively, if the dashboard is started with `aspire dashboard run --allow-anonymous` or the `ASPIRE_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS` environment variable is set to `true`, no authentication is required — this should only be used during local development.
+
+### View telemetry with `aspire otel`
+
+The [`aspire otel`](/reference/cli/commands/aspire-otel/) commands retrieve telemetry from the dashboard. Use `--dashboard-url` to point at a standalone dashboard — you can paste the full login URL and the browser token is automatically exchanged for an API key:
+
+```bash title="Aspire CLI"
+aspire otel logs --dashboard-url "http://localhost:18888/login?t="
+aspire otel traces --dashboard-url "http://localhost:18888/login?t="
+aspire otel spans --dashboard-url "http://localhost:18888/login?t="
+```
+
+For more details, see the [`aspire otel logs`](/reference/cli/commands/aspire-otel-logs/), [`aspire otel traces`](/reference/cli/commands/aspire-otel-traces/), and [`aspire otel spans`](/reference/cli/commands/aspire-otel-spans/) command references.
+
+### Use AI agents with `aspire agent mcp`
+
+The [`aspire agent mcp`](/reference/cli/commands/aspire-agent-mcp/) command starts an MCP (Model Context Protocol) server that AI assistants can connect to. When used with `--dashboard-url`, it runs in dashboard-only mode and exposes telemetry tools (structured logs and traces) to MCP-compatible clients:
+
+```bash title="Aspire CLI"
+aspire agent mcp --dashboard-url "http://localhost:18888/login?t="
+```
+
## Sample
For a sample of using the standalone dashboard, see the [Standalone Aspire dashboard sample app](https://github.com/microsoft/aspire-samples/tree/main/samples/standalone-dashboard).
diff --git a/src/frontend/src/content/docs/de/index.mdx b/src/frontend/src/content/docs/de/index.mdx
index 573c0a082..7b0871904 100644
--- a/src/frontend/src/content/docs/de/index.mdx
+++ b/src/frontend/src/content/docs/de/index.mdx
@@ -11,7 +11,7 @@ prev: false
next: false
banner:
content: |
- 🚀 Aspire 13.2 wurde veröffentlicht! — Was ist neu in Aspire 13.2.
+ 🚀 Aspire 13.3 wurde veröffentlicht! — Was ist neu in Aspire 13.3.
hero:
tagline: Dein Stack, vereinfacht.
Orchestriere Frontends, APIs, Container und Datenbanken mühelos—ohne Umschreiben, ohne Grenzen. Erweitere Aspire, um jedes Projekt anzutreiben.
image:
diff --git a/src/frontend/src/content/docs/deployment/azure/app-service.mdx b/src/frontend/src/content/docs/deployment/azure/app-service.mdx
index fb38bf49a..27908adcf 100644
--- a/src/frontend/src/content/docs/deployment/azure/app-service.mdx
+++ b/src/frontend/src/content/docs/deployment/azure/app-service.mdx
@@ -99,6 +99,51 @@ Start with the common case:
App Service routes public traffic to the app's target port, even if the app listens on a development port such as `8080`.
+### Automatic HTTPS upgrade
+
+Azure App Service redirects HTTP traffic to HTTPS at the platform level. To match the deployed serving scheme, Aspire upgrades external HTTP endpoints in an App Service environment to HTTPS on port 443 when it generates endpoint URLs and service discovery connection strings for dependent resources.
+
+This behavior is enabled by default. If you intentionally need generated endpoint URLs and connection strings to preserve `http://`, disable the upgrade on the App Service environment:
+
+
+
+```csharp title="AppHost.cs"
+var builder = DistributedApplication.CreateBuilder(args);
+
+builder.AddAzureAppServiceEnvironment("app-service-env")
+ .WithHttpsUpgrade(false);
+
+builder.AddDockerfile("web", "./web")
+ .WithHttpEndpoint(port: 8080, targetPort: 8080, name: "http")
+ .WithExternalHttpEndpoints();
+
+builder.Build().Run();
+
+```
+
+
+```typescript title="apphost.ts" twoslash
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+await builder
+ .addAzureAppServiceEnvironment("app-service-env")
+ .withHttpsUpgrade(false);
+
+await builder
+ .addDockerfile("web", "./web")
+ .withHttpEndpoint({ port: 8080, targetPort: 8080, name: "http" })
+ .withExternalHttpEndpoints();
+
+await builder.build().run();
+```
+
+
+
+
+Disabling the upgrade primarily affects generated endpoint configuration for dependent resources. Azure App Service can still redirect HTTP requests to HTTPS after deployment.
+
### Constraints
Azure App Service uses a public website model:
diff --git a/src/frontend/src/content/docs/deployment/azure/container-apps.mdx b/src/frontend/src/content/docs/deployment/azure/container-apps.mdx
index ab5dc36d9..d54a3c49d 100644
--- a/src/frontend/src/content/docs/deployment/azure/container-apps.mdx
+++ b/src/frontend/src/content/docs/deployment/azure/container-apps.mdx
@@ -114,7 +114,45 @@ Azure Container Apps uses an Envoy-based HTTP edge proxy for HTTP ingress:
- public HTTP-style ingress is exposed through the platform HTTPS endpoint by default
- HTTP requests sent to the app are redirected to HTTPS
-- you can turn off that default upgrade behavior on the environment if you need to keep HTTP behavior
+
+To match the deployed serving scheme, Aspire upgrades external HTTP endpoints in an Azure Container Apps environment to HTTPS when it generates endpoint URLs and service discovery connection strings for dependent resources. If you intentionally need generated endpoint URLs and connection strings to preserve `http://`, disable the upgrade on the environment:
+
+
+
+```csharp title="AppHost.cs"
+var builder = DistributedApplication.CreateBuilder(args);
+
+builder.AddAzureContainerAppEnvironment("aca-env")
+ .WithHttpsUpgrade(false);
+
+builder.AddDockerfile("web", "./web")
+ .WithHttpEndpoint(port: 8080, targetPort: 8080, name: "http")
+ .WithExternalHttpEndpoints();
+
+builder.Build().Run();
+```
+
+
+```typescript title="apphost.ts" twoslash
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+await builder
+ .addAzureContainerAppEnvironment("aca-env")
+ .withHttpsUpgrade(false);
+
+await builder
+ .addDockerfile("web", "./web")
+ .withHttpEndpoint({ port: 8080, targetPort: 8080, name: "http" })
+ .withExternalHttpEndpoints();
+
+await builder.build().run();
+```
+
+
+
+Disabling the upgrade primarily affects generated endpoint configuration for dependent resources. Azure Container Apps can still redirect HTTP requests to HTTPS after deployment.
### Constraints and edge cases
@@ -221,8 +259,26 @@ The `ConfigureCustomDomain` API is experimental and may change or be removed in
:::
-:::note
-The `ConfigureCustomDomain` API is not yet available in the TypeScript AppHost SDK.
+```typescript title="apphost.ts" twoslash
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const customDomain = await builder.addParameter("customDomain");
+const certificateName = await builder.addParameter("certificateName");
+
+await builder.addAzureContainerAppEnvironment("env");
+
+const api = await builder.addProject("api", "../Api/Api.csproj", "http");
+await api.publishAsAzureContainerApp(async (_infrastructure, app) => {
+ await app.configureCustomDomain(customDomain, certificateName);
+});
+
+await builder.build().run();
+```
+
+:::caution
+The `configureCustomDomain` API is experimental and may change or be removed in future updates. For more information, see [ASPIREACADOMAINS001](/diagnostics/aspireacadomains001/).
:::
@@ -325,14 +381,14 @@ After deployment, use the dashboard URL in the CLI output, the Azure portal, or
## Clean up resources
-To remove deployed resources, delete the resource group:
+To tear down deployed resources, use `aspire destroy`. The command discovers the deployment, lists the Azure resources it will remove, and prompts for confirmation before deleting the resource group:
-```bash title="Delete resource group"
-az group delete --name
+```bash title="Aspire CLI - Destroy the deployed environment"
+aspire destroy
```
:::danger
-This command deletes all resources in the resource group, including any resources not created by the Aspire CLI.
+`aspire destroy` deletes the entire resource group, including any resources that weren't created by the Aspire CLI. Pass `--yes` only in automated or CI scenarios where you've already validated what will be removed. This action cannot be undone.
:::
## See also
diff --git a/src/frontend/src/content/docs/deployment/azure/index.mdx b/src/frontend/src/content/docs/deployment/azure/index.mdx
index ff68589d3..889132f40 100644
--- a/src/frontend/src/content/docs/deployment/azure/index.mdx
+++ b/src/frontend/src/content/docs/deployment/azure/index.mdx
@@ -53,6 +53,7 @@ After authentication succeeds, `aspire deploy` still needs a small set of shared
| `Azure:SubscriptionId` | `Azure__SubscriptionId` | Target Azure subscription |
| `Azure:Location` | `Azure__Location` | Default Azure region for provisioned resources |
| `Azure:ResourceGroup` | `Azure__ResourceGroup` | Resource group to create or reuse |
+| `Azure:CredentialProcessTimeoutSeconds` | `Azure__CredentialProcessTimeoutSeconds` | Override the credential subprocess timeout (5–600 seconds) when Azure CLI or other process-based credentials are slow |
### Local settings
@@ -79,6 +80,7 @@ After your CI/CD system authenticates to Azure, provide the shared Azure setting
| Azure subscription | `Azure__SubscriptionId` | Select the target Azure subscription |
| Azure location | `Azure__Location` | Set the default region for provisioned resources |
| Azure resource group | `Azure__ResourceGroup` | Select the resource group to create or reuse |
+| Azure credential process timeout | `Azure__CredentialProcessTimeoutSeconds` | Override the credential subprocess timeout (5–600 seconds) when Azure CLI, PowerShell, or AZD credential validation is slow on the build agent |
| AppHost parameters | `Parameters__*` environment variables | Supply any required AppHost inputs |
| Aspire environment | `aspire deploy --environment ` | Select the Aspire environment, such as `Production`, for environment-scoped parameter resolution |
diff --git a/src/frontend/src/content/docs/deployment/docker-compose.mdx b/src/frontend/src/content/docs/deployment/docker-compose.mdx
index ecd44ed01..e5068111a 100644
--- a/src/frontend/src/content/docs/deployment/docker-compose.mdx
+++ b/src/frontend/src/content/docs/deployment/docker-compose.mdx
@@ -1,12 +1,12 @@
---
title: Deploy to Docker Compose
-description: Learn how to publish and deploy your Aspire application using Docker Compose.
+description: Learn how to publish and deploy your Aspire application using Docker Compose with Docker or Podman.
---
import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
import LearnMore from '@components/LearnMore.astro';
-Docker Compose is a deployment target for Aspire applications. When you add a Docker Compose environment to your AppHost, Aspire generates Docker Compose files, environment variable configurations, and container images from your app model. You can then deploy these artifacts to any machine running Docker.
+Docker Compose is a deployment target for Aspire applications. When you add a Docker Compose environment to your AppHost, Aspire generates Docker Compose files, environment variable configurations, and container images from your app model. You can then deploy these artifacts to any machine running Docker or Podman.
+## Container runtime support
+
+Aspire supports both **Docker** and **Podman** as container runtimes for Docker Compose deployments. Podman is popular in security-conscious, daemonless, and rootless environments. Aspire automatically detects and uses the best available runtime — no additional configuration is required.
+
+### Auto-detection
+
+At deploy time, Aspire probes Docker and Podman in parallel and selects the active runtime using the following priority:
+
+1. A runtime that is **running** (daemon active) is preferred over one that is merely installed.
+2. If both runtimes are running, **Docker is preferred** as the tiebreaker.
+3. The detected runtime is cached for the lifetime of the operation.
+
+
+
+### Override the active runtime
+
+To bypass auto-detection and force a specific runtime, set the `ASPIRE_CONTAINER_RUNTIME` environment variable to `docker` or `podman` before invoking the Aspire CLI:
+
+```bash title="Terminal"
+ASPIRE_CONTAINER_RUNTIME=podman aspire deploy
+```
+
+When this variable is set, the chosen runtime is shown by `aspire doctor` with the reason `(explicit configuration)` and is honored even when both runtimes are running.
+
+### Podman-specific behavior
+
+When Podman is the active runtime, Aspire uses `podman-compose` (or the Docker Compose v2 provider for Podman) for `compose up` / `compose down` operations. Service discovery uses `podman ps --filter label=...`, which is compatible with both `podman-compose` (Python) and Docker Compose v2 providers.
+
+
+
## Prerequisites
To deploy with Docker Compose, add a Docker Compose environment resource to your AppHost using `AddDockerComposeEnvironment`:
@@ -109,13 +149,19 @@ Aspire provides a progressive deployment workflow for Docker Compose, allowing y
### Clean up deployment
-To stop and remove a running Docker Compose deployment, use the `aspire do docker-compose-down-{resource-name}` command:
+To stop and remove a running Docker Compose deployment, use the `aspire destroy` command:
```bash title="Terminal"
-aspire do docker-compose-down-env
+aspire destroy
```
-This command stops and removes all containers, networks, and volumes created by the Docker Compose deployment.
+The command shows the resources that will be removed and prompts for confirmation. Once confirmed, it stops and removes all containers, networks, and volumes created by the Docker Compose deployment.
+
+
## Multi-environment deployments
@@ -166,7 +212,87 @@ When you publish or deploy, Aspire generates the following artifacts in the `asp
| `docker-compose.yaml` | The generated Compose file defining all services, networks, and volumes. |
| `.env` | Environment variable file with expected parameters (unfilled after `aspire publish`). |
| `.env.{environment}` | Environment-specific variable files with filled-in values (generated during `prepare` or `deploy`). |
-| `Dockerfile` (per resource) | Dockerfiles for resources that require generated build contexts. |
+| `Dockerfile` (per resource) | Dockerfiles for resources that use existing or programmatically generated Dockerfile build contexts. |
+
+## Generate Dockerfiles from your AppHost
+
+Docker Compose deployments can build images from Dockerfiles that are generated by your AppHost. Use `AddDockerfileBuilder` to create a new container resource from a generated Dockerfile, or `WithDockerfileBuilder` to replace the image for an existing container resource with a generated Dockerfile build.
+
+
+
+
+
+```csharp title="AppHost.cs"
+using Aspire.Hosting.ApplicationModel.Docker;
+
+var builder = DistributedApplication.CreateBuilder(args);
+
+builder.AddDockerComposeEnvironment("env");
+
+#pragma warning disable ASPIREDOCKERFILEBUILDER001
+builder.AddDockerfileBuilder("frontend", "../frontend", context =>
+{
+ var build = context.Builder.From("node:22-alpine", "build");
+ build.WorkDir("/app")
+ .Copy("package*.json", "./")
+ .Run("npm ci")
+ .Copy(".", ".")
+ .Run("npm run build");
+
+ var runtime = context.Builder.From("nginx:alpine", "runtime");
+ runtime.CopyFrom("build", "/app/dist", "/usr/share/nginx/html")
+ .Expose(80);
+
+ return Task.CompletedTask;
+}, stage: "runtime");
+#pragma warning restore ASPIREDOCKERFILEBUILDER001
+
+builder.Build().Run();
+```
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+import type { DockerfileBuilderCallbackContext } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+await builder.addDockerComposeEnvironment('env');
+
+const configureDockerfile = async (context: DockerfileBuilderCallbackContext) => {
+ const dockerfile = await context.builder();
+
+ await dockerfile
+ .from('node:22-alpine', { stageName: 'build' })
+ .workDir('/app')
+ .copy('package*.json', './')
+ .run('npm ci')
+ .copy('.', '.')
+ .run('npm run build');
+
+ await dockerfile
+ .from('nginx:alpine', { stageName: 'runtime' })
+ .copyFrom('build', '/app/dist', '/usr/share/nginx/html')
+ .expose(80);
+};
+
+await builder.addDockerfileBuilder(
+ 'frontend',
+ '../frontend',
+ configureDockerfile,
+ { stage: 'runtime' }
+);
+
+await builder.build().run();
+```
+
+
+
+The generated Dockerfile is included with the Docker Compose output and is used when `aspire do prepare-{resource-name}` or `aspire deploy` builds container images.
## Environment variables
@@ -195,14 +321,72 @@ builder.AddDockerComposeEnvironment("env")
```
-:::note
-The `ConfigureEnvFile` API is not yet available in the TypeScript AppHost SDK.
-:::
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const api = await builder.addContainer('api', 'nginx:alpine');
+await api.withBindMount('/host/path/data', '/container/data');
+
+const compose = await builder.addDockerComposeEnvironment('env');
+await compose.configureEnvFile(async (envVars) => {
+ const bindMount = await envVars.get('API_BINDMOUNT_0');
+ await bindMount.description.set('Customized bind mount source');
+ await bindMount.defaultValue.set('./data');
+});
+
+await builder.build().run();
+```
This is useful when you need to add custom environment variables to the generated `.env` file or modify how environment variables are captured.
+## Customize the Docker Compose file
+
+Use `ConfigureComposeFile` to customize the generated `docker-compose.yml` model before Aspire writes it to disk:
+
+
+
+```csharp title="AppHost.cs"
+var builder = DistributedApplication.CreateBuilder(args);
+
+builder.AddDockerComposeEnvironment("env")
+ .ConfigureComposeFile(composeFile =>
+ {
+ composeFile.Name = "my-app";
+
+ var api = composeFile.Services["api"];
+ api.PullPolicy = "always";
+ });
+
+builder.AddProject("api");
+
+builder.Build().Run();
+```
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const compose = await builder.addDockerComposeEnvironment('env');
+await compose.configureComposeFile(async (composeFile) => {
+ await composeFile.name.set('my-app');
+
+ const api = await composeFile.services.get('api');
+ await api.pullPolicy.set('always');
+});
+
+await builder.addProject('api', '../Api/Api.csproj', 'http');
+
+await builder.build().run();
+```
+
+
+
## Customize Docker Compose services
To customize the generated Docker Compose service for a specific resource, use the `PublishAsDockerComposeService` method. This is optional — all resources are automatically included in the Docker Compose output. Use this method only when you need to modify the generated service definition:
@@ -214,27 +398,37 @@ var builder = DistributedApplication.CreateBuilder(args);
builder.AddDockerComposeEnvironment("env");
+var containerName = builder.AddParameter("container-name");
+
builder.AddContainer("cache", "redis", "latest")
.PublishAsDockerComposeService((resource, service) =>
{
// Customize the generated Docker Compose service
+ service.ContainerName =
+ containerName.AsEnvironmentPlaceholder(resource);
service.Labels.Add("com.example.team", "backend");
+ service.Restart = "unless-stopped";
});
builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
+```typescript title="apphost.ts"
import { createBuilder } from './.modules/aspire.js';
const builder = await createBuilder();
await builder.addDockerComposeEnvironment('env');
+const containerName = await builder.addParameter('container-name');
const cache = await builder.addContainer('cache', 'redis:latest');
-await cache.publishAsDockerComposeService(async (resource, service) => {
+await cache.publishAsDockerComposeService(async (composeService, service) => {
+ await service.containerName.set(
+ await containerName.asEnvironmentPlaceholder(composeService)
+ );
await service.labels.set('com.example.team', 'backend');
+ await service.restart.set('unless-stopped');
});
await builder.build().run();
@@ -242,7 +436,42 @@ await builder.build().run();
-The `configure` callback receives the `DockerComposeServiceResource` and the generated `Service` object, allowing you to modify properties like labels, restart policy, or other Docker Compose service settings.
+The `configure` callback receives the `DockerComposeServiceResource` and the generated `Service` object, allowing you to modify properties like labels, restart policy, container name, or other Docker Compose service settings. Use `AsEnvironmentPlaceholder` for values that should be written as Compose environment variable placeholders in the generated YAML and `.env` files.
+
+## Resolve Docker Compose service host names
+
+Use `GetHostAddressExpression` when you need the host name that another Docker Compose service should use for an endpoint. In Docker Compose deployments, this expression resolves to the generated service name on the Compose network.
+
+
+
+```csharp title="AppHost.cs"
+var builder = DistributedApplication.CreateBuilder(args);
+
+var compose = builder.AddDockerComposeEnvironment("env");
+
+var api = builder.AddContainer("api", "nginx", "alpine")
+ .WithHttpEndpoint(name: "http", targetPort: 80);
+
+var apiHost = compose.Resource.GetHostAddressExpression(
+ api.GetEndpoint("http"));
+```
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const compose = await builder.addDockerComposeEnvironment('env');
+
+const api = await builder.addContainer('api', 'nginx:alpine');
+await api.withHttpEndpoint({ name: 'http', targetPort: 80 });
+
+const apiEndpoint = await api.getEndpoint('http');
+const apiHost = await compose.getHostAddressExpression(apiEndpoint);
+```
+
+
## Image pull policy
@@ -542,6 +771,7 @@ This workflow checks out your code, sets up .NET and installs the Aspire CLI, au
- [Docker integration](/integrations/compute/docker/)
- [Pipelines and app topology](/deployment/pipelines/)
- [Deploy your first Aspire app](/get-started/deploy-first-app/)
+- [aspire doctor command](/reference/cli/commands/aspire-doctor/)
- [`Aspire.Hosting.Docker` C# API reference](https://aspire.dev/reference/api/csharp/aspire.hosting.docker/)
- [`Aspire.Hosting.Docker` TypeScript API reference](https://aspire.dev/reference/api/typescript/aspire.hosting.docker/)
- [Working with GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry)
diff --git a/src/frontend/src/content/docs/deployment/index.mdx b/src/frontend/src/content/docs/deployment/index.mdx
index 2f09d67ca..0234f5aff 100644
--- a/src/frontend/src/content/docs/deployment/index.mdx
+++ b/src/frontend/src/content/docs/deployment/index.mdx
@@ -99,7 +99,7 @@ Aspire is not locked to a single cloud. Your local dev topology translates direc
icon: 'seti:config',
title: 'Kubernetes',
description:
- 'Produce Kubernetes Helm charts that reflect your AppHost model. Deploy to any K8s cluster with your existing toolchain.',
+ 'Publish Helm charts or deploy directly to any Kubernetes cluster — self-managed or AKS — with full publish and deploy support.',
href: '/deployment/kubernetes/',
},
{
diff --git a/src/frontend/src/content/docs/deployment/javascript-apps.mdx b/src/frontend/src/content/docs/deployment/javascript-apps.mdx
index 955aaeaa3..2572f31b8 100644
--- a/src/frontend/src/content/docs/deployment/javascript-apps.mdx
+++ b/src/frontend/src/content/docs/deployment/javascript-apps.mdx
@@ -1,77 +1,56 @@
---
title: Deploy JavaScript apps
-description: Learn the main production deployment models for Vite and JavaScript frontends in Aspire.
+description: Learn the main production deployment models for JavaScript app resources in Aspire.
---
-import { Aside, Tabs, TabItem } from '@astrojs/starlight/components';
+import { Aside, TabItem, Tabs } from '@astrojs/starlight/components';
import LearnMore from '@components/LearnMore.astro';
-For Vite and other JavaScript frontends, there are three common production deployment models:
+JavaScript app resources can represent static browser frontends, Node.js servers, SSR frameworks, and specialized resources such as Next.js apps. The deployment decision is less about which AppHost API you started with and more about **which resource owns the public HTTP surface in production**.
-- A backend service serves the built frontend files.
-- A reverse proxy serves the built frontend files.
-- A standalone static frontend talks directly to a separately hosted backend.
+Use this table as the starting point:
-All three models use `AddViteApp` for local development and for producing the frontend build output during publishing. The difference is which resource becomes the production entrypoint and whether the browser talks to the backend directly.
+| What are you deploying? | Production entrypoint | Aspire pattern |
+| ----------------------------------------------------- | --------------------------- | --------------------------- |
+| Static frontend served by a backend | Backend, API, or web server | `PublishWithContainerFiles` |
+| Static frontend served by a gateway or BFF | Reverse proxy | `PublishWithStaticFiles` |
+| Static frontend served by its own JavaScript resource | JavaScript app resource | `PublishAsStaticWebsite` |
+| SSR or Node.js app with a built server artifact | JavaScript app resource | `PublishAsNodeServer` |
+| SSR or Node.js app started by a package script | JavaScript app resource | `PublishAsNpmScript` |
+| Next.js standalone app | Next.js app resource | `AddNextJsApp` |
+| Static frontend hosted separately from the backend | Static host + separate API | Custom topology |
-These same production models also apply to `AddJavaScriptApp`. The difference
-is that `AddViteApp` knows about Vite's development conventions, while
-`AddJavaScriptApp` makes fewer assumptions and leaves the run and build commands
-under your control. This article is deployment-target agnostic: it explains the
-JavaScript hosting models you can use with Aspire, not the full steps for a
-specific deployment target.
+This article is deployment-target agnostic. It explains the JavaScript hosting models you can use with Aspire, not the full steps for a specific deployment target.
-## Deployment rule
+## Choose the production entrypoint
-For deployment, `AddViteApp` and `AddJavaScriptApp` should be treated as frontend build resources, not as the final production web server.
+For deployment, most JavaScript app resources should be treated as build-only resources until you choose a production serving model. Different app resource APIs provide different defaults, such as Vite conventions, direct Node.js script execution, command-driven JavaScript apps, or Next.js standalone output.
-To deploy a JavaScript frontend, you must choose which other resource owns the public HTTP surface in production:
+To deploy a JavaScript app, choose which resource owns the public HTTP surface in production:
-- Use `PublishWithContainerFiles(...)` when your backend or web server will serve the built frontend files.
-- Use `PublishWithStaticFiles(...)` when your reverse proxy, gateway, or BFF will serve the built frontend files.
+- Use `PublishWithContainerFiles(...)` when your backend or web server serves the built frontend files.
+- Use `PublishWithStaticFiles(...)` when your reverse proxy, gateway, or BFF serves the built frontend files.
+- Use a JavaScript publish method when the JavaScript app resource should become the deployed static website or Node.js server.
+- Use `AddNextJsApp(...)` for a Next.js app that publishes as a standalone Next.js server.
-If you only add a Vite or JavaScript app and reference backend services, Aspire still needs one of these production hosting patterns to know who serves the built frontend in deployment.
+:::caution[Experimental]
+The JavaScript app resource publish methods and `AddNextJsApp` are marked `[Experimental]` and require suppressing the `ASPIREJAVASCRIPT001` diagnostic.
+:::
-
+If you only add a Vite or JavaScript app and reference backend services, Aspire still needs one of these production hosting patterns to know who serves requests in deployment. During publish or deploy, Aspire validates that build-only containers are either consumed by another resource through `PublishWithContainerFiles(...)` or `PublishWithStaticFiles(...)`, or converted into a deployable JavaScript resource with a JavaScript publish method. An unconsumed build-only container does not participate in deployment.
-
-## Deployment models
-
-| Model | Production entrypoint | Aspire publish API | Best for |
-| -------------------------------------- | -------------------------- | --------------------------- | --------------------------------------------------------------------------------- |
-| Backend serves frontend | API or web server | `PublishWithContainerFiles` | Apps where one service serves both the API and the frontend |
-| Reverse proxy serves frontend | Reverse proxy | `PublishWithStaticFiles` | Apps that want a gateway or BFF in front of APIs and static frontend assets |
-| Static frontend calls backend directly | Static site + separate API | Custom / less integrated | Apps that intentionally keep frontend hosting separate and can manage CORS/config |
-
-
-## Model 1: Backend serves the built frontend
+## Static frontend served by a backend
-Use this model when your backend, API, or server is responsible for serving static frontend files in production from `wwwroot`, `static`, or a similar directory.
+Use this shape when your backend, API, or web server is responsible for serving static frontend files in production from `wwwroot`, `static`, or a similar directory.
-This model only works if that backend or server can actually serve the built frontend assets. In other words, the deployed application service must be both the API host and the static file host for the frontend.
-
-
+This shape only works if the deployed application service can actually serve those files. `PublishWithContainerFiles(...)` copies the built frontend assets into the destination container; it does not configure the destination app to serve them.
```mermaid
flowchart LR
@@ -81,7 +60,8 @@ flowchart LR
-```csharp title="C# — AppHost.cs"
+
+```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
var app = builder
@@ -98,9 +78,11 @@ app.PublishWithContainerFiles(frontend, "./static");
builder.Build().Run();
```
+
-```typescript title="TypeScript — apphost.ts" twoslash
+
+```typescript title="apphost.ts" twoslash
import { createBuilder } from './.modules/aspire.js';
const builder = await createBuilder();
@@ -119,41 +101,34 @@ await app.publishWithContainerFiles(frontend, './static');
await builder.build().run();
```
+
+
### How it works
1. `AddViteApp` runs the Vite dev server during `aspire run`.
2. During publish, Aspire builds the frontend and extracts its production output.
-3. `PublishWithContainerFiles` copies those files into the Node app container.
-4. The Node app becomes the deployed HTTP endpoint and serves the frontend files.
-
-### Why Aspire supports this model
-
-This model keeps the production topology simple. The frontend build output becomes part of the same deployable unit as the backend, so one service owns the application surface, static files, and API behavior together.
+3. `PublishWithContainerFiles` copies those files into the backend or web server container.
+4. The backend or web server becomes the deployed HTTP endpoint and serves the frontend files.
-It also maps well to frameworks that already know how to serve static files from `wwwroot`, `static`, or a similar folder. Instead of introducing an extra gateway or frontend-serving container, Aspire lets the backend stay responsible for the final HTTP response.
-
-### When to use this model
+### When to use this shape
- Your backend already serves static files, or you are willing to make it do so.
- You want one deployed service to host both the API and the frontend.
-- You want the same resource to own routing, auth, and static asset hosting.
+- You want the same resource to own routing, auth, headers, fallback behavior, and static asset hosting.
### Implications
-- Your backend container gets larger because it now contains both backend code and frontend assets.
+- The backend container gets larger because it contains both backend code and frontend assets.
- Frontend and backend are deployed together, which is convenient when they change together but less flexible if you want to scale or release them independently.
- Authentication, caching, headers, and fallback routing are handled where the backend serves the files.
- This usually gives the simplest mental model: one deployed service, one public endpoint, one place to troubleshoot.
-- This is the default pattern Aspire is steering you toward for Vite frontends unless you intentionally introduce a gateway or BFF to own the public surface.
-
-## Model 2: Reverse proxy serves the built frontend
-Use this model when a reverse proxy should be the public entrypoint for your app, either as a gateway or as a backend-for-frontend (BFF).
+## Static frontend served by a gateway or BFF
-This model works well when you want a dedicated gateway or BFF in front of the rest of the application. In Aspire, YARP is the built-in example, but the same topology also applies when you use another reverse proxy such as Nginx or Caddy.
+Use this shape when a reverse proxy should be the public entrypoint for your app, either as a gateway or as a BFF. In Aspire, YARP is the built-in example, but the same topology applies when you use another reverse proxy such as Nginx or Caddy.
```mermaid
flowchart LR
@@ -164,7 +139,8 @@ flowchart LR
-```csharp title="C# — AppHost.cs"
+
+```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
var api = builder
@@ -185,9 +161,11 @@ builder
builder.Build().Run();
```
+
-```typescript title="TypeScript — apphost.ts" twoslash
+
+```typescript title="apphost.ts" twoslash
import { createBuilder } from './.modules/aspire.js';
const builder = await createBuilder();
@@ -205,29 +183,27 @@ await builder
.publishWithStaticFiles(frontend)
.withConfiguration(async (yarp) => {
(
- await yarp.addRouteFromEndpoint('/api/{**catch-all}', apiEndpoint)
+ await yarp.addRoute('/api/{**catch-all}', apiEndpoint)
).withTransformPathRemovePrefix('/api');
});
await builder.build().run();
```
+
+
-
+`AddViteApp` is still fine in this shape, but the Vite development server endpoint is not used at publish time.
### Dev-only gateway wiring
-If your gateway or BFF needs to know about the frontend dev server during local
-development, gate that wiring to run mode only:
+If your gateway or BFF needs to know about the frontend dev server during local development, gate that wiring to run mode only:
-```csharp title="C# — AppHost.cs"
+
+```csharp title="AppHost.cs"
var frontend = builder
.AddViteApp("frontend", "./frontend");
@@ -242,9 +218,11 @@ if (builder.ExecutionContext.IsRunMode)
gateway.WithEnvironment("FRONTEND_DEV_URL", frontend.GetEndpoint("http"));
}
```
+
-```typescript title="TypeScript — apphost.ts"
+
+```typescript title="apphost.ts"
const frontend = await builder.addViteApp('frontend', './frontend');
const gateway = await builder
@@ -258,7 +236,9 @@ if (await builder.executionContext.isRunMode()) {
await gateway.withEnvironment('FRONTEND_DEV_URL', frontendDevEndpoint);
}
```
+
+
+## Export value catalogs
+
+Use `[AspireValue]` to export immutable predefined values from your integration into guest SDKs as typed catalog objects. This is useful when your integration ships well-known constants or configuration presets—such as a list of supported model names or region identifiers—that polyglot AppHost authors should be able to reference without reconstructing them manually.
+
+`[AspireValue]` is an experimental API protected by the `ASPIREATS001` diagnostic. Suppress it in your project file along with the other ATS attributes:
+
+```xml title="XML — MyIntegration.csproj"
+
+ $(NoWarn);ASPIREATS001
+
+```
+
+### Define a value catalog
+
+Apply `[AspireValue]` to `static readonly` fields or `static` properties on your type. The required `catalogName` argument sets the root name of the generated catalog in guest SDKs:
+
+```csharp title="C# — MyModels.cs"
+using Aspire.Hosting;
+
+[AspireDto]
+public sealed class MyModel
+{
+ public required string Name { get; init; }
+ public required string Version { get; init; }
+}
+
+public static class MyModels
+{
+ public static class FastModels
+ {
+ /// A fast, lightweight model for simple tasks.
+ [AspireValue("MyModels")]
+ public static readonly MyModel Lite = new() { Name = "my-model-lite", Version = "1" };
+
+ /// A fast model with extended context support.
+ [AspireValue("MyModels")]
+ public static readonly MyModel LiteLong = new() { Name = "my-model-lite-long", Version = "1" };
+ }
+
+ public static class PowerModels
+ {
+ /// A high-capability model for complex tasks.
+ [AspireValue("MyModels")]
+ public static readonly MyModel Pro = new() { Name = "my-model-pro", Version = "2" };
+ }
+}
+```
+
+The scanner snaps the values at scan time by serializing each field or property to JSON. It also reads XML doc comments to include descriptions in the generated catalog.
+
+### Use catalog values in guest SDKs
+
+After generating the SDK (for example, with `aspire run`), the catalog is available as a nested object in each supported language. The nesting mirrors the static class hierarchy of the C# source:
+
+```typescript title="TypeScript — apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+import { MyModels } from './.modules/my-integration.js';
+
+const builder = await createBuilder();
+
+// Use predefined catalog values directly
+await builder
+ .addMyService('svc', { model: MyModels.FastModels.Lite })
+ .build()
+ .run();
+```
+
+```python title="Python — apphost.py"
+from modules.my_integration import MyModels
+
+builder = await create_builder()
+
+await (
+ builder
+ .add_my_service("svc", model=MyModels.FastModels.Lite)
+ .build()
+ .run()
+)
+```
+
+### Override the exported name
+
+By default, the exported name matches the field or property name. Use the `Name` property to override it:
+
+```csharp title="C# — Override exported name"
+[AspireValue("MyModels", Name = "lite")]
+public static readonly MyModel Lite = new() { Name = "my-model-lite", Version = "1" };
+```
+
+### Value catalog constraints
+
+- Exported fields and properties must be **static**.
+- The value must be serializable to JSON. Avoid types that hold runtime handles, delegates, or other non-serializable state.
+- Handles (`IResourceBuilder`, resource instances) are **not** valid as exported values.
+- Values are snapped **once** at scan time. They are emitted as compile-time constants in generated SDKs and are not refreshed at runtime.
+
+:::note
+`[AspireValue]` exports predefined constants into the generated SDK. It is not a mechanism for sharing live runtime state. For runtime interactions, use `[AspireExport]` methods instead.
+:::
+
## Handle incompatible overloads
Some C# overloads use types that can't be represented in TypeScript (e.g., `Action` delegates with non-serializable contexts, interpolated string handlers, or C#-specific types). Mark these with `[AspireExportIgnore]`:
@@ -215,7 +479,13 @@ When a parameter accepts multiple types, use `[AspireUnion]` to declare the vali
public static IResourceBuilder WithEnvironment(
this IResourceBuilder builder,
string name,
- [AspireUnion(typeof(string), typeof(ReferenceExpression), typeof(EndpointReference))]
+ [AspireUnion(
+ typeof(string),
+ typeof(ReferenceExpression),
+ typeof(EndpointReference),
+ typeof(IResourceBuilder),
+ typeof(IResourceBuilder),
+ typeof(IExpressionValue))]
object value)
where T : IResourceWithEnvironment
{
@@ -241,6 +511,9 @@ The `Aspire.Hosting.Integration.Analyzers` package reports these diagnostics:
| ASPIREEXPORT008 | Warning | Public extension method on exported type missing `[AspireExport]` or `[AspireExportIgnore]` |
| ASPIREEXPORT009 | Warning | Export name may collide with other integrations |
| ASPIREEXPORT010 | Warning | Synchronous callback invoked inline — may deadlock in multi-language app hosts |
+| ASPIREEXPORT011 | Warning | Explicit export ID matches the convention-derived name |
+| ASPIREEXPORT012 | Warning | Callback context type missing `[AspireExport]` |
+| ASPIREEXPORT013 | Warning | Duplicate polyglot capability ID across exports in the same assembly |
A clean build with zero analyzer warnings means your integration is ready for multi-language use.
@@ -255,7 +528,7 @@ You can test your integration locally without publishing to a NuGet feed. In you
"language": "typescript/nodejs"
},
"packages": {
- "Aspire.Hosting.Redis": "13.2.0",
+ "Aspire.Hosting.Redis": "13.3.0",
"MyCompany.Hosting.MyDatabase": "../src/MyCompany.Hosting.MyDatabase/MyCompany.Hosting.MyDatabase.csproj"
}
}
@@ -325,10 +598,11 @@ The following types are ATS-compatible and can be used in exported method signat
| **Enums** | Any enum type |
| **Handles** | `IResourceBuilder`, `IDistributedApplicationBuilder`, resource types marked with `[AspireExport]` |
| **DTOs** | Classes/structs marked with `[AspireDto]` |
+| **Exported values** | Static fields/properties marked with `[AspireValue]` (emitted as catalog constants in guest SDKs) |
| **Collections** | `List`, `Dictionary`, arrays — where `T` is ATS-compatible |
| **Delegates** | `Action`, `Func`, and other delegate types (use `RunSyncOnBackgroundThread = true` for synchronous delegates invoked inline) |
| **Services** | `ILogger`, `IServiceProvider`, `IConfiguration` (already exported by the core framework) |
-| **Special** | `ParameterResource`, `ReferenceExpression`, `EndpointReference`, `CancellationToken` |
+| **Special** | `ParameterResource`, `ReferenceExpression`, `EndpointReference`, `IExpressionValue`, `CancellationToken` |
| **Nullable** | Any of the above as nullable (`T?`) |
Types that are **not** ATS-compatible include: interpolated string handlers and custom complex types without `[AspireExport]` or `[AspireDto]`.
diff --git a/src/frontend/src/content/docs/fr/index.mdx b/src/frontend/src/content/docs/fr/index.mdx
index 762175023..553b7e849 100644
--- a/src/frontend/src/content/docs/fr/index.mdx
+++ b/src/frontend/src/content/docs/fr/index.mdx
@@ -11,7 +11,7 @@ prev: false
next: false
banner:
content: |
- 🚀 Aspire 13.2 est disponible ! — Découvrez les nouveautés d'Aspire 13.2.
+ 🚀 Aspire 13.3 est disponible ! — Découvrez les nouveautés d'Aspire 13.3.
hero:
tagline: Votre stack, simplifiée.
Orchestrez vos frontends, APIs, conteneurs et bases de données sans effort — sans réécriture, sans limites. Étendez Aspire pour propulser n'importe quel projet.
image:
diff --git a/src/frontend/src/content/docs/fundamentals/custom-resource-commands.mdx b/src/frontend/src/content/docs/fundamentals/custom-resource-commands.mdx
index c8b1b56f6..3f22f5d34 100644
--- a/src/frontend/src/content/docs/fundamentals/custom-resource-commands.mdx
+++ b/src/frontend/src/content/docs/fundamentals/custom-resource-commands.mdx
@@ -3,7 +3,7 @@ title: Custom resource commands
description: Learn how to create custom resource commands in Aspire.
---
-import { Aside } from '@astrojs/starlight/components';
+import { Aside, Tabs, TabItem } from '@astrojs/starlight/components';
import { Image } from 'astro:assets';
import customClearCacheCommand from '@assets/fundamentals/custom-clear-cache-command.png';
import customClearCacheCommandSucceeded from '@assets/fundamentals/custom-clear-cache-command-succeeded.png';
@@ -18,7 +18,12 @@ Each resource in the Aspire app model is represented as an `IResource` and when
## Add custom commands to a resource
-Start by creating a new Aspire Starter App from the available templates. After creating this solution, add a new class named _RedisResourceBuilderExtensions.cs_ to the AppHost project. Replace the contents of the file with the following code:
+Start with an Aspire AppHost project. The walkthrough below registers a `clear-cache` command on a Redis resource. The C# version uses an extension method that wraps the registration; the TypeScript version registers the command inline against a Node service that exposes an admin endpoint. Both flows produce the same dashboard/CLI experience.
+
+
+
+
+Add a new class named _RedisResourceBuilderExtensions.cs_ to the AppHost project and replace its contents with the following code:
```csharp title="RedisResourceBuilderExtensions.cs"
using Microsoft.Extensions.DependencyInjection;
@@ -84,30 +89,90 @@ internal static class RedisResourceBuilderExtensions
The preceding code:
-- Shares the `Aspire.Hosting` namespace so that it's visible to the AppHost project.
-- Is a `static class` so that it can contain extension methods.
-- It defines a single extension method named `WithClearCommand`, extending the `IResourceBuilder` interface.
-- The `WithClearCommand` method registers a command named `clear-cache` that clears the cache of the Redis resource.
-- The `WithClearCommand` method returns the `IResourceBuilder` instance to allow chaining.
+- Shares the `Aspire.Hosting` namespace so that the extension is visible from the AppHost.
+- Is a `static class` containing extension methods on `IResourceBuilder`.
+- Defines a single extension method named `WithClearCommand` that registers a `clear-cache` command on the Redis resource, plus the `executeCommand` and `updateState` callbacks the dashboard uses to run the command and decide when it's enabled.
+- Returns the `IResourceBuilder` to keep the call site chainable.
+
+
+
+
+In a TypeScript AppHost, register the command inline on the resource builder. Because TypeScript doesn't have extension methods, the closure captures any state the command needs:
+
+```typescript title="apphost.ts"
+import {
+ createBuilder,
+ type ExecuteCommandContext,
+ type ExecuteCommandResult,
+} from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const cache = await builder
+ .addNodeApp("cache", "./cache-service", "src/server.ts")
+ .withHttpEndpoint();
+
+await cache.withCommand(
+ "clear-cache",
+ "Clear Cache",
+ async (_context: ExecuteCommandContext): Promise => {
+ const endpoint = await cache.getEndpoint("http");
+ const url = await endpoint.url.get();
+ const res = await fetch(`${url}/admin/cache/clear`, { method: "POST" });
+
+ if (!res.ok) {
+ return {
+ success: false,
+ message: `Cache service returned ${res.status} ${res.statusText}.`,
+ };
+ }
+
+ return { success: true, message: "Cache cleared." };
+ },
+ {
+ commandOptions: {
+ description: "Drops all entries in the cache service.",
+ confirmationMessage: "Clear all cached entries?",
+ iconName: "AnimalRabbitOff",
+ },
+ });
+
+await builder.build().run();
+```
+
+The preceding code:
+
+- Adds a Node app named `cache` and registers a `clear-cache` command on it.
+- Implements the `executeCommand` callback as an async arrow that calls the resource's HTTP endpoint to perform the actual cache invalidation. The pattern works for any resource that exposes an admin operation over HTTP; for resources whose clients you'd rather use directly (databases, message brokers, …), call those clients from the same callback.
+- Passes a fourth argument with `{ commandOptions: { ... } }` to set the dashboard description, confirmation prompt, and icon.
+
+
+
The `WithCommand` API adds the appropriate annotations to the resource, which are consumed in the [Aspire dashboard](/dashboard/overview/). The dashboard uses these annotations to render the command in the UI. Before getting too far into those details, let's ensure that you first understand the parameters of the `WithCommand` method:
- `name`: The name of the command to invoke.
- `displayName`: The name of the command to display in the dashboard.
-- `executeCommand`: The `Func>` to run when the command is invoked, which is where the command logic is implemented.
-- `updateState`: The `Func` callback is invoked to determine the "enabled" state of the command, which is used to enable or disable the command in the dashboard.
+- `executeCommand`: The callback that runs when the command is invoked. In C# the type is `Func>`; in TypeScript it's `(context: ExecuteCommandContext) => Promise`.
+- `updateState`: A callback that decides the command's enabled state in the dashboard. In C# it's `Func` and supplied via `CommandOptions.UpdateState`; in TypeScript it's the `updateState` field on `CommandOptions` (currently typed as `any`).
- `iconName`: The name of the icon to display in the dashboard. The icon is optional, but when you do provide it, it should be a valid [Fluent UI Blazor icon name](https://www.fluentui-blazor.net/Icon#explorer).
- `iconVariant`: The variant of the icon to display in the dashboard, valid options are `Regular` (default) or `Filled`.
## Execute command logic
-The `executeCommand` delegate is where the command logic is implemented. This parameter is defined as a `Func>`. The `ExecuteCommandContext` provides the following properties:
+The `executeCommand` callback is where the command logic is implemented. The `ExecuteCommandContext` it receives gives you access to the service provider, the resource being acted on, and a cancellation token.
+
+
+
-- `ExecuteCommandContext.ServiceProvider`: The `IServiceProvider` instance that's used to resolve services.
-- `ExecuteCommandContext.ResourceName`: The name of the resource instance that the command is being executed on.
-- `ExecuteCommandContext.CancellationToken`: The `CancellationToken` that's used to cancel the command execution.
+In C# the parameter is typed as `Func>`. `ExecuteCommandContext` exposes:
-In the preceding example, the `executeCommand` delegate is implemented as an `async` method that clears the cache of the Redis resource. It delegates out to a private class-scoped function named `OnRunClearCacheCommandAsync` to perform the actual cache clearing. Consider the following code:
+- `ServiceProvider` _(`IServiceProvider`)_ — used to resolve services such as loggers or `ResourceCommandService`.
+- `ResourceName` _(`string`)_ — the name of the resource instance that the command is being executed on.
+- `CancellationToken` _(`CancellationToken`)_ — used to cancel command execution.
+- `Logger` _(`ILogger`)_ — used to write log messages directly to the resource's logs. These logs can be viewed in the [Aspire dashboard](/dashboard/explore/#console-logs-page) or with the `aspire logs ` command.
+
+The earlier example delegates to a private static method named `OnRunClearCacheCommandAsync` to clear the cache:
```csharp title="RedisResourceBuilderExtensions.cs"
private static async Task OnRunClearCacheCommandAsync(
@@ -136,14 +201,62 @@ The preceding code:
- Executes the `FLUSHALL` command to clear the cache.
- Returns a `CommandResults.Success()` instance to indicate that the command was successful.
+
+
+
+In TypeScript the callback signature is `(context: ExecuteCommandContext) => Promise`. `ExecuteCommandContext` exposes the same surface in camelCase, plus a `logger` accessor as a shortcut:
+
+- `serviceProvider` — async accessor for the dependency-injection container.
+- `resourceName` — async accessor for the resource instance name.
+- `cancellationToken` — async accessor returning a `CancellationToken` that you can `await` and forward to APIs that accept an `AbortSignal`.
+- `logger` — async accessor for the resource logger; equivalent to resolving a logger from `serviceProvider`.
+
+The TypeScript callback is typically defined inline as an async arrow function. The example below pulls the resource's HTTP endpoint and forwards the cancellation token to `fetch`:
+
+```typescript title="apphost.ts"
+async (context: ExecuteCommandContext): Promise => {
+ const endpoint = await cache.getEndpoint("http");
+ const url = await endpoint.url.get();
+ const cancellation = await context.cancellationToken.get();
+
+ const res = await fetch(`${url}/admin/cache/clear`, {
+ method: "POST",
+ signal: cancellation as unknown as AbortSignal,
+ });
+
+ if (!res.ok) {
+ return {
+ success: false,
+ message: `Cache service returned ${res.status} ${res.statusText}.`,
+ };
+ }
+
+ return { success: true, message: "Cache cleared." };
+}
+```
+
+The preceding code:
+
+- Reads the resource's HTTP endpoint URL.
+- Calls the resource's admin endpoint with the cancellation token forwarded as an abort signal so the request stops when the dashboard or CLI cancels the command.
+- Returns `{ success: false, message: ... }` if the HTTP call fails, otherwise `{ success: true, message: "Cache cleared." }`.
+
+
+
+
## Update command state logic
-The `updateState` delegate is where the command state is determined. This parameter is defined as a `Func`. The `UpdateCommandStateContext` provides the following properties:
+The `updateState` callback decides whether a command is enabled in the dashboard. The dashboard invokes it whenever the resource's state changes — typical use cases are gating destructive commands behind a healthy resource, or only enabling a command after a startup probe has succeeded.
+
+
+
-- `UpdateCommandStateContext.ServiceProvider`: The `IServiceProvider` instance that's used to resolve services.
-- `UpdateCommandStateContext.ResourceSnapshot`: The snapshot of the resource instance that the command is being executed on.
+In C# the parameter is typed as `Func`. `UpdateCommandStateContext` exposes:
-The immutable snapshot is an instance of `CustomResourceSnapshot`, which exposes all sorts of valuable details about the resource instance. Consider the following code:
+- `ServiceProvider` _(`IServiceProvider`)_ — used to resolve services.
+- `ResourceSnapshot` _(`CustomResourceSnapshot`)_ — an immutable snapshot of the resource instance, including health status, lifecycle state, properties, and URLs.
+
+The earlier example gates the `clear-cache` command on the Redis resource being healthy:
```csharp title="RedisResourceBuilderExtensions.cs"
private static ResourceCommandState OnUpdateResourceState(
@@ -170,9 +283,38 @@ The preceding code:
- Logs the resource snapshot details.
- Returns `ResourceCommandState.Enabled` if the resource is healthy; otherwise, it returns `ResourceCommandState.Disabled`.
+
+
+
+
+
+In TypeScript the `updateState` field on `CommandOptions` is typed loosely (`any`), and the SDK does not yet export a `ResourceCommandState` enum. The simplest path for TypeScript AppHosts today is to omit `updateState` entirely — commands default to enabled — and only register the command after the resource is reachable:
+
+```typescript title="apphost.ts"
+await cache.withCommand(
+ "clear-cache",
+ "Clear Cache",
+ executeClearCache,
+ {
+ commandOptions: {
+ iconName: "AnimalRabbitOff",
+ },
+ });
+```
+
+When you need lifecycle-aware gating in a TypeScript AppHost, drive it from outside the callback by registering the command only after the resource exposes the data your command depends on (for example, after subscribing to a connection-string or endpoint event).
+
+
+
+
## Test the custom command
-To test the custom command, update your AppHost project's _AppHost.cs_ file to include the following code:
+To test the custom command, update your AppHost to wire up the resource and the command:
+
+
+
```csharp title="AppHost.cs" {4}
var builder = DistributedApplication.CreateBuilder(args);
@@ -192,7 +334,66 @@ builder.AddProject("webfrontend")
builder.Build().Run();
```
-The preceding code calls the `WithClearCommand` extension method to add the custom command to the Redis resource. Run the app and navigate to the Aspire dashboard. You should see the custom command listed under the Redis resource. On the **Resources** page of the dashboard, select the ellipsis button under the **Actions** column:
+The preceding code calls the `WithClearCommand` extension method to add the custom command to the Redis resource.
+
+
+
+
+```typescript title="apphost.ts" {5-15}
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const cache = await builder.addNodeApp("cache", "../cache/server.js");
+await cache.withHttpEndpoint();
+await cache.withCommand(
+ "clear-cache",
+ "Clear Cache",
+ async (context) => {
+ const endpoint = await cache.getEndpoint("http");
+ const url = await endpoint.url.get();
+ const cancellation = await context.cancellationToken.get();
+
+ const res = await fetch(`${url}/admin/cache/clear`, {
+ method: "POST",
+ signal: cancellation as unknown as AbortSignal,
+ });
+
+ if (!res.ok) {
+ return {
+ success: false,
+ message: `Cache service returned ${res.status} ${res.statusText}.`,
+ };
+ }
+
+ return { success: true, message: "Cache cleared." };
+ },
+ {
+ commandOptions: {
+ iconName: "AnimalRabbitOff",
+ description: "Clears the application cache.",
+ confirmationMessage: "Are you sure you want to clear the cache?",
+ },
+ });
+
+const apiService = await builder.addProject("apiservice", "../ApiService/ApiService.csproj");
+
+const web = await builder.addProject("webfrontend", "../Web/Web.csproj");
+await web.withExternalHttpEndpoints();
+await web.withReference(cache);
+await web.waitFor(cache);
+await web.withReference(apiService);
+await web.waitFor(apiService);
+
+await builder.build().run();
+```
+
+The preceding code registers the cache service as a Node app with an HTTP endpoint and attaches the `clear-cache` command directly to it.
+
+
+
+
+Run the app and navigate to the Aspire dashboard. You should see the custom command listed under the cache resource. On the **Resources** page of the dashboard, select the ellipsis button under the **Actions** column:
+Programmatic command execution via `ResourceCommandService` is currently a **C# AppHost feature**. The TypeScript SDK can register custom commands but does not yet expose a service for invoking commands from inside other commands. The next three subsections — _Execute commands from a custom command_, _Execute a command by resource ID_, and _Execute commands on resources with replicas_ — are therefore C#-only. The [_Handle command execution results_](#handle-command-execution-results) subsection that follows applies to commands written in either language.
+
+
In addition to executing commands through the [Aspire dashboard](/dashboard/overview/), you can also execute commands programmatically using the `ResourceCommandService`. This service is useful when you need to:
- Execute commands from within your application code
@@ -225,7 +430,6 @@ The following example shows how to create a custom command that executes other c
```csharp title="AppHost.cs"
using Aspire.Hosting.ApplicationModel;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
var builder = DistributedApplication.CreateBuilder(args);
@@ -244,10 +448,9 @@ var api = builder.AddProject("api")
.WithReference(database)
.WithCommand("reset-all", "Reset Everything", async (context, ct) =>
{
- var logger = context.ServiceProvider.GetRequiredService>();
var commandService = context.ServiceProvider.GetRequiredService();
- logger.LogInformation("Starting full system reset...");
+ context.Logger.LogInformation("Starting full system reset...");
var clearResult = await commandService.ExecuteCommandAsync(
resource: cache.Resource,
@@ -264,7 +467,7 @@ var api = builder.AddProject("api")
return CommandResults.Failure("System reset failed");
}
- logger.LogInformation("System reset completed successfully");
+ context.Logger.LogInformation("System reset completed successfully");
return CommandResults.Success();
});
@@ -273,7 +476,8 @@ builder.Build().Run();
In this example:
-- The `context` parameter provides access to `ServiceProvider`
+- The `context` parameter provides access to `ServiceProvider` and `Logger`
+- `context.Logger` writes log messages directly to the resource's log stream in the Aspire dashboard — no need to resolve a logger from the service provider
- The `ResourceCommandService` is retrieved from the service provider
- Commands are executed on resource instances using `cache.Resource` and `database.Resource`
- Each command result is checked for success before proceeding
@@ -298,7 +502,7 @@ else if (result.Canceled)
}
else
{
- logger.LogError("Command failed: {ErrorMessage}", result.ErrorMessage);
+ logger.LogError("Command failed: {Message}", result.Message);
}
```
@@ -326,19 +530,319 @@ The behavior when executing on multiple replicas:
### Handle command execution results
-The `ExecuteCommandResult` class provides information about the command execution:
+The `ExecuteCommandResult` type carries the outcome of a command. It exposes the same conceptual fields in both languages:
-- **`Success`**: A boolean indicating whether the command was successful.
-- **`ErrorMessage`**: An optional string property with an error message if the command was unsuccessful.
-- **`Canceled`**: A boolean indicating whether the command was canceled by the user.
+- **`Success` / `success`** _(boolean)_ — Whether the command completed successfully.
+- **`Canceled` / `canceled`** _(boolean)_ — Whether the command was canceled by the user.
+- **`Message` / `message`** _(string?)_ — A human-readable message that surfaces in the dashboard [notification center](/dashboard/explore/#notification-center) and CLI output. Used for both success messages and error messages.
+- **`Data` / `data`** _(`CommandResultData?`)_ — Optional structured output (plain text, JSON, or Markdown). See [Return structured output from a command](#return-structured-output-from-a-command).
-You can use these properties to handle different execution outcomes in your application logic. The `CommandResults` helper class provides factory methods to create result instances:
+
+
+
+
+
+The `CommandResults` helper class provides factory methods for the common shapes:
```csharp
-// In your command implementation
+// Success with no payload
return CommandResults.Success();
-// or
+
+// Failure with a status message
return CommandResults.Failure("Error occurred during execution");
-// or
+
+// Cancellation (typically when the user backs out of a confirmation prompt)
return CommandResults.Canceled();
+
+// Failure derived from an exception (uses Exception.Message)
+return CommandResults.Failure(ex);
+```
+
+
+
+
+In TypeScript there is no helper class — return a plain object that conforms to `ExecuteCommandResult`:
+
+```typescript
+// Success with no payload
+return { success: true };
+
+// Failure with a status message
+return { success: false, message: "Error occurred during execution" };
+
+// Cancellation (typically when the user backs out of a confirmation prompt)
+return { canceled: true };
+
+// Failure derived from a caught exception
+return { success: false, message: err instanceof Error ? err.message : String(err) };
+```
+
+
+
+
+## Return structured output from a command
+
+Resource commands can return a structured payload — plain text, JSON, or Markdown — alongside the success/failure signal. The payload flows automatically through the entire Aspire pipeline:
+
+- **Dashboard**: The success notification gains a **View response** action that opens a text visualizer dialog with the payload. `Json` results lock the visualizer to JSON syntax highlighting; `Markdown` results lock it to Markdown rendering. Set `DisplayImmediately` to open the dialog as soon as the command completes (no click required).
+- **CLI**: Status messages route to `stderr` and the payload routes to `stdout`, so the output is safe to pipe to tools such as `jq`:
+
+ ```bash title="Terminal"
+ aspire resource cache issue-access-token | jq -r .token
+ ```
+
+- **MCP tools**: The payload is appended to the tool response as a second `TextContentBlock` after the status message.
+
+### Return text or JSON
+
+A common scenario is issuing an **access token** from a custom command — developers can copy it straight from the dashboard or pipe it into a `Bearer` header from the CLI. In `Text` mode, the result is just the token; in `Json` mode, the result includes the expiry and scopes.
+
+Use the `CommandResults.Success(message, result, resultFormat)` overload to attach a payload to a successful result. The `message` is the notification center/CLI status; `result` is the payload string; `resultFormat` controls how the dashboard visualizer interprets it.
+
+
+
+
+```csharp title="C# — AppHost.cs"
+using System.Security.Cryptography;
+using Aspire.Hosting.ApplicationModel;
+
+var builder = DistributedApplication.CreateBuilder(args);
+
+builder.AddProject("myservice")
+ .WithCommand(
+ name: "issue-access-token",
+ displayName: "Issue Access Token",
+ executeCommand: context =>
+ {
+ var token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
+
+ return Task.FromResult(CommandResults.Success(
+ message: "Access token issued.",
+ result: token,
+ resultFormat: CommandResultFormat.Text));
+ });
+
+builder.Build().Run();
+```
+
+To return JSON with the expiry and scopes, switch the format to `CommandResultFormat.Json`:
+
+```csharp title="C# — AppHost.cs"
+using System.Security.Cryptography;
+using System.Text.Json;
+using Aspire.Hosting.ApplicationModel;
+
+var builder = DistributedApplication.CreateBuilder(args);
+
+builder.AddProject("myservice")
+ .WithCommand(
+ name: "issue-access-token",
+ displayName: "Issue Access Token",
+ executeCommand: context =>
+ {
+ var token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
+ var json = JsonSerializer.Serialize(new
+ {
+ token,
+ expiresAt = DateTimeOffset.UtcNow.AddHours(1),
+ scopes = new[] { "read", "write" },
+ });
+
+ return Task.FromResult(CommandResults.Success(
+ message: "Access token issued.",
+ result: json,
+ resultFormat: CommandResultFormat.Json));
+ });
+
+builder.Build().Run();
+```
+
+
+
+
+In TypeScript, return an `ExecuteCommandResult` object literal with a `data` property of type `CommandResultData`:
+
+```typescript title="TypeScript — apphost.ts"
+import {
+ createBuilder,
+ CommandResultFormat,
+ type ExecuteCommandContext,
+ type ExecuteCommandResult,
+} from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+await builder
+ .addNodeApp("myservice", "./myservice", "src/server.ts")
+ .withCommand("issue-access-token", "Issue Access Token",
+ async (_context: ExecuteCommandContext): Promise => {
+ const token = crypto.randomUUID().replace(/-/g, "");
+
+ return {
+ success: true,
+ message: "Access token issued.",
+ data: {
+ value: token,
+ format: CommandResultFormat.Text,
+ },
+ };
+ });
+
+await builder.build().run();
+```
+
+To return JSON with the expiry and scopes, switch the format to `CommandResultFormat.Json`:
+
+```typescript title="TypeScript — apphost.ts"
+import {
+ createBuilder,
+ CommandResultFormat,
+ type ExecuteCommandContext,
+ type ExecuteCommandResult,
+} from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+await builder
+ .addNodeApp("myservice", "./myservice", "src/server.ts")
+ .withCommand("issue-access-token", "Issue Access Token",
+ async (_context: ExecuteCommandContext): Promise => {
+ const token = crypto.randomUUID().replace(/-/g, "");
+ const json = JSON.stringify({
+ token,
+ expiresAt: new Date(Date.now() + 3_600_000).toISOString(),
+ scopes: ["read", "write"],
+ });
+
+ return {
+ success: true,
+ message: "Access token issued.",
+ data: {
+ value: json,
+ format: CommandResultFormat.Json,
+ },
+ };
+ });
+
+await builder.build().run();
+```
+
+
+
+
+### Choose a result format
+
+`CommandResultFormat` has three values:
+
+| Value | Dashboard behavior |
+|------------|--------------------|
+| `Text` | Plain text. Opens in the text visualizer dialog with no fixed format — the user can switch to a different syntax for inspection. |
+| `Json` | JSON. The visualizer locks to JSON mode for syntax highlighting and pretty-printing. |
+| `Markdown` | Markdown. The visualizer locks to Markdown rendering. |
+
+Omit `Data` (return `CommandResults.Success()` or `{ success: true }`) when the command has no payload to display.
+
+### Return data on a failure
+
+The matching `CommandResults.Failure(errorMessage, result, resultFormat)` overload attaches the same payload shape to a failed result, which is useful for surfacing diagnostic detail that the user (or a downstream tool) can inspect:
+
+
+
+
+```csharp title="C# — AppHost.cs"
+return CommandResults.Failure(
+ errorMessage: "Migration failed.",
+ result: errorJson,
+ resultFormat: CommandResultFormat.Json);
+```
+
+
+
+
+```typescript title="TypeScript — apphost.ts"
+return {
+ success: false,
+ message: "Migration failed.",
+ data: {
+ value: errorJson,
+ format: CommandResultFormat.Json,
+ },
+};
+```
+
+
+
+
+### Auto-open the result dialog in the dashboard
+
+By default, the dashboard surfaces the payload as a **View response** action on the notification in the [notification center](/dashboard/explore/#notification-center). Set `DisplayImmediately` on `CommandResultData` to open the visualizer the moment the command completes — useful for short commands where the response _is_ the point:
+
+
+
+
+```csharp title="C# — AppHost.cs"
+return CommandResults.Success(
+ message: "Access token issued.",
+ value: new CommandResultData
+ {
+ Value = json,
+ Format = CommandResultFormat.Json,
+ DisplayImmediately = true,
+ });
+```
+
+
+
+
+```typescript title="TypeScript — apphost.ts"
+return {
+ success: true,
+ message: "Access token issued.",
+ data: {
+ value: json,
+ format: CommandResultFormat.Json,
+ displayImmediately: true,
+ },
+};
+```
+
+
+
+
+### Replica aggregation
+
+When a resource has multiple replicas, the command runs on all instances in parallel. If more than one replica returns a payload, only the first successful payload is propagated to the caller; the others are discarded.
+
+### Invoke commands from the CLI
+
+Any command registered with `WithCommand` can be invoked from the terminal — including the built-in `start`, `stop`, and `restart` commands — using the `aspire resource` command:
+
+```bash title="Terminal"
+aspire resource [--apphost ]
+```
+
+The CLI splits status output from payload output so the result is friendly to scripting:
+
+- The `Message` and any progress text is written to **stderr**.
+- The `Data.Value` (when present) is written to **stdout** verbatim. `Json` and `Text` payloads are emitted as-is; `Markdown` is rendered with terminal-styled headings, lists, and code blocks.
+- The exit code is `0` on success and non-zero on failure or cancellation.
+
+Combined, this means you can pipe the structured payload directly into `jq`, redirect it to a file, or branch on the exit code:
+
+```bash title="Terminal — Pipe a JSON payload through jq"
+$ aspire resource cache issue-access-token | jq -r .token
+ey7WqGxk2vL...
+```
+
+The status message `Access token issued.` was written to stderr, so stdout stays clean for the pipe.
+
+```bash title="Terminal — Use the exit code in a script"
+if aspire resource db migrate > migrations.json; then
+ echo "Applied $(jq length migrations.json) migrations."
+else
+ echo "Migration failed — see error above."
+ exit 1
+fi
```
diff --git a/src/frontend/src/content/docs/fundamentals/http-commands.mdx b/src/frontend/src/content/docs/fundamentals/http-commands.mdx
index 53aa0bc09..8aae1e5de 100644
--- a/src/frontend/src/content/docs/fundamentals/http-commands.mdx
+++ b/src/frontend/src/content/docs/fundamentals/http-commands.mdx
@@ -3,7 +3,7 @@ title: Custom HTTP commands
description: Learn how to create custom HTTP commands in Aspire.
---
-import { Aside } from '@astrojs/starlight/components';
+import { Aside, Tabs, TabItem } from '@astrojs/starlight/components';
import { Image } from 'astro:assets';
import LearnMore from '@components/LearnMore.astro';
import customHttpCommandHighlighted from '@assets/fundamentals/custom-http-command-highlighted.png';
@@ -88,6 +88,7 @@ The preceding code:
- `commandOptions`: An optional instance of `HttpCommandOptions` that configures the command's behavior and appearance in the UI:
- `Description`: Provides a description of the command that's shown in the UI.
- `PrepareRequest`: A callback function that configures the HTTP request before sending it. In this case, it adds a custom (`X-CacheInvalidation-Key`) header with the value of the `ApiCacheInvalidationKey` parameter.
+ - `ResultMode`: Specifies whether the response body should be returned as command result data. For more information, see [Return the HTTP response body](#return-the-http-response-body).
- `IconName`: Specifies the icon to be used for the command in the UI (`DocumentLightning`).
- `IsHighlighted`: Indicates whether the command should be highlighted in the UI.
- Finally, the application is built and run.
@@ -133,6 +134,96 @@ The preceding code:
- If the header is valid, it calls the `ClearAllAsync` method on the `ICacheService` to clear all cached items.
- Finally, it returns an HTTP OK response.
+## Return the HTTP response body
+
+By default, an HTTP command uses the HTTP status code to determine whether the command succeeded, and it doesn't return the response body as command result data. Set `HttpCommandOptions.ResultMode` to opt in to returning a non-empty response body from the endpoint. The body is surfaced as the command's structured output, just like [structured output from custom resource commands](/fundamentals/custom-resource-commands/#return-structured-output-from-a-command).
+
+The following example configures an HTTP command that returns a JSON response body:
+
+
+
+
+```csharp title="AppHost.cs"
+var builder = DistributedApplication.CreateBuilder(args);
+
+builder.AddProject("api")
+ .WithHttpCommand(
+ path: "/admin/sync",
+ displayName: "Sync now",
+ commandOptions: new HttpCommandOptions
+ {
+ Method = HttpMethod.Post,
+ ResultMode = HttpCommandResultMode.Auto
+ });
+
+builder.Build().Run();
+```
+
+
+
+
+```typescript title="apphost.ts"
+import { createBuilder, HttpCommandResultMode } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+await builder
+ .addNodeApp('api', './api', 'src/index.ts')
+ .withHttpCommand('/admin/sync', 'Sync now', {
+ methodName: 'POST',
+ resultMode: HttpCommandResultMode.Auto,
+ });
+
+await builder.build().run();
+```
+
+
+
+
+The endpoint can return the payload that should be shown to the command caller. For example, a C# minimal API endpoint can use `MapPost`:
+
+```csharp title="Program.cs"
+app.MapPost("/admin/sync", () =>
+{
+ return Results.Json(new
+ {
+ status = "Completed",
+ itemsProcessed = 42
+ });
+});
+```
+
+The Node.js app registered by the TypeScript AppHost sample can return the same payload from an Express endpoint:
+
+```typescript title="src/index.ts"
+import express from 'express';
+
+const app = express();
+const port = process.env.PORT || 3000;
+
+app.post('/admin/sync', (_req, res) => {
+ res.json({
+ status: 'Completed',
+ itemsProcessed: 42,
+ });
+});
+
+app.listen(port, () => {
+ console.log(`API listening on port ${port}`);
+});
+```
+
+`HttpCommandResultMode` controls how the default HTTP command result handler interprets the response body:
+
+| Result mode | Behavior |
+| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `None` | The default. The response body isn't captured as command result data. |
+| `Auto` | Infers the result format from the response `Content-Type`. JSON content types, such as `application/json` and `application/*+json`, are returned as JSON. Text-like content types, such as `text/*`, XML, and `application/x-www-form-urlencoded`, are returned as text. Other content types aren't captured. |
+| `Json` | Returns the response body as JSON command result data, regardless of the response `Content-Type`. |
+| `Text` | Returns the response body as plain text command result data, regardless of the response `Content-Type`. |
+
+If the endpoint returns a non-success status code, the command fails. When `ResultMode` captures a response body, that body is attached to the failure result so callers can inspect the error details. `ResultMode` is used only when `HttpCommandOptions.GetCommandResult` isn't set; if you provide `GetCommandResult`, that callback controls the command's success, message, and result data.
+
### Example dashboard experiences
The sample AppHost and corresponding ASP.NET Core minimal API projects demonstrate both sides of the HTTP command implementation. When you run the AppHost, the dashboard's **Resources** page displays the custom HTTP command as a button. When you specify that the command should be highlighted (`isHighlighted: true`), the button appears on the **Actions** column of the **Resources** page. This allows users to easily trigger the command from the dashboard, as shown in the following screenshot:
diff --git a/src/frontend/src/content/docs/fundamentals/networking-overview.mdx b/src/frontend/src/content/docs/fundamentals/networking-overview.mdx
index 7506be390..e147d3bb8 100644
--- a/src/frontend/src/content/docs/fundamentals/networking-overview.mdx
+++ b/src/frontend/src/content/docs/fundamentals/networking-overview.mdx
@@ -263,6 +263,46 @@ For more information, see the available properties of the [EndpointAnnotation pr
For project-resource-specific endpoint filtering, see [Project resources](/integrations/dotnet/project-resources/).
+### Excluding endpoints from service discovery
+
+By default, every endpoint on a resource is included when another resource references it via `WithReference(resource)`. Some resources expose auxiliary endpoints—such as admin dashboards or health-check ports—that consumer services should not discover automatically. Use the `ExcludeReferenceEndpoint` property on `EndpointAnnotation` to opt an endpoint out of the default reference set:
+
+```csharp title="AppHost.cs"
+builder.AddContainer("myservice", "myimage")
+ .WithHttpEndpoint(name: "management", port: 8080)
+ .WithEndpoint("management", ep => ep.ExcludeReferenceEndpoint = true);
+```
+
+When `ExcludeReferenceEndpoint` is `true`, the endpoint is **not** injected into dependent services by a plain `WithReference(resource)` call. It can still be referenced explicitly by name:
+
+```csharp title="AppHost.cs"
+var myService = builder.AddContainer("myservice", "myimage")
+ .WithHttpEndpoint(name: "api", port: 5000)
+ .WithHttpEndpoint(name: "management", port: 8080)
+ .WithEndpoint("management", ep => ep.ExcludeReferenceEndpoint = true);
+
+// Consumer only gets the "api" endpoint — "management" is excluded
+var api = builder.AddProject("api")
+ .WithReference(myService);
+
+// Opt in explicitly when the management endpoint is actually needed
+var adminApp = builder.AddProject("admin")
+ .WithReference(myService.GetEndpoint("management"));
+```
+
+The following built-in Aspire integrations already apply this pattern to their auxiliary endpoints:
+
+| Resource | Excluded endpoint |
+|---|---|
+| Keycloak | Management dashboard (`management`) |
+| Azure Cosmos DB Emulator | Health-check endpoint (`emulatorHealth`) |
+| Azure Event Hubs Emulator | Health-check endpoint (`emulatorHealth`) |
+| Azure Service Bus Emulator | Health-check endpoint (`emulatorHealth`) |
+
+:::note
+`ExcludeReferenceEndpoint` defaults to `false`. Existing endpoints continue to behave as before unless the property is explicitly set to `true`.
+:::
+
## Troubleshooting
### Port already in use
diff --git a/src/frontend/src/content/docs/get-started/add-aspire-existing-app.mdx b/src/frontend/src/content/docs/get-started/add-aspire-existing-app.mdx
index fe847a40a..b1240a342 100644
--- a/src/frontend/src/content/docs/get-started/add-aspire-existing-app.mdx
+++ b/src/frontend/src/content/docs/get-started/add-aspire-existing-app.mdx
@@ -1,6 +1,6 @@
---
title: "Add Aspire to an existing app"
-description: "Add Aspire to an existing application by choosing an AppHost, modeling your existing resources, and running everything locally."
+description: "Add Aspire to an existing application using aspire init and an AI coding agent, or by manually wiring resources in your AppHost."
next: false
---
@@ -8,7 +8,7 @@ import { FileTree, Steps, TabItem, Tabs } from '@astrojs/starlight/components';
import { Kbd } from 'starlight-kbd/components';
import LearnMore from '@components/LearnMore.astro';
-Add Aspire to the app you already have instead of rebuilding your solution around a new template. Start by choosing the AppHost style that fits your repo, then register the services, containers, shared infrastructure, and, when needed, custom executables you already run today, regardless of whether those workloads are written in C#, Node.js, Python, Go, Rust, Java, or something else.
+Add Aspire to the app you already have instead of rebuilding your solution around a new template. The fastest path is `aspire init` paired with an AI coding agent that automatically discovers your services and wires them into an AppHost. If you prefer full control, manual steps are provided below.
## Why add Aspire to an existing app?
@@ -16,42 +16,90 @@ As distributed applications grow, local development often turns into a collectio
You can also adopt Aspire incrementally. Start by modeling the parts that are hardest to keep aligned by hand, such as containers, databases, caches, queues, background workers, and local dev commands. Add telemetry when you're ready, then deepen the model as your app grows.
-## Start with your scenario
+## Prerequisites
+
+Before you begin, make sure you have:
+
+- [Aspire CLI installed](/get-started/install-cli/)
+- An existing application or workspace to add Aspire to
+- The runtimes and tools your existing services already need
+
+### AppHost-specific requirements
+
+
+
-This guide is organized around the kinds of resources you already manage rather than around a preferred service language. Aspire is multi-language by design, so one AppHost can orchestrate a system that spans C#, Node.js, Python, Go, Rust, Java, and other supported workloads.
+- [.NET SDK 10.0 or later](/get-started/prerequisites/)
+- Visual Studio 2022 17.13 or later, Visual Studio Code, or JetBrains Rider (optional)
+
+
+
+
+- [Node.js 22 or later](https://nodejs.org/)
+- npm, yarn, or pnpm
+
+
+
-- **Existing services with hosting integrations**: You already have C# services, Node.js apps, Vite frontends, Python apps, or ASGI apps such as FastAPI and want to use Aspire's dedicated hosting APIs.
-- **Existing containers and shared infrastructure**: You already have images, Docker Compose files, databases, caches, queues, or reverse proxies that you want Aspire to coordinate.
-- **Existing C# services**: You already have C# projects or file-based C# apps and want the C# AppHost to orchestrate them directly.
+## Recommended: Use an AI coding agent with the "aspireify" skill
+
+The fastest way to add Aspire to an existing app is to let `aspire init` scaffold the skeleton, then hand off wiring to the **`aspireify` agent skill**. The skill handles resource discovery, dependency wiring, OpenTelemetry setup, and validation automatically.
+
+
+
+1. Run `aspire init` in your repo root:
+
+ ```bash title="Initialize Aspire"
+ aspire init
+ ```
+
+ Choose your AppHost language (C# or TypeScript) when prompted, or pass `--language csharp` / `--language typescript`. The command creates a minimal AppHost file, an `aspire.config.json`, and installs the `aspireify` skill into your agent's skill directory.
+
+ :::note[The AppHost language doesn't limit what you can orchestrate]
+ Whether you choose a C# or TypeScript AppHost, Aspire can orchestrate services written in any language — C#, JavaScript, Python, Go, Rust, Java, containers, and more. The AppHost language is just how you express the orchestration, not a constraint on the workloads.
+ :::
+
+2. Ask your AI coding agent to run the `aspireify` skill. The agent will:
+
+ - Scan your repo and discover existing projects, services, containers, and infrastructure
+ - Ask you to confirm the resources it found, which ones you want included, and other clarifying questions before starting
+ - Wire resources into the AppHost with `WithReference`, `WaitFor`, endpoints, and volumes
+ - Add ServiceDefaults and configure OpenTelemetry for each service
+ - Validate the setup by running `aspire start`
+
+3. Once the agent reports success, run `aspire start` yourself and open the dashboard to verify everything looks correct. Something not right? Tell the agent-it has plenty of tools from Aspire to troubleshoot!
+
+
+
+
+
+For more details on the `aspire init` command and the aspireify skill, see the [CLI reference: `aspire init`](/reference/cli/commands/aspire-init/).
+
+
+:::tip[Works with any AI coding agent]
+The aspireify skill works with GitHub Copilot, Claude Code, or any MCP-compatible assistant. It reads your repo structure and applies the same wiring patterns described in the manual sections below.
+:::
+
+---
-In every scenario, you can choose either AppHost style and mix resource types in the same application model.
+## Wire your AppHost manually
-When a dedicated hosting API exists for a workload, prefer it over `AddExecutable` or `addExecutable`. Executable resources are the fallback for custom tools, one-off commands, or workloads that do not yet have a dedicated hosting integration.
+If you prefer full control over the wiring, or want to understand what the aspireify skill does under the hood, follow the manual steps below. This is also the reference for anyone extending or customizing an AppHost after the initial setup.
-## Choose your AppHost
+### Choose your AppHost
The AppHost is the orchestration layer. Your choice here changes how you express orchestration, not what Aspire can orchestrate.
-Use a C# AppHost when your repo already centers on C# or when you want a single-file orchestrator that still fits naturally into .NET SDK and IDE workflows.
+Aspire offers two C# AppHost styles:
-- Lives in a single `apphost.cs` file that uses `#:sdk` and `#:package` directives
-- Common APIs include:
+**File-based AppHost** — a single `apphost.cs` file that uses `#:sdk` and `#:package` directives. No `.csproj`, no solution integration required. Best for polyglot repos or quick setups.
- | API | Description |
- | --- | --- |
- | `AddContainer()` | Run a prebuilt container image that already exists in your current workflow. |
- | `AddCSharpApp()` | Point a file-based AppHost at an existing C# app or `.csproj` without creating a separate AppHost project graph. |
- | `AddDockerfile()` | Build and run a container from an existing Dockerfile in your repo. |
- | `AddExecutable()` | Fall back to a custom command when a dedicated hosting API does not exist yet. |
- | `AddJavaScriptApp()`, `AddNodeApp()`, `AddViteApp()` | Model JavaScript and TypeScript workloads, from general package-script apps to Node entrypoints and Vite frontends. |
- | `AddParameter()` | Define reusable config values and secrets for the resources in your model. |
- | `AddPostgres()`, `AddRedis()` | Add shared infrastructure with first-class integrations and connection wiring. |
- | `AddPythonApp()`, `AddUvicornApp()` | Model Python scripts, workers, and ASGI apps such as FastAPI. |
- | `WaitFor()`, `WithHttpEndpoint()`, `WithReference()` | Wire service discovery, startup ordering, and HTTP endpoints between resources. |
-- Fits naturally into existing .NET SDK, IDE, and repo workflows
+**Project-based AppHost** — a traditional `AppHost.csproj` that lives inside a `.sln` alongside your other C# projects. Uses `ProjectReference` items and the generated `Projects` namespace for strongly-typed `AddProject()` calls. Best when your repo is already a .NET solution and you want IDE-integrated orchestration.
+
+Both styles use the same `Aspire.AppHost.Sdk` and the same hosting APIs.
@@ -59,204 +107,223 @@ Use a C# AppHost when your repo already centers on C# or when you want a single-
Use a TypeScript AppHost when your repo already centers on a Node.js workspace or when you prefer path-based orchestration in TypeScript.
- Lives in `apphost.ts`
-- Common APIs include:
-
- | API | Description |
- | --- | --- |
- | `addContainer()` | Run a prebuilt container image that you already publish or pull today. |
- | `addCSharpApp()` | Point at an existing C# app, `.csproj`, or directory directly from a TypeScript AppHost without relying only on `addProject()`. |
- | `addDockerfile()` | Build and run a container directly from a Dockerfile in the repo. |
- | `addExecutable()` | Fall back to a custom command for unsupported runtimes or one-off tools. |
- | `addNodeApp()`, `addViteApp()` | Model Node.js services and Vite frontends with JavaScript-aware defaults. |
- | `addParameter()` | Define reusable config values and secrets that multiple resources can consume. |
- | `addPostgres()`, `addRedis()` | Add shared infrastructure with first-class integrations and references. |
- | `addProject()` | Add an existing project by path, such as a `.csproj`, into a TypeScript AppHost. |
- | `addPythonApp()`, `addUvicornApp()` | Model Python scripts, workers, and ASGI apps such as FastAPI. |
- | `waitFor()`, `withHttpEndpoint()`, `withReference()` | Wire service discovery, startup ordering, and HTTP endpoints between resources. |
+- Runs under popular package managers including npm, pnpm, yarn, and Bun
- Fits naturally into existing package-manager and monorepo workflows
:::tip[No runtime lock-in]
-Both AppHost styles can orchestrate multi-language systems. You can mix C#, Node.js, Python, Go, Rust, Java, containers, and supported integrations in the same application model, and use executable resources when you truly need a custom command path. Pick the AppHost that best fits your repo and team, not the one that matches a single workload.
+Both AppHost styles can orchestrate multi-language systems. You can mix C#, Node.js, Python, Go, Rust, Java, containers, and supported integrations in the same application model. Pick the AppHost that best fits your repo and team, not the one that matches a single workload.
:::
-## Prerequisites
-
-Before you begin, make sure you have:
-
-- [Aspire CLI installed](/get-started/install-cli/)
-- An existing application or workspace to add Aspire to
-- The runtimes and tools your existing services already need
-
-### AppHost-specific requirements
+### Set up your AppHost
-- [.NET SDK 10.0 or later](/get-started/prerequisites/)
-- Visual Studio 2022 17.13 or later, Visual Studio Code, or JetBrains Rider (optional)
+#### File-based AppHost (default)
-
-
+Use a file-based AppHost when you want a lightweight single-file orchestrator without adding a project to your solution. This is the default style created by `aspire init` when no `.sln` is detected.
-- [Node.js 22 or later](https://nodejs.org/)
-- npm, yarn, or pnpm
+
-
-
+1. Run `aspire init` from your repo root. Without a `.sln` present, it creates a file-based `apphost.cs`:
-### Scenario-specific requirements
+ ```bash title="Initialize Aspire with a file-based AppHost"
+ aspire init
+ ```
-**For workloads with hosting integrations:**
+2. Add hosting integrations:
-- Service directories and standard project metadata for the workloads you plan to model, such as `package.json`, `pyproject.toml`, or `requirements.txt`
-- The runtimes and package managers those services already need
+ ```bash title="Add hosting integrations"
+ aspire add redis
+ ```
-**For custom executables as a fallback:**
+3. Wire the resources in `apphost.cs`:
-- Working start commands for each service you plan to model
-- Any repo-local config files or working directories those commands rely on
+ ```csharp title="apphost.cs"
+ #:sdk Aspire.AppHost.Sdk@13.2.0
+ #:package Aspire.Hosting.Redis@13.2.0
-**For containers and shared infrastructure:**
+ #pragma warning disable ASPIRECSHARPAPPS001
-- Existing image names, Dockerfiles, or Compose knowledge for the services you want to model
-- The databases, caches, queues, or reverse proxies you want Aspire to own or connect to
+ var builder = DistributedApplication.CreateBuilder(args);
-**For C# services:**
+ var cache = builder.AddRedis("cache");
-- One or more C# projects or file-based C# apps if you plan to use `AddCSharpApp`
-- A solution file is optional and not required for a file-based C# AppHost
+ var api = builder.AddCSharpApp("api", "./src/Api/MyApp.Api.csproj")
+ .WithReference(cache)
+ .WithHttpHealthCheck("/health");
-## Overview of the process
+ var worker = builder.AddCSharpApp("worker", "./src/Worker/MyApp.Worker.csproj")
+ .WithReference(cache);
-Adding Aspire to an existing app usually follows these steps:
+ builder.Build().Run();
+ ```
-
+
-1. **Choose an AppHost** that fits your repo and workflow.
-2. **Initialize Aspire support** with `aspire init`.
-3. **Register your existing processes, containers, and shared resources** in the AppHost.
-4. **Add telemetry and integrations** where they add value.
-5. **Run and verify** the full system with Aspire orchestration.
+After setup, a typical repo layout looks like this:
-
+
+ - apphost.cs (new)
+ - aspire.config.json (new)
+ - src/
+ - Api/
+ - MyApp.Api.csproj
+ - Worker/
+ - MyApp.Worker.csproj
+
-## Initialize Aspire support
+:::caution[AddCSharpApp is experimental]
+`AddCSharpApp` is currently experimental. If you want details on limitations or diagnostic suppression, see [C# file-based apps](/integrations/dotnet/csharp-file-based-apps/).
+:::
-The `aspire init` command creates the orchestration layer and helps wire the first set of resources into it.
+#### Project-based AppHost (with a .sln)
-
-
+Use this approach when your repo is already a .NET solution (`.sln` or `.slnx`) with multiple projects. The project-based AppHost uses `ProjectReference` items and the generated `Projects` namespace for strongly-typed `AddProject()` calls, giving you full IDE support including IntelliSense, refactoring, and build-order awareness.
-1. Navigate to the root of your existing repo:
+1. Run `aspire init` from your solution root. It detects the `.sln` and creates a project-based AppHost automatically:
- ```bash title="Navigate to your repo"
- cd /path/to/your-repo
+ ```bash title="Initialize Aspire in a .NET solution"
+ aspire init
```
-2. Run `aspire init`:
+2. Add project references from the AppHost to each service you want to orchestrate:
- ```bash title="Initialize Aspire with a C# AppHost"
- aspire init
+ ```bash title="Add project references"
+ dotnet add MyApp.AppHost reference src/Api/MyApp.Api.csproj
+ dotnet add MyApp.AppHost reference src/Web/MyApp.Web.csproj
+ dotnet add MyApp.AppHost reference src/Worker/MyApp.Worker.csproj
```
- The command runs in interactive mode by default. It can detect existing C# projects, create a single-file AppHost, and add the initial package directives and configuration it needs.
+3. Add hosting integrations:
-
+ ```bash title="Add hosting integrations"
+ aspire add redis
+ aspire add postgres
+ ```
-
-For more details on the `aspire init` command and its options, see the [CLI reference: `aspire init`](/reference/cli/commands/aspire-init/).
-
+4. Wire the resources in the AppHost's `Program.cs`:
-After initialization, a typical C#-centric repo might look like this:
+ ```csharp title="MyApp.AppHost/Program.cs"
+ var builder = DistributedApplication.CreateBuilder(args);
-
- - apphost.cs (new)
- - apphost.run.json (new)
- - services/
- - Api/
- - ExampleEcommerce.Api.csproj
- - Web/
- - package.json
- - src/
- - workers/
- - inventory-sync/
- - worker.py
-
+ var cache = builder.AddRedis("cache")
+ .WithLifetime(ContainerLifetime.Persistent);
-Starter AppHost:
+ var db = builder.AddPostgres("postgres")
+ .WithLifetime(ContainerLifetime.Persistent)
+ .AddDatabase("mydb");
-```csharp title="apphost.cs — Initial state"
-#:sdk Aspire.AppHost.Sdk@13.2.0
+ var api = builder.AddProject("api")
+ .WithReference(db)
+ .WithReference(cache)
+ .WaitFor(db);
-var builder = DistributedApplication.CreateBuilder(args);
+ builder.AddProject("web")
+ .WithReference(api)
+ .WaitFor(api);
-// TODO: Add resources here
+ builder.AddProject("worker")
+ .WithReference(cache)
+ .WithReference(db);
-builder.Build().Run();
-```
+ builder.Build().Run();
+ ```
+
+
-Add `#:package` directives in `apphost.cs` for the hosting integrations you use, such as `Aspire.Hosting.Redis`, `Aspire.Hosting.Python`, `Aspire.Hosting.JavaScript`, and `Aspire.Hosting.PostgreSQL`.
+After setup, a typical solution layout looks like this:
+
+
+ - MyApp.sln
+ - MyApp.AppHost/
+ - MyApp.AppHost.csproj
+ - Program.cs
+ - MyApp.ServiceDefaults/
+ - MyApp.ServiceDefaults.csproj
+ - Extensions.cs
+ - src/
+ - Api/
+ - MyApp.Api.csproj
+ - Web/
+ - MyApp.Web.csproj
+ - Worker/
+ - MyApp.Worker.csproj
+
+
+:::note[ProjectReference wiring]
+Each `ProjectReference` in the AppHost triggers a source generator that creates a class in the `Projects` namespace. When you call `AddProject("api")`, Aspire knows the project path and how to build and launch it. See [Aspire SDK](/get-started/aspire-sdk/) for details on how project references and metadata generation work.
+:::
+
+
+For the full `AddProject` workflow including custom type names, multi-project solutions, and launch profiles, see [Project resources](/integrations/dotnet/project-resources/).
+
-1. Navigate to the root of your existing workspace or repo:
+1. Run `aspire init` from your workspace root with the TypeScript language option:
- ```bash title="Navigate to your workspace or repo"
- cd /path/to/your-workspace
+ ```bash title="Initialize Aspire with a TypeScript AppHost"
+ aspire init --language typescript
```
-2. Run `aspire init` with the TypeScript AppHost option:
+2. Add hosting integrations:
- ```bash title="Initialize Aspire with a TypeScript AppHost"
- aspire init --language typescript
+ ```bash title="Add hosting integrations"
+ aspire add redis
+ aspire add postgres
```
- If you omit `--language typescript`, choose **TypeScript** when the CLI prompts for the AppHost language.
+3. Wire the resources in `apphost.ts`:
-
+ ```typescript title="apphost.ts" twoslash
+ import { createBuilder } from './.modules/aspire.js';
-
-For more details on the `aspire init` command and its options, see the [CLI reference: `aspire init`](/reference/cli/commands/aspire-init/).
-
+ const builder = await createBuilder();
-After initialization, a typical workspace might look like this:
+ const cache = await builder.addRedis('cache');
-
- - my-store/
- - **apphost.ts** (new)
- - **.modules/** (new)
- - **aspire.config.json** (new)
- - **package.json** (new or updated)
- - services/
- - api/
- - Dockerfile
- - web/
- - package.json
- - src/
- - workers/
- - inventory-sync/
- - worker.py
-
+ const db = (await builder.addPostgres('postgres')).addDatabase('mydb');
-Starter AppHost:
+ const api = await builder
+ .addProject('api', './src/Api/MyApp.Api.csproj')
+ .withReference(db)
+ .withReference(cache)
+ .waitFor(db);
-```typescript title="apphost.ts — Initial state" twoslash
-import { createBuilder } from './.modules/aspire.js';
+ await builder
+ .addViteApp('web', './services/web')
+ .withReference(api)
+ .waitFor(api);
-const builder = await createBuilder();
+ await builder.build().run();
+ ```
-// Add your resources here
+
-await builder.build().run();
-```
+After setup, a typical workspace layout looks like this:
+
+
+ - apphost.ts (new)
+ - .modules/ (new)
+ - aspire.config.json (new)
+ - package.json (new or updated)
+ - services/
+ - web/
+ - package.json
+ - src/
+ - src/
+ - Api/
+ - MyApp.Api.csproj
+
:::note[The .modules folder is generated]
Let the Aspire CLI manage `.modules/` rather than editing generated SDK files manually.
@@ -265,17 +332,6 @@ Let the Aspire CLI manage `.modules/` rather than editing generated SDK files ma
-## Model your existing apps in the AppHost
-
-Once you have an AppHost, use it to model the parts of your system that matter during local development: workloads, supporting infrastructure, and the connections between them. A resource does not need to mirror every repo boundary or implementation detail; it should represent something Aspire needs to start, observe, or connect.
-
-Think first about relationships: which workloads consume which backing services, which endpoints need to be reachable, and which resources must be ready before others can do useful work.
-
-The same patterns apply across AppHost styles:
-
-- Use `WithReference` or `withReference` to express that one resource depends on another and to flow connection information through the model.
-- Use `WaitFor` or `waitFor` when readiness matters and one resource should not start until another is available.
-
### Scenario: Existing services with hosting integrations
Use this approach when Aspire already has a first-class resource type for the workload you want to run. That keeps the application model focused on what the service is and what it depends on, instead of reducing it to a generic shell command.
@@ -286,10 +342,10 @@ Common examples include Node.js apps, Vite frontends, Python workers, and Uvicor
```csharp title="apphost.cs — Existing services with hosting integrations"
-#:sdk Aspire.AppHost.Sdk@13.2.0
-#:package Aspire.Hosting.Redis@13.2.0
-#:package Aspire.Hosting.Python@13.2.0
-#:package Aspire.Hosting.JavaScript@13.2.0
+#:sdk Aspire.AppHost.Sdk@13.3.0
+#:package Aspire.Hosting.Redis@13.3.0
+#:package Aspire.Hosting.Python@13.3.0
+#:package Aspire.Hosting.JavaScript@13.3.0
var builder = DistributedApplication.CreateBuilder(args);
@@ -366,9 +422,9 @@ aspire add redis
```csharp title="apphost.cs — Existing containers and shared infrastructure"
-#:sdk Aspire.AppHost.Sdk@13.2.0
-#:package Aspire.Hosting.PostgreSQL@13.2.0
-#:package Aspire.Hosting.Redis@13.2.0
+#:sdk Aspire.AppHost.Sdk@13.3.0
+#:package Aspire.Hosting.PostgreSQL@13.3.0
+#:package Aspire.Hosting.Redis@13.3.0
var builder = DistributedApplication.CreateBuilder(args);
@@ -418,63 +474,6 @@ await builder.build().run();
-### Scenario: Existing C# services
-
-Use this approach when your repo already contains .NET services that should stay where they are. The goal is to register those services as resources by pointing at their existing paths, then connect the infrastructure, endpoints, and health checks they already need as part of the larger application model.
-
-In a file-based C# AppHost, `AddCSharpApp` is the direct way to point at an existing C# service path. This works with single-file C# apps, directories, or existing `.csproj` files.
-
-:::caution[AddCSharpApp is experimental]
-`AddCSharpApp` is currently experimental. If you want details on limitations or diagnostic suppression, see [C# file-based apps](/integrations/dotnet/csharp-file-based-apps/).
-:::
-
-
-
-
-```csharp title="apphost.cs — Existing C# services"
-#:sdk Aspire.AppHost.Sdk@13.2.0
-#:package Aspire.Hosting.Redis@13.2.0
-
-#pragma warning disable ASPIRECSHARPAPPS001
-
-var builder = DistributedApplication.CreateBuilder(args);
-
-var cache = builder.AddRedis("cache");
-
-var api = builder.AddCSharpApp("api", "./src/Api/Store.Api.csproj")
- .WithReference(cache)
- .WithHttpHealthCheck("/health");
-
-var worker = builder.AddCSharpApp("worker", "./src/Worker/Store.Worker.csproj")
- .WithReference(cache);
-
-builder.Build().Run();
-```
-
-
-
-
-```typescript title="apphost.ts — Existing C# services" twoslash
-import { createBuilder } from './.modules/aspire.js';
-
-const builder = await createBuilder();
-
-const cache = await builder.addRedis('cache');
-
-const api = await builder
- .addProject('api', './src/Api/Store.Api.csproj')
- .withReference(cache)
- .withHttpHealthCheck({ path: '/health' });
-
-await builder
- .addProject('worker', './src/Worker/Store.Worker.csproj')
- .withReference(cache);
-
-await builder.build().run();
-```
-
-
-
### Scenario: Docker Compose
@@ -514,8 +513,8 @@ services:
```csharp title="apphost.cs" showLineNumbers
-#:sdk Aspire.AppHost.Sdk@13.2.0
-#:package Aspire.Hosting.PostgreSQL@13.2.0
+#:sdk Aspire.AppHost.Sdk@13.3.0
+#:package Aspire.Hosting.PostgreSQL@13.3.0
var builder = DistributedApplication.CreateBuilder(args);
@@ -562,7 +561,7 @@ await builder.build().run();
These scenarios are starting points, not mutually exclusive modes. Most real apps mix workload-specific resources, containers, shared infrastructure, project-path references, and occasional custom commands in a single application model. The key is that dependencies, endpoints, configuration, and startup behavior become explicit.
-For more examples, see [C# file-based apps](/integrations/dotnet/csharp-file-based-apps/), [Executable resources](/app-host/executable-resources/), and [Migrate from Docker Compose](/app-host/migrate-from-docker-compose/).
+For more examples, see [Project resources](/integrations/dotnet/project-resources/), [C# file-based apps](/integrations/dotnet/csharp-file-based-apps/), [Executable resources](/app-host/executable-resources/), and [Migrate from Docker Compose](/app-host/migrate-from-docker-compose/).
## Add telemetry configuration (optional)
@@ -576,7 +575,7 @@ If your app includes C# services, ServiceDefaults is the standard way to add obs
-1. Add ServiceDefaults if you did not enable it during `aspire init`:
+1. Create a ServiceDefaults project and reference it from your service:
```bash title="Add ServiceDefaults"
dotnet new aspire-servicedefaults -n YourProject.ServiceDefaults
diff --git a/src/frontend/src/content/docs/get-started/ai-coding-agents.mdx b/src/frontend/src/content/docs/get-started/ai-coding-agents.mdx
index d9abb12e5..28ae0cb1b 100644
--- a/src/frontend/src/content/docs/get-started/ai-coding-agents.mdx
+++ b/src/frontend/src/content/docs/get-started/ai-coding-agents.mdx
@@ -16,7 +16,7 @@ Aspire provides a first-class setup experience for AI coding agents. Run `aspire
Aspire gives coding agents the same visibility into your running application that a developer has. The resource data, structured logs, and distributed traces you see in the [Aspire Dashboard](/dashboard/overview/) are exposed to agents through the [Aspire MCP server](/get-started/aspire-mcp-server/) and the [Aspire CLI](/get-started/install-cli/). Whether a person is debugging in the dashboard or an agent is diagnosing through MCP, they see the same picture.
-The Aspire CLI is built for agent-driven workflows — every command works non-interactively to not block and prompts and supports `--format Json` for structured plain text output. Key commands include `aspire start` (background execution), `aspire start --isolated` (parallel worktrees), `aspire wait` (block until healthy), `aspire describe`, `aspire logs`, and `aspire docs search`.
+The Aspire CLI is built for agent-driven workflows — commands support non-interactive execution to avoid blocking on prompts, and many commands support `--format Json` for structured plain text output. Key commands include `aspire start` (background execution), `aspire start --isolated` (parallel worktrees), `aspire wait` (block until healthy), `aspire describe`, `aspire logs`, and `aspire docs search`.
The Aspire skill file (installed by `aspire agent init`) teaches agents all of these patterns automatically.
@@ -34,11 +34,20 @@ When you create a new Aspire project with `aspire new` or `aspire init`, you're
aspire agent init
```
-1. Select the components you want to configure:
+1. Select the **skill locations** where you want files installed. The **Standard** location is pre-selected by default:
- - **Aspire skill file** (recommended) — teaches your AI agent how to use Aspire CLI commands
+ | Location | Directory | Notes |
+ |---|---|---|
+ | **Standard** | `.agents/skills/` | Supported by VS Code, GitHub Copilot, and OpenCode |
+ | **Claude Code** | `.claude/skills/` | Claude Code specific |
+ | **GitHub Skills** | `.github/skills/` | VS Code / GitHub Copilot specific |
+ | **OpenCode** | `.opencode/skill/` | OpenCode specific |
+
+1. Select the **skills and tools** to install into those locations. All options are pre-selected by default:
+
+ - **Aspire skill** — teaches your AI agent how to use Aspire CLI commands
- **Playwright CLI** — enables browser automation for testing web resources
- - **Aspire MCP server** — gives your AI agent runtime access to resources, logs, and traces
+ - **dotnet-inspect skill** — teaches your AI agent to query .NET API surfaces
@@ -55,14 +64,18 @@ The `aspire agent init` command detects your AI development environment and crea
### Aspire skill file
-The skill file teaches your AI coding agent how to work with Aspire. It includes a CLI command reference, key workflows (starting apps, debugging issues, adding integrations), and important rules to follow. The file is created in the format your agent expects:
+The skill file teaches your AI coding agent how to work with Aspire. It includes a CLI command reference, key workflows (starting apps, debugging issues, adding integrations), and important rules to follow. The file is installed into each selected skill location:
+- .agents/skills/aspire/
+ - SKILL.md Standard (VS Code, GitHub Copilot, OpenCode)
- .github/skills/aspire/
- - SKILL.md GitHub Copilot
+ - SKILL.md GitHub Copilot (legacy location)
- .claude/skills/aspire/
- SKILL.md Claude Code
+- .opencode/skill/aspire/
+ - SKILL.md OpenCode
@@ -73,6 +86,29 @@ The skill file guides your agent on how to:
- Add integrations with `aspire add` and search documentation with `aspire docs`
- Use resource MCP tools for database queries and other resource-specific operations
+### dotnet-inspect skill
+
+The `dotnet-inspect` skill teaches your AI agent to query .NET API surfaces using the [`dotnet-inspect`](https://github.com/richlander/dotnet-inspect) tool. It's installed alongside the Aspire skill in each selected location:
+
+
+
+- .agents/skills/dotnet-inspect/
+ - SKILL.md
+- .github/skills/dotnet-inspect/
+ - SKILL.md
+- .claude/skills/dotnet-inspect/
+ - SKILL.md
+- .opencode/skill/dotnet-inspect/
+ - SKILL.md
+
+
+
+The skill enables your agent to inspect NuGet package API surfaces, compare API changes between package versions, and explore .NET types and members.
+
+:::note[Skill conflict]
+The `dotnet-inspect` skill should not be installed alongside the Aspire skill. The Aspire skill already includes support for `aspire docs api` subcommands, providing a powerful way to query Aspire APIs across both C# and TypeScript.
+:::
+
### Aspire MCP server
The MCP server gives your AI agent direct runtime access to your running Aspire application — resource status, logs, traces, and commands. See [Aspire MCP server](/get-started/aspire-mcp-server/) for configuration details, available tools, and the security model.
@@ -165,4 +201,4 @@ The Aspire MCP server uses the STDIO transport protocol and may work with other
- [aspire agent init command](/reference/cli/commands/aspire-agent-init/)
- [aspire agent mcp command](/reference/cli/commands/aspire-agent-mcp/)
- [Dashboard security considerations](/dashboard/security-considerations/)
-- [GitHub Copilot in the Dashboard](/dashboard/copilot/)
+- [Dashboard and AI coding agents](/dashboard/ai-coding-agents/)
diff --git a/src/frontend/src/content/docs/get-started/aspire-mcp-server.mdx b/src/frontend/src/content/docs/get-started/aspire-mcp-server.mdx
index a8f33ca3b..d1237b9b7 100644
--- a/src/frontend/src/content/docs/get-started/aspire-mcp-server.mdx
+++ b/src/frontend/src/content/docs/get-started/aspire-mcp-server.mdx
@@ -6,6 +6,10 @@ description: Learn about the Aspire MCP server tools, security model, and troubl
import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
import LearnMore from '@components/LearnMore.astro';
+:::note
+The MCP server that was previously built into the Aspire dashboard has been removed. The [`aspire agent mcp`](/reference/cli/commands/aspire-agent-mcp/) CLI command is now the way to start the Aspire MCP server.
+:::
+
The Aspire MCP server gives AI coding agents direct runtime access to your running Aspire application. Through the Model Context Protocol (MCP), agents can query resource status, read logs, inspect distributed traces, and execute commands — without you copy-pasting terminal output.
The MCP server is configured automatically when you run `aspire agent init` and select **Install Aspire MCP server**.
@@ -193,8 +197,6 @@ The MCP server does **not** expose:
With STDIO transport (the default), no additional authentication is required — communication is restricted to the local process pipe.
-For the HTTP-based MCP endpoint exposed by the Aspire dashboard (used in [manual MCP configuration](/dashboard/mcp-server/) for older Aspire versions), an API key (`x-mcp-api-key` header) is required.
-
### Enterprise considerations
For teams evaluating the Aspire MCP server:
@@ -312,5 +314,4 @@ If your AI assistant doesn't detect your AppHost, verify it's running with `aspi
- [Use AI coding agents](/get-started/ai-coding-agents/) — set up your project for AI agents
- [aspire agent mcp command](/reference/cli/commands/aspire-agent-mcp/)
-- [Dashboard MCP server](/dashboard/mcp-server/)
- [Dashboard security considerations](/dashboard/security-considerations/)
diff --git a/src/frontend/src/content/docs/get-started/aspire-sdk-templates.mdx b/src/frontend/src/content/docs/get-started/aspire-sdk-templates.mdx
index f5542002b..e8fe2c63e 100644
--- a/src/frontend/src/content/docs/get-started/aspire-sdk-templates.mdx
+++ b/src/frontend/src/content/docs/get-started/aspire-sdk-templates.mdx
@@ -39,10 +39,12 @@ The following Aspire solution templates are available (assume the solution is na
- **AspireSample.ApiService**: An [ASP.NET Core Minimal API](https://learn.microsoft.com/aspnet/core/fundamentals/minimal-apis) project that provides data to the frontend.
- **AspireSample.Web**: A React-based frontend application with TypeScript configured to work with the Aspire app.
-- **Aspire Starter App with FastAPI and React**: This template provides a Python-based starter app with the following projects:
- - **AspireSample.AppHost**: The orchestrator project for managing the app's services.
- - **AspireSample.ApiService**: A [FastAPI](https://fastapi.tiangolo.com/) Python backend service that provides data to the frontend.
- - **AspireSample.Web**: A React-based frontend application with TypeScript.
+- **Aspire Starter App with FastAPI and React**: This template provides a Python-based starter app using a **TypeScript AppHost**. It includes:
+ - **apphost.ts**: The TypeScript AppHost that orchestrates the app's services using `addUvicornApp`.
+ - **app/**: A [FastAPI](https://fastapi.tiangolo.com/) Python backend service that provides data to the frontend.
+ - **frontend/**: A React-based frontend application with TypeScript.
+
+ The template supports an optional `--use-redis-cache` flag that adds a Redis cache resource to the app. Because the AppHost is TypeScript-based, the .NET SDK is not required to scaffold or run this template.
For more information on Python workloads in Aspire, see [Python integration](/integrations/frameworks/python/) and [Add Aspire to an existing app](/get-started/add-aspire-existing-app/).
diff --git a/src/frontend/src/content/docs/get-started/aspire-sdk.mdx b/src/frontend/src/content/docs/get-started/aspire-sdk.mdx
index 1a083d2db..f654ac15e 100644
--- a/src/frontend/src/content/docs/get-started/aspire-sdk.mdx
+++ b/src/frontend/src/content/docs/get-started/aspire-sdk.mdx
@@ -35,7 +35,7 @@ The `Aspire.AppHost.Sdk` is defined in the top-level `Project` node's `Sdk` attr
The `Aspire.AppHost.Sdk` is defined in a file-based app's source file using the `#:sdk` directive:
```csharp title='C# — file-based app' {1}
-#:sdk Aspire.AppHost.Sdk@13.1.0
+#:sdk Aspire.AppHost.Sdk@13.3.0
var builder = DistributedApplication.CreateBuilder(args);
diff --git a/src/frontend/src/content/docs/get-started/deploy-first-app.mdx b/src/frontend/src/content/docs/get-started/deploy-first-app.mdx
index 2aeb18379..8953f825f 100644
--- a/src/frontend/src/content/docs/get-started/deploy-first-app.mdx
+++ b/src/frontend/src/content/docs/get-started/deploy-first-app.mdx
@@ -4,7 +4,6 @@ description: Learn how to deploy your first Aspire application with either a C#
---
import { Aside, FileTree, Steps, TabItem, Tabs } from '@astrojs/starlight/components';
-import AppHostLangPivot from '@components/AppHostLangPivot.astro';
import { Image } from 'astro:assets';
import { Kbd } from 'starlight-kbd/components';
import Expand from '@components/Expand.astro';
@@ -297,7 +296,7 @@ In the AppHost, chain a call to the appropriate environment API method to config
@@ -368,7 +367,7 @@ In the AppHost, chain a call to the appropriate environment API method to config
@@ -1135,22 +1134,18 @@ To verify that your application is running as expected after deployment, follow
After deploying your application, it's important to clean up resources to avoid incurring unnecessary costs or consuming local system resources.
-
-
- To clean up resources after deploying with Docker Compose, you can stop and remove the running containers using the following command:
+Use `aspire destroy` to tear down the deployed environment. The command discovers the deployment, shows what will be removed, and prompts for confirmation before continuing.
- ```bash title="Aspire CLI - Stop and remove containers"
- aspire do docker-compose-down-env
- ```
-
-
- To clean up resources after deploying to Azure, you can use the Azure CLI to delete the resource group that contains your application. This will remove all resources within the resource group.
+```bash title="Aspire CLI - Destroy the deployed environment"
+aspire destroy
+```
- ```bash title="Azure CLI - Delete resource group"
- az group delete --name --yes --no-wait
- ```
-
-
+
For a deep-dive into the related foundational concepts, see [Pipelines and app topology](/deployment/pipelines/).
@@ -1163,23 +1158,18 @@ You've just built your first Aspire app and deployed it to production. Now you m
After deploying your application, it's important to clean up resources to avoid incurring unnecessary costs or consuming local system resources.
-
-
- To clean up resources after deploying with Docker Compose, you can stop and remove the running containers using the following command:
-
- ```bash title="Aspire CLI - Stop and remove containers"
- aspire do docker-compose-down-env
- ```
-
-
- To clean up resources after deploying to Azure, you can use the Azure CLI to delete the resource group that contains your application. This will remove all resources within the resource group.
+Use `aspire destroy` to tear down the deployed environment. The command discovers the deployment, shows what will be removed, and prompts for confirmation before continuing.
- ```bash title="Azure CLI - Delete resource group"
- az group delete --name --yes --no-wait
- ```
-
+```bash title="Aspire CLI - Destroy the deployed environment"
+aspire destroy
+```
-
+
For a deep-dive into the related foundational concepts, see [Pipelines and app topology](/deployment/pipelines/).
diff --git a/src/frontend/src/content/docs/get-started/install-cli.mdx b/src/frontend/src/content/docs/get-started/install-cli.mdx
index 7da9a2a31..c6ec287a4 100644
--- a/src/frontend/src/content/docs/get-started/install-cli.mdx
+++ b/src/frontend/src/content/docs/get-started/install-cli.mdx
@@ -54,7 +54,7 @@ aspire --version
If that command works, you're presented with the version of the Aspire CLI tool:
```bash title="Aspire CLI — Output" data-disable-copy
-13.2.0+{commitSHA}
+13.3.0+{commitSHA}
```
The `+{commitSHA}` suffix indicates the specific commit from which the Aspire CLI was built.
diff --git a/src/frontend/src/content/docs/get-started/prerequisites.mdx b/src/frontend/src/content/docs/get-started/prerequisites.mdx
index 7a8702b46..ceafcc7fe 100644
--- a/src/frontend/src/content/docs/get-started/prerequisites.mdx
+++ b/src/frontend/src/content/docs/get-started/prerequisites.mdx
@@ -32,9 +32,13 @@ Ready to dive into Aspire? Before you begin, make sure your development environm
- Aspire's TypeScript AppHost requires [Node.js](https://nodejs.org/) 20 or later (LTS recommended) and a package manager such as npm or pnpm.
+ Aspire's TypeScript AppHost requires [Node.js](https://nodejs.org/) 20 or later (LTS recommended) and a compatible package manager. The following package managers are supported: **npm** (included with Node.js), **pnpm**, **Yarn**, and **Bun**. The Aspire CLI auto-detects the active toolchain from your `package.json` `packageManager` field or from lockfiles in your project directory or parent directories.
- Follow the [Node.js installation instructions](https://nodejs.org/) for your operating system (Windows, macOS, or Linux) to complete the setup.
+ Follow the [Node.js installation instructions](https://nodejs.org/) for your operating system (Windows, macOS, or Linux) to complete the setup. If you use an alternative package manager, install it separately:
+
+ - [pnpm](https://pnpm.io/installation)
+ - [Yarn](https://yarnpkg.com/getting-started/install)
+ - [Bun](https://bun.sh/docs/installation)
diff --git a/src/frontend/src/content/docs/hi/index.mdx b/src/frontend/src/content/docs/hi/index.mdx
index e6145843f..a37300a3e 100644
--- a/src/frontend/src/content/docs/hi/index.mdx
+++ b/src/frontend/src/content/docs/hi/index.mdx
@@ -11,7 +11,7 @@ prev: false
next: false
banner:
content: |
- 🚀 Aspire 13.2 रिलीज़ हो गया है! — Aspire 13.2 में नया क्या है देखें।
+ 🚀 Aspire 13.3 रिलीज़ हो गया है! — Aspire 13.3 में नया क्या है देखें।
hero:
tagline: आपका स्टैक, सरल।
फ्रंटएंड, API, कंटेनर और डेटाबेस को आसानी से ऑर्केस्ट्रेट करें—बिना रीराइट, बिना सीमा। किसी भी प्रोजेक्ट को शक्ति देने के लिए Aspire को विस्तारित करें।
image:
diff --git a/src/frontend/src/content/docs/id/index.mdx b/src/frontend/src/content/docs/id/index.mdx
index 5c7fdc2d3..16fb421a9 100644
--- a/src/frontend/src/content/docs/id/index.mdx
+++ b/src/frontend/src/content/docs/id/index.mdx
@@ -11,7 +11,7 @@ prev: false
next: false
banner:
content: |
- 🚀 Aspire 13.2 telah dirilis! — Lihat apa yang baru di Aspire 13.2.
+ 🚀 Aspire 13.3 telah dirilis! — Lihat apa yang baru di Aspire 13.3.
hero:
tagline: Stack Anda, disederhanakan.
Orkestrasi frontend, API, container, dan database dengan mudah—tanpa menulis ulang, tanpa batasan. Perluas Aspire untuk mendukung proyek apa pun.
image:
diff --git a/src/frontend/src/content/docs/index.mdx b/src/frontend/src/content/docs/index.mdx
index 55b19d546..f8c6d3e1e 100644
--- a/src/frontend/src/content/docs/index.mdx
+++ b/src/frontend/src/content/docs/index.mdx
@@ -11,7 +11,7 @@ prev: false
next: false
banner:
content: |
- Aspire 13.2 is here! Get the latest release that brings TypeScript AppHost authoring, an agent-native CLI, and so much more. — Get Aspire 13.2
+ Aspire 13.3 is here! — See what's new
hero:
tagline: Your stack, streamlined.
Orchestrate frontends, APIs, containers, and databases effortlessly—no rewrites, no limits. Extend Aspire to power any project. Free, open-source, and agent ready.
image:
diff --git a/src/frontend/src/content/docs/integrations/cloud/azure/aks/index.mdx b/src/frontend/src/content/docs/integrations/cloud/azure/aks/index.mdx
new file mode 100644
index 000000000..ddd7b18ac
--- /dev/null
+++ b/src/frontend/src/content/docs/integrations/cloud/azure/aks/index.mdx
@@ -0,0 +1,110 @@
+---
+title: Azure Kubernetes Service (AKS) integration
+description: Learn how to add Azure Kubernetes Service deployment support to your Aspire application using the Aspire.Hosting.Azure.Kubernetes hosting integration.
+---
+
+import InstallPackage from '@components/InstallPackage.astro';
+import { Tabs, TabItem } from '@astrojs/starlight/components';
+import LearnMore from '@components/LearnMore.astro';
+
+The Aspire AKS hosting integration enables you to deploy your Aspire application to Azure Kubernetes Service (AKS) with full provisioning. Aspire creates the AKS cluster, Azure Container Registry (ACR), managed identity, and any Azure resources your app depends on — all from your AppHost definition.
+
+## Hosting integration
+
+To get started with the Aspire AKS hosting integration, install the [📦 Aspire.Hosting.Azure.Kubernetes](https://www.nuget.org/packages/Aspire.Hosting.Azure.Kubernetes) NuGet package in the AppHost project:
+
+
+
+## Add AKS environment
+
+After installing the package, add an AKS environment to your AppHost:
+
+
+
+```csharp title="AppHost.cs"
+var builder = DistributedApplication.CreateBuilder(args);
+
+var aks = builder.AddAzureKubernetesEnvironment("aks");
+
+var api = builder.AddProject("api");
+
+builder.Build().Run();
+```
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const aks = await builder.addAzureKubernetesEnvironment('aks');
+
+const api = await builder
+ .addNodeApp('api', './api', 'src/index.ts')
+ .withHttpEndpoint({ env: 'PORT' })
+ .withExternalHttpEndpoints();
+
+await builder.build().run();
+```
+
+
+
+When an AKS environment is present, all compute resources are automatically deployed to AKS — no additional opt-in is required.
+
+## Configure the system node pool
+
+Customize the system node pool VM size and scaling using `WithSystemNodePool`:
+
+
+
+```csharp title="AppHost.cs"
+builder.AddAzureKubernetesEnvironment("aks")
+ .WithSystemNodePool("Standard_D4s_v5", minCount: 1, maxCount: 5);
+```
+
+
+```typescript title="apphost.ts"
+const aks = await builder.addAzureKubernetesEnvironment('aks');
+await aks.withSystemNodePool('Standard_D4s_v5', 1, 5);
+```
+
+
+
+## Add node pools
+
+Add additional node pools for workload isolation, GPU workloads, or specialized hardware requirements:
+
+
+
+```csharp title="AppHost.cs"
+var aks = builder.AddAzureKubernetesEnvironment("aks");
+var gpuPool = aks.AddNodePool("gpupool", "Standard_NC6s_v3", minCount: 0, maxCount: 5);
+
+builder.AddContainer("ml-worker", "my-ml-image")
+ .WithNodePool(gpuPool);
+```
+
+
+```typescript title="apphost.ts"
+const aks = await builder.addAzureKubernetesEnvironment('aks');
+const gpuPool = await aks.addNodePool('gpupool', 'Standard_NC6s_v3', 0, 5);
+
+const worker = await builder.addContainer('ml-worker', 'my-ml-image');
+await worker.withNodePool(gpuPool);
+```
+
+
+
+## Publishing and deployment
+
+The AKS integration supports both `aspire publish` (generate Helm chart and Bicep artifacts) and `aspire deploy` (provision Azure infrastructure and deploy in a single command).
+
+For a complete end-to-end walkthrough, see [Deploy to AKS](/deployment/kubernetes/aks/).
+
+## See also
+
+- [Deploy to AKS](/deployment/kubernetes/aks/)
+- [Deploy to Kubernetes](/deployment/kubernetes/)
+- [Kubernetes integration](/integrations/compute/kubernetes/)
+- [Deploy to Azure](/deployment/azure/)
+- [Azure Kubernetes Service documentation](https://learn.microsoft.com/azure/aks/)
diff --git a/src/frontend/src/content/docs/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-get-started.mdx b/src/frontend/src/content/docs/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-get-started.mdx
index 91734b949..97ad4ca48 100644
--- a/src/frontend/src/content/docs/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-get-started.mdx
+++ b/src/frontend/src/content/docs/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-get-started.mdx
@@ -20,7 +20,7 @@ import aiFoundryIcon from '@assets/icons/azure-ai-foundry-icon.png';
-[Microsoft Foundry](https://learn.microsoft.com/ai-studio) provides a unified platform for developing, testing, and deploying AI applications. The Aspire Microsoft Foundry integration enables you to connect to Microsoft Foundry services from your applications, providing access to various AI capabilities including model deployments, prompt flow, and more.
+[Microsoft Foundry](https://learn.microsoft.com/azure/foundry/what-is-foundry) provides a unified platform for developing, testing, and deploying AI applications. The Aspire Microsoft Foundry integration enables you to connect to Microsoft Foundry services from your applications, providing access to AI capabilities including model deployments, Foundry projects, and hosted agents.
In this introduction, you'll see how to install and use the Aspire Microsoft Foundry integrations in a simple configuration. If you already have this knowledge, see [Microsoft Foundry Hosting integration](/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-host/) for full reference details.
@@ -64,7 +64,7 @@ The preceding code adds a Microsoft Foundry resource named `foundry` with a depl
## Set up client integration
-The client integration for Azure AI Foundry is the [Azure AI Inference](/integrations/cloud/azure/azure-ai-inference/azure-ai-inference-get-started/) integration. To use Azure AI Foundry from your client applications, install the Azure AI Inference integration in your client project.
+The client integration for Microsoft Foundry is the [Azure AI Inference](/integrations/cloud/azure/azure-ai-inference/azure-ai-inference-get-started/) integration. To use Microsoft Foundry from your client applications, install the Azure AI Inference integration in your client project.
For more information on using the client integration, see the [Azure AI Inference integration documentation](/integrations/cloud/azure/azure-ai-inference/azure-ai-inference-get-started/).
diff --git a/src/frontend/src/content/docs/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-host.mdx b/src/frontend/src/content/docs/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-host.mdx
index e31d647d7..04964df05 100644
--- a/src/frontend/src/content/docs/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-host.mdx
+++ b/src/frontend/src/content/docs/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-host.mdx
@@ -3,10 +3,15 @@ title: Microsoft Foundry hosting integration
description: Learn how to use the Aspire Microsoft Foundry hosting integration to create Microsoft Foundry resources.
---
-import { Aside, Badge } from '@astrojs/starlight/components';
+import { Aside, Badge, Tabs, TabItem } from '@astrojs/starlight/components';
import InstallPackage from '@components/InstallPackage.astro';
+import ThemeAwareImage from '@components/ThemeImage.astro';
import { Image } from 'astro:assets';
import aiFoundryIcon from '@assets/icons/azure-ai-foundry-icon.png';
+import agentSendMessage from '@assets/integrations/cloud/azure/azure-ai-foundry/agent-send-message.png';
+import agentSendMessageLight from '@assets/integrations/cloud/azure/azure-ai-foundry/agent-send-message-light.png';
+import agentResponsesResult from '@assets/integrations/cloud/azure/azure-ai-foundry/agent-responses-result.png';
+import agentResponsesResultLight from '@assets/integrations/cloud/azure/azure-ai-foundry/agent-responses-result-light.png';
+
+
```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -48,6 +56,26 @@ builder.AddProject()
// After adding all resources, run the app...
```
+
+
+
+```typescript title="TypeScript — apphost.ts" twoslash
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const foundry = await builder.addFoundry('foundry');
+
+await builder
+ .addProject('api', '../ExampleProject/ExampleProject.csproj')
+ .withReference(foundry);
+
+await builder.build().run();
+```
+
+
+
+
The preceding code adds a Microsoft Foundry resource named `foundry` to the AppHost project. The `WithReference` method passes the connection information to the `ExampleProject` project.
> [!IMPORTANT]
@@ -57,7 +85,10 @@ The preceding code adds a Microsoft Foundry resource named `foundry` to the AppH
To add a Microsoft Foundry deployment resource, call the `AddDeployment` method with one of the generated `FoundryModel` entries:
-```csharp
+
+
+
+```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
var foundry = builder.AddFoundry("foundry");
@@ -71,15 +102,43 @@ builder.AddProject()
// After adding all resources, run the app...
```
+
+
+
+```typescript title="TypeScript — apphost.ts" twoslash
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const foundry = await builder.addFoundry('foundry');
+
+const chat = await foundry.addDeploymentFromModel('chat', {
+ name: 'gpt-5-mini',
+ version: '2025-06-01',
+ format: 'OpenAI',
+});
+
+await builder
+ .addProject('api', '../ExampleProject/ExampleProject.csproj')
+ .withReference(chat)
+ .waitFor(chat);
+
+await builder.build().run();
+```
+
+
+
+
The preceding code:
- Adds a Microsoft Foundry resource named `foundry`.
- Adds a Microsoft Foundry deployment resource named `chat` using the generated `FoundryModel.OpenAI.Gpt5Mini` descriptor.
-For more information about the generated model descriptors, see the [FoundryModel API reference](https://learn.microsoft.com/dotnet/api/aspire.hosting.foundrymodel).
-
If you need a different generated model descriptor, use the corresponding nested type, such as `FoundryModel.Microsoft.Phi4Reasoning`:
+
+
+
```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -94,6 +153,33 @@ builder.AddProject()
builder.Build().Run();
```
+
+
+
+```typescript title="TypeScript — apphost.ts" twoslash
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const foundry = await builder.addFoundry('foundry');
+
+const chat = await foundry.addDeploymentFromModel('chat', {
+ name: 'Phi-4-reasoning',
+ version: '1',
+ format: 'Microsoft',
+});
+
+await builder
+ .addProject('api', '../ExampleProject/ExampleProject.csproj')
+ .withReference(chat)
+ .waitFor(chat);
+
+await builder.build().run();
+```
+
+
+
+
## Configure deployment properties
You can customize deployment properties using the `WithProperties` method:
@@ -113,6 +199,9 @@ The preceding code sets the SKU name to `Standard` and capacity to `10` for the
Use the `AddProject` method to create a Microsoft Foundry project for agents, project-scoped connections, and related Azure resources:
+
+
+
```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -129,12 +218,44 @@ builder.AddProject("api")
builder.Build().Run();
```
+
+
+
+```typescript title="TypeScript — apphost.ts" twoslash
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const foundry = await builder.addFoundry('foundry');
+const project = await foundry.addProject('my-project');
+
+const chat = await project.addModelDeploymentFromModel('chat', {
+ name: 'gpt-5-mini',
+ version: '2025-06-01',
+ format: 'OpenAI',
+});
+
+await builder
+ .addProject('api', '../ExampleProject/ExampleProject.csproj')
+ .withReference(project)
+ .withReference(chat)
+ .waitFor(chat);
+
+await builder.build().run();
+```
+
+
+
+
The preceding code creates a Foundry project and adds a deployment through `AddModelDeployment`. When you call `WithReference(project)`, Aspire injects the standard connection string and project-specific connection properties such as the project endpoint and Application Insights connection string into the consuming resource.
### Configure a Foundry project
You can customize the Azure resources associated with a Foundry project:
+
+
+
```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -149,12 +270,38 @@ var project = foundry.AddProject("my-project")
.WithContainerRegistry(registry);
```
+
+
+
+```typescript title="TypeScript — apphost.ts" twoslash
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const foundry = await builder.addFoundry('foundry');
+const appInsights = await builder.addAzureApplicationInsights('appinsights');
+const keyVault = await builder.addAzureKeyVault('keyvault');
+const registry = await builder.addAzureContainerRegistry('agents');
+
+await foundry
+ .addProject('my-project')
+ .withAppInsights(appInsights)
+ .withKeyVault(keyVault)
+ .withContainerRegistry(registry);
+```
+
+
+
+
`AddProject` creates a default Azure Container Registry for hosted agents. Use `WithContainerRegistry` when you want to point the project at a different registry.
## Publish a hosted agent to Microsoft Foundry
Use `PublishAsHostedAgent` to publish an executable or containerized app as a hosted agent in a Foundry project:
+
+
+
```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -162,7 +309,13 @@ var foundry = builder.AddFoundry("foundry");
var project = foundry.AddProject("my-project");
var chat = project.AddModelDeployment("chat", FoundryModel.OpenAI.Gpt5Mini);
-builder.AddPythonApp("agent", "..\\agent", "main:app")
+builder.AddPythonApp("agent-python", "..\\agent", "main:app")
+ .WithReference(project)
+ .WithReference(chat)
+ .PublishAsHostedAgent(project);
+
+builder.AddProject("agent-dotnet")
+ .WithHttpEndpoint(targetPort: 9000)
.WithReference(project)
.WithReference(chat)
.PublishAsHostedAgent(project);
@@ -170,13 +323,75 @@ builder.AddPythonApp("agent", "..\\agent", "main:app")
builder.Build().Run();
```
-In run mode, `PublishAsHostedAgent` configures local HTTP, liveness, and readiness endpoints and enables OpenTelemetry output for the agent. In publish mode, Aspire creates an `AzureHostedAgentResource` and publishes the container to the project-associated registry.
+
+
+
+```typescript title="TypeScript — apphost.ts" twoslash
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const foundry = await builder.addFoundry('foundry');
+const project = await foundry.addProject('my-project');
+const chat = await project.addModelDeploymentFromModel('chat', {
+ name: 'gpt-5-mini',
+ version: '2025-06-01',
+ format: 'OpenAI',
+});
+
+await builder
+ .addPythonApp('agent-python', '../agent', 'main:app')
+ .withReference(project)
+ .withReference(chat)
+ .publishAsHostedAgent({ project });
+
+await builder
+ .addProject('agent-dotnet', '../Agent/Agent.csproj')
+ .withHttpEndpoint({ targetPort: 9000 })
+ .withReference(project)
+ .withReference(chat)
+ .publishAsHostedAgent({ project });
+
+await builder.build().run();
+```
+
+
+
+
+In run mode, `PublishAsHostedAgent(...)` configures a local `http` endpoint for the agent. Aspire first checks whether the resource already has an endpoint named `http`. If that endpoint has a target port, Aspire preserves it. If there is no existing `http` target port, Aspire defaults to `8088`.
+
+Aspire also injects the selected port as `DEFAULT_AD_PORT`, so hosted-agent apps can bind to the right port:
+
+```csharp
+var port = Environment.GetEnvironmentVariable("DEFAULT_AD_PORT") ?? "8088";
+builder.WebHost.UseUrls($"http://+:{port}");
+```
+
+If your hosted agent listens on a different port, declare the endpoint before calling `PublishAsHostedAgent(...)`:
+
+```csharp
+var dotnetWeatherAgent = builder.AddProject("weather-agent-dotnet")
+ .WithHttpEndpoint(targetPort: 9000)
+ .WithReference(project).WaitFor(project)
+ .WithReference(chat).WaitFor(chat);
+
+dotnetWeatherAgent.PublishAsHostedAgent(project);
+```
+
+In run mode, Aspire also adds dashboard URLs for `/responses`, `/liveness`, and `/readiness`, configures `/liveness` as an HTTP health check, adds a dashboard command for sending a message to `/responses`, and enables OpenTelemetry environment variables for agent instrumentation.
+
+For .NET hosted agents, add your Service Defaults project reference and call `builder.AddServiceDefaults()` in the hosted-agent app so logs, metrics, traces, HTTP instrumentation, and OTLP export flow into the Aspire dashboard. Then enable telemetry on the Foundry Responses chat client that backs the Microsoft Agent Framework (MAF) agent.
+
+In publish mode, Aspire creates an `AzureHostedAgentResource` and publishes the container to the project-associated registry.
If you omit the `project` argument, Aspire uses an existing Foundry project from the app model or creates one automatically.
### Add and publish a prompt agent
-For prompt-only scenarios, use `AddAndPublishPromptAgent` on a Foundry project:
+For prompt-only scenarios, use `AddPromptAgent` on a Foundry project:
+
+
+
```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -185,21 +400,88 @@ var foundry = builder.AddFoundry("foundry");
var project = foundry.AddProject("my-project");
var chat = project.AddModelDeployment("chat", FoundryModel.OpenAI.Gpt5Mini);
-project.AddAndPublishPromptAgent(
- chat,
- "support-agent",
- instructions: "You are a helpful support assistant.");
+var webSearch = project.AddWebSearchTool("web-search");
+
+var webResearcher = project.AddPromptAgent(chat, name: "web-researcher",
+ instructions: """
+ You are the Web Researcher. Use web search for current information,
+ cite sources, summarize tradeoffs, and keep answers concise and practical.
+ """)
+ .WithTool(webSearch);
builder.Build().Run();
```
-This creates an `AzurePromptAgentResource` that uses the specified model deployment in the target Foundry project.
+
+
+
+```typescript title="TypeScript — apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const foundry = await builder.addFoundry('foundry');
+const project = await foundry.addProject('my-project');
+const chat = await project.addModelDeploymentFromModel('chat', {
+ name: 'gpt-5-mini',
+ version: '2025-06-01',
+ format: 'OpenAI',
+});
+
+const webSearch = await project.addWebSearchTool('web-search');
+
+await project
+ .addPromptAgent(
+ chat,
+ 'web-researcher',
+ `
+ You are the Web Researcher. Use web search for current information,
+ cite sources, summarize tradeoffs, and keep answers concise and practical.
+ `
+ )
+ .withTool(webSearch);
+
+await builder.build().run();
+```
+
+
+
+
+Parameter details:
+
+| API | Parameter | Description |
+| --------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `AddPromptAgent(...)` | `model` | The Foundry model deployment resource the prompt agent uses, such as `chat` from `project.AddModelDeployment(...)`. |
+| `AddPromptAgent(...)` | `name` | The Aspire resource name and Foundry agent name. This also drives generated environment variable prefixes, for example `web-researcher` becomes `WEB_RESEARCHER`. |
+| `AddPromptAgent(...)` | `instructions` | Optional system instructions for the prompt agent. Use this to define behavior, tool-use guidance, and response style. |
+| `WithTool(...)` | `tool` | A Foundry tool resource created on the same Foundry project, such as `AddWebSearchTool(...)`, `AddCodeInterpreterTool(...)`, or `AddAISearchTool(...)`. |
+
+Prompt agents are deployed to Azure AI Foundry even during local development. Local apps call the cloud-provisioned agent through the injected project endpoint and agent name.
+
+## Invoke agents from the Aspire dashboard
+
+Aspire also makes the declared agents easy to try from the dashboard. Prompt agents get a **Send Message** command from `AddPromptAgent(...)`. Hosted agents published with `PublishAsHostedAgent(...)` get a highlighted **Send Message** command that posts to the local `/responses` endpoint, plus dashboard links for `/responses`, `/liveness`, and `/readiness`.
+
+
+
+
## Connect to an existing Microsoft Foundry service
You might have an existing Microsoft Foundry service that you want to connect to. You can chain a call to annotate that your `FoundryResource` is an existing resource:
-```csharp
+
+
+
+```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
var existingFoundryName = builder.AddParameter("existingFoundryName");
@@ -214,6 +496,33 @@ builder.AddProject()
// After adding all resources, run the app...
```
+
+
+
+```typescript title="TypeScript — apphost.ts" twoslash
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const existingFoundryName = await builder.addParameter('existingFoundryName');
+const existingFoundryResourceGroup = await builder.addParameter(
+ 'existingFoundryResourceGroup'
+);
+
+const foundry = await builder
+ .addFoundry('foundry')
+ .asExisting(existingFoundryName, existingFoundryResourceGroup);
+
+await builder
+ .addProject('api', '../ExampleProject/ExampleProject.csproj')
+ .withReference(foundry);
+
+await builder.build().run();
+```
+
+
+
+
> [!IMPORTANT]
> When you call `RunAsExisting`, `PublishAsExisting`, or `AsExisting` methods to work with resources that are already present in your Azure subscription, you must add certain configuration values to your AppHost to ensure that Aspire can locate them. The necessary configuration values include **SubscriptionId**, **AllowResourceGroupCreation**, **ResourceGroup**, and **Location**. If you don't set them, "Missing configuration" errors appear in the Aspire dashboard. For more information about how to set them, see [Use existing Azure resources](/integrations/cloud/azure/overview/#use-existing-azure-resources).
@@ -221,6 +530,9 @@ builder.AddProject()
Aspire supports the usage of Foundry Local for local development. Add the following to your AppHost project:
+
+
+
```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -236,6 +548,33 @@ builder.AddProject()
// After adding all resources, run the app...
```
+
+
+
+```typescript title="TypeScript — apphost.ts" twoslash
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const foundry = await builder.addFoundry('foundry').runAsFoundryLocal();
+
+const chat = await foundry.addDeploymentFromModel('chat', {
+ name: 'Phi-4',
+ version: '1',
+ format: 'Microsoft',
+});
+
+await builder
+ .addProject('api', '../ExampleProject/ExampleProject.csproj')
+ .withReference(chat)
+ .waitFor(chat);
+
+await builder.build().run();
+```
+
+
+
+
When the AppHost starts up, the local foundry service is also started. This requires the local machine to have [Foundry Local](https://learn.microsoft.com/azure/ai-foundry/foundry-local/get-started) installed and running.
The `RunAsFoundryLocal` method configures the resource to run as an emulator. It downloads and loads the specified models locally. The method provides health checks for the local service and automatically manages the Foundry Local lifecycle.
@@ -250,14 +589,42 @@ The `RunAsFoundryLocal` method configures the resource to run as an emulator. It
You can assign specific roles to resources that need to access the Microsoft Foundry service. Use the `WithRoleAssignments` method:
+
+
+
```csharp title="C# — AppHost.cs"
+var builder = DistributedApplication.CreateBuilder(args);
+
var foundry = builder.AddFoundry("chat");
builder.AddProject("api")
.WithRoleAssignments(foundry, CognitiveServicesBuiltInRole.CognitiveServicesUser)
.WithReference(foundry);
+
+builder.Build().Run();
```
+
+
+
+```typescript title="TypeScript — apphost.ts" twoslash
+import { createBuilder, FoundryRole } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const foundry = await builder.addFoundry('chat');
+
+await builder
+ .addProject('api', '../Api/Api.csproj')
+ .withRoleAssignments(foundry, [FoundryRole.CognitiveServicesUser])
+ .withReference(foundry);
+
+await builder.build().run();
+```
+
+
+
+
The preceding code assigns the `CognitiveServicesUser` role to the `api` project, granting it the necessary permissions to access the Microsoft Foundry resource.
## Provisioning-generated Bicep
diff --git a/src/frontend/src/content/docs/integrations/cloud/azure/azure-front-door.mdx b/src/frontend/src/content/docs/integrations/cloud/azure/azure-front-door.mdx
new file mode 100644
index 000000000..3877141ba
--- /dev/null
+++ b/src/frontend/src/content/docs/integrations/cloud/azure/azure-front-door.mdx
@@ -0,0 +1,206 @@
+---
+title: Azure Front Door
+description: Learn how to use the Aspire Azure Front Door hosting integration to put a global edge in front of your application's backends.
+---
+
+import { Aside } from '@astrojs/starlight/components';
+import InstallPackage from '@components/InstallPackage.astro';
+import { Image } from 'astro:assets';
+import azureIcon from '@assets/icons/azure-front-door-icon.png';
+
+
+
+[Azure Front Door](https://learn.microsoft.com/azure/frontdoor/) is a global, scalable entry point that uses Microsoft's edge network to deliver fast, secure, and highly available web applications. The Aspire Azure Front Door hosting integration provisions a Front Door profile and routes traffic to your backends.
+
+## Hosting integration
+
+The Aspire Azure Front Door hosting integration models the resource as the following type:
+
+- `AzureFrontDoorResource`: Represents an Azure Front Door profile, including its endpoints, origin groups, origins, and routes.
+
+To access this type and APIs, add the [📦 Aspire.Hosting.Azure.FrontDoor](https://www.nuget.org/packages/Aspire.Hosting.Azure.FrontDoor) NuGet package to your AppHost project.
+
+
+
+### Add an Azure Front Door resource
+
+In your AppHost, call `AddAzureFrontDoor` to add a Front Door profile, then call `WithOrigin` to attach a backend resource as an origin:
+
+```csharp title="C# — AppHost.cs"
+var builder = DistributedApplication.CreateBuilder(args);
+
+builder.AddAzureAppServiceEnvironment("production");
+
+var api = builder.AddProject("api")
+ .WithExternalHttpEndpoints();
+
+builder.AddAzureFrontDoor("frontdoor")
+ .WithOrigin(api);
+
+// After adding all resources, run the app...
+builder.Build().Run();
+```
+
+The preceding code:
+
+- Adds an Azure App Service environment (a compute environment is required so Front Door can resolve the origin's hostname).
+- Adds an `api` project and exposes it externally with `WithExternalHttpEndpoints`. Front Door requires an origin to expose an external HTTP or HTTPS endpoint.
+- Adds a Front Door profile named `frontdoor` and configures the `api` project as an origin.
+
+Each call to `WithOrigin` creates a separate Front Door endpoint, origin group, origin, and route, so each backend is independently routable through its own `*.azurefd.net` hostname.
+
+
+
+### Add multiple origins
+
+You can add multiple backends to the same Front Door profile. Each origin gets its own Front Door endpoint and route:
+
+```csharp title="C# — AppHost.cs"
+var builder = DistributedApplication.CreateBuilder(args);
+
+builder.AddAzureAppServiceEnvironment("production");
+
+var api = builder.AddProject("api")
+ .WithExternalHttpEndpoints();
+
+var web = builder.AddProject("web")
+ .WithExternalHttpEndpoints();
+
+builder.AddAzureFrontDoor("frontdoor")
+ .WithOrigin(api)
+ .WithOrigin(web);
+
+builder.Build().Run();
+```
+
+Each origin must be added at most once per Front Door resource — adding the same resource twice throws `InvalidOperationException`.
+
+### Health probes
+
+Front Door uses health probes to determine which origins are healthy. By default, the Aspire integration probes the origin's external endpoint at path `/` over HTTPS. If you have configured an HTTP probe on the origin (for example with `WithHttpProbe`), Front Door uses the path and protocol from the resource's liveness probe instead.
+
+### Provisioning-generated Bicep
+
+When you publish your app, Aspire generates Bicep alongside the manifest. A Front Door resource with a single origin generates Bicep similar to the following:
+
+```bicep title="Generated Bicep — frontdoor.bicep"
+@description('The location for the resource(s) to be deployed.')
+param location string = resourceGroup().location
+
+param my_api_host string
+
+resource frontdoor 'Microsoft.Cdn/profiles@2025-06-01' = {
+ name: take('frontdoor-${uniqueString(resourceGroup().id)}', 90)
+ location: 'Global'
+ sku: {
+ name: 'Standard_AzureFrontDoor'
+ }
+ tags: {
+ 'aspire-resource-name': 'frontdoor'
+ }
+}
+
+resource my_apiEndpoint 'Microsoft.Cdn/profiles/afdEndpoints@2025-06-01' = {
+ name: take('my-api-${uniqueString(resourceGroup().id)}', 46)
+ location: 'Global'
+ parent: frontdoor
+}
+
+resource my_apiOriginGroup 'Microsoft.Cdn/profiles/originGroups@2025-06-01' = {
+ name: take('my-api-og-${uniqueString(resourceGroup().id)}', 90)
+ properties: {
+ healthProbeSettings: {
+ probePath: '/'
+ probeProtocol: 'Https'
+ }
+ loadBalancingSettings: {
+ sampleSize: 4
+ successfulSamplesRequired: 3
+ additionalLatencyInMilliseconds: 50
+ }
+ }
+ parent: frontdoor
+}
+
+resource my_apiOrigin 'Microsoft.Cdn/profiles/originGroups/origins@2025-06-01' = {
+ name: take('my-api-origin-${uniqueString(resourceGroup().id)}', 90)
+ properties: {
+ hostName: my_api_host
+ originHostHeader: my_api_host
+ }
+ parent: my_apiOriginGroup
+}
+
+resource my_apiRoute 'Microsoft.Cdn/profiles/afdEndpoints/routes@2025-06-01' = {
+ name: take('my-api-route-${uniqueString(resourceGroup().id)}', 90)
+ properties: {
+ forwardingProtocol: 'HttpsOnly'
+ httpsRedirect: 'Enabled'
+ linkToDefaultDomain: 'Enabled'
+ originGroup: {
+ id: my_apiOriginGroup.id
+ }
+ patternsToMatch: [
+ '/*'
+ ]
+ }
+ parent: my_apiEndpoint
+ dependsOn: [
+ my_apiOrigin
+ ]
+}
+
+output my_api_endpointUrl string = 'https://${my_apiEndpoint.properties.hostName}'
+```
+
+Aspire passes the origin's hostname (resolved from the compute environment, not the full URL) into the `my_api_host` parameter so the Front Door origin points at the deployed backend.
+
+The generated Bicep is a starting point and is influenced by changes to the provisioning infrastructure in C#. Customizations to the Bicep file directly are overwritten, so make changes through the C# provisioning APIs to ensure they're reflected in the generated files.
+
+#### Customize provisioning infrastructure
+
+For advanced scenarios such as upgrading to the Premium SKU, attaching a Web Application Firewall (WAF) policy, configuring caching, or tuning routes, use `ConfigureInfrastructure` to modify the generated Bicep model directly:
+
+```csharp title="C# — AppHost.cs"
+using Azure.Provisioning.Cdn;
+
+var frontDoor = builder.AddAzureFrontDoor("frontdoor")
+ .WithOrigin(api)
+ .ConfigureInfrastructure(infra =>
+ {
+ var resources = infra.GetProvisionableResources();
+
+ // Upgrade to Premium SKU (required for WAF).
+ var profile = resources.OfType().Single();
+ profile.SkuName = CdnSkuName.PremiumAzureFrontDoor;
+
+ // Enable caching on the route.
+ var route = resources.OfType().Single();
+ route.CacheConfiguration = new FrontDoorRouteCacheConfiguration
+ {
+ QueryStringCachingBehavior = FrontDoorQueryStringCachingBehavior.IgnoreQueryString
+ };
+ });
+```
+
+For more information, see [Customize Azure resources](/integrations/cloud/azure/customize-resources/). For the full list of configurable properties, see the [Azure.Provisioning.Cdn](https://learn.microsoft.com/dotnet/api/azure.provisioning.cdn) and [Azure.Provisioning.FrontDoor](https://learn.microsoft.com/dotnet/api/azure.provisioning.frontdoor) API documentation.
+
+## See also
+
+- [Azure Front Door documentation](https://learn.microsoft.com/azure/frontdoor/)
+- [Azure Front Door tier comparison](https://learn.microsoft.com/azure/frontdoor/standard-premium/tier-comparison)
+- [Azure App Service integration](/integrations/cloud/azure/azure-app-service/azure-app-service-host/)
+- [Azure integration overview](/integrations/cloud/azure/overview/)
diff --git a/src/frontend/src/content/docs/integrations/cloud/azure/azure-functions/azure-functions-host.mdx b/src/frontend/src/content/docs/integrations/cloud/azure/azure-functions/azure-functions-host.mdx
index 883de0153..3a659ddca 100644
--- a/src/frontend/src/content/docs/integrations/cloud/azure/azure-functions/azure-functions-host.mdx
+++ b/src/frontend/src/content/docs/integrations/cloud/azure/azure-functions/azure-functions-host.mdx
@@ -165,3 +165,81 @@ builder.Build().Run();
```
The preceding code adds an Azure Storage resource to the AppHost and references it in the Azure Functions project. The `blobs` resource is added to the `storage` resource and then referenced by the `functions` resource. The connection information required to connect to the `blobs` resource is automatically injected into the Azure Functions project and enables the project to define a `BlobTrigger` that relies on `blobs` resource.
+
+## Durable Task scheduler integration
+
+:::caution[Experimental APIs]
+The Durable Task scheduler and task hub APIs are experimental in this release. They are marked with the `[Experimental("ASPIREDURABLETASK001")]` attribute and may change before becoming generally available (GA).
+
+To use these APIs without compiler warnings, suppress the `ASPIREDURABLETASK001` diagnostic. For suppression options, see [ASPIREDURABLETASK001](/diagnostics/aspiredurabletask001/).
+:::
+
+The `Aspire.Hosting.Azure.Functions` package includes support for [Azure Durable Task Scheduler](https://learn.microsoft.com/azure/azure-functions/durable/durable-task-scheduler/durable-task-scheduler), which provides a managed backend for Durable Functions orchestrations. You can start a local DTS emulator or connect to an existing scheduler instance. Use `AddDurableTaskScheduler` to add a scheduler resource and `AddTaskHub` to associate a task hub with it.
+
+### Add a Durable Task scheduler and task hub
+
+Call `AddDurableTaskScheduler` on the `builder` instance to add a scheduler resource, then call `AddTaskHub` to create a task hub and pass its reference to the Azure Functions project:
+
+```csharp title="C# — AppHost.cs"
+#pragma warning disable ASPIREDURABLETASK001
+
+var builder = DistributedApplication.CreateBuilder(args);
+
+var storage = builder.AddAzureStorage("storage")
+ .RunAsEmulator();
+
+var scheduler = builder.AddDurableTaskScheduler("scheduler")
+ .RunAsEmulator();
+
+var taskHub = scheduler.AddTaskHub("taskhub");
+
+builder.AddAzureFunctionsProject("functions")
+ .WithHostStorage(storage)
+ .WithReference(taskHub);
+
+builder.Build().Run();
+
+#pragma warning restore ASPIREDURABLETASK001
+```
+
+In the preceding example:
+
+- `AddDurableTaskScheduler` adds a `DurableTaskSchedulerResource` to the app model.
+- `RunAsEmulator` configures the scheduler to use the local Durable Task emulator for development and testing.
+- `AddTaskHub` creates a `DurableTaskHubResource` associated with the scheduler.
+- `WithReference(taskHub)` injects the task hub connection information into the Azure Functions project.
+
+When using `RunAsEmulator()`, Aspire starts a local container running the DTS emulator and automatically provides:
+
+- A **Scheduler Dashboard** URL on the scheduler resource.
+- A **Task Hub Dashboard** URL on each task hub resource.
+- A `DTS_TASK_HUB_NAMES` environment variable on the emulator container listing the associated task hub names.
+
+### Use an existing scheduler
+
+To connect to an already-deployed Durable Task Scheduler instance instead of provisioning a new one, use `RunAsExisting`. The `RunAsExisting` overload accepts either a plain `string` connection string or an `IResourceBuilder` for securely supplying the value at runtime:
+
+```csharp title="C# — AppHost.cs"
+#pragma warning disable ASPIREDURABLETASK001
+
+var builder = DistributedApplication.CreateBuilder(args);
+
+var connectionString = builder.AddParameter("dts-connection-string", secret: true);
+
+var scheduler = builder.AddDurableTaskScheduler("scheduler")
+ .RunAsExisting(connectionString);
+
+var taskHubNameParam = builder.AddParameter("taskhub-name");
+var taskHub = scheduler.AddTaskHub("taskhub").WithTaskHubName(taskHubNameParam);
+
+builder.AddAzureFunctionsProject("functions")
+ .WithReference(taskHub);
+
+builder.Build().Run();
+
+#pragma warning restore ASPIREDURABLETASK001
+```
+
+
diff --git a/src/frontend/src/content/docs/integrations/cloud/azure/azure-virtual-network.mdx b/src/frontend/src/content/docs/integrations/cloud/azure/azure-virtual-network.mdx
index dc9f5fd6a..c678fe144 100644
--- a/src/frontend/src/content/docs/integrations/cloud/azure/azure-virtual-network.mdx
+++ b/src/frontend/src/content/docs/integrations/cloud/azure/azure-virtual-network.mdx
@@ -1,6 +1,6 @@
---
title: Azure Virtual Network
-description: Learn how to use the Azure Virtual Network integration to configure virtual networks, subnets, NAT gateways, network security groups, and private endpoints.
+description: Learn how to use the Azure Virtual Network integration to configure virtual networks, subnets, NAT gateways, network security groups, private endpoints, and network security perimeters.
---
import InstallPackage from '@components/InstallPackage.astro';
@@ -28,6 +28,7 @@ The Azure Virtual Network hosting integration models network resources as the fo
- `AzureNetworkSecurityGroupResource`: Represents a network security group for traffic control.
- `AzureNatGatewayResource`: Represents a NAT gateway for deterministic outbound IP addresses.
- `AzurePublicIPAddressResource`: Represents a public IP address.
+- `AzureNetworkSecurityPerimeterResource`: Represents an Azure Network Security Perimeter for PaaS service isolation.
To access these types and APIs, add the [📦 Aspire.Hosting.Azure.Network](https://www.nuget.org/packages/Aspire.Hosting.Azure.Network) NuGet package in the [AppHost](/get-started/app-host/) project:
@@ -224,6 +225,126 @@ storage.ConfigureInfrastructure(infra =>
[Azure SQL Server](/integrations/cloud/azure/azure-sql-database/azure-sql-database-get-started/) uses a deployment script to grant your application's managed identity access to the SQL database. When you add a private endpoint for a SQL Server resource, the deployment script needs to run inside the virtual network via Azure Container Instances (ACI). Aspire automatically creates a delegated subnet and a storage account for the deployment script container. For details on customizing this behavior, see [Admin deployment script](/integrations/cloud/azure/azure-sql-database/azure-sql-database-host/#admin-deployment-script).
+##### Azure Container Registry
+
+[Azure Container Registry](/integrations/cloud/azure/azure-container-registry/azure-container-registry-host/) requires the **Premium** SKU to support private endpoints. When you add a private endpoint for an Azure Container Registry resource, Aspire automatically upgrades the SKU to Premium.
+
+
+
+### Add a network security perimeter
+
+[Azure Network Security Perimeters (NSPs)](https://learn.microsoft.com/azure/private-link/network-security-perimeter-concepts) provide a logical security boundary for Azure PaaS services such as Storage, Key Vault, Cosmos DB, and SQL. Where virtual networks and private endpoints secure connectivity at the infrastructure layer, NSPs operate at the PaaS layer—grouping resources so they can communicate with each other while restricting public access via access rules.
+
+#### Why use a network security perimeter?
+
+NSPs complement private endpoints with a higher-level grouping model. Instead of configuring a private endpoint for every resource pair, you place all related PaaS resources inside a single perimeter. Resources within the perimeter communicate freely, and you define inbound and outbound access rules that apply to the entire boundary. This is useful when:
+
+- You have many PaaS resources that need to talk to each other but shouldn't be publicly accessible.
+- You want a single set of access rules (IP ranges, subscription allow-lists, FQDN allow-lists) that apply uniformly to the group.
+- You want to audit traffic before enforcing restrictions by using **Learning** mode.
+
+#### Create a perimeter
+
+Call `AddNetworkSecurityPerimeter` on the `builder` instance to create an NSP with a default profile:
+
+```csharp title="C# — AppHost.cs"
+var builder = DistributedApplication.CreateBuilder(args);
+
+var nsp = builder.AddNetworkSecurityPerimeter("my-nsp");
+
+// After adding all resources, run the app...
+builder.Build().Run();
+```
+
+#### Add access rules
+
+Access rules control traffic flowing into and out of the perimeter. Call `WithAccessRule` and pass an `AzureNspAccessRule` to define a rule:
+
+```csharp title="C# — AppHost.cs"
+var nsp = builder.AddNetworkSecurityPerimeter("my-nsp")
+ .WithAccessRule(new AzureNspAccessRule
+ {
+ Name = "allow-my-ip",
+ Direction = NetworkSecurityPerimeterAccessRuleDirection.Inbound,
+ AddressPrefixes = { "203.0.113.0/24" }
+ })
+ .WithAccessRule(new AzureNspAccessRule
+ {
+ Name = "allow-outbound-fqdn",
+ Direction = NetworkSecurityPerimeterAccessRuleDirection.Outbound,
+ FullyQualifiedDomainNames = { "*.blob.core.windows.net" }
+ });
+```
+
+The following properties are available on `AzureNspAccessRule`:
+
+| Property | Direction | Description |
+|---|---|---|
+| `AddressPrefixes` | Inbound | CIDR ranges allowed to reach the perimeter. |
+| `Subscriptions` | Inbound | Subscription resource IDs (format: `/subscriptions/{id}`) allowed access. |
+| `FullyQualifiedDomainNames` | Outbound | FQDNs that resources inside the perimeter can communicate with. |
+
+Each property also has a corresponding `*References` variant (for example, `AddressPrefixReferences`) that accepts `ReferenceExpression` values resolved at deploy time.
+
+
+
+#### Associate resources with a perimeter
+
+Call `WithNetworkSecurityPerimeter` on a PaaS resource builder to associate it with an NSP:
+
+```csharp title="C# — AppHost.cs"
+var nsp = builder.AddNetworkSecurityPerimeter("my-nsp");
+
+var storage = builder.AddAzureStorage("storage")
+ .WithNetworkSecurityPerimeter(nsp);
+var keyVault = builder.AddAzureKeyVault("kv")
+ .WithNetworkSecurityPerimeter(nsp);
+```
+
+Once associated, resources within the perimeter can communicate with each other, while public access is governed by the perimeter's access rules.
+
+The following Azure resources support NSP association:
+
+- [Azure AI Foundry](/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-get-started/)
+- [Azure AI Search](/integrations/cloud/azure/azure-ai-search/azure-ai-search-get-started/)
+- [Azure Cosmos DB](/integrations/cloud/azure/azure-cosmos-db/azure-cosmos-db-get-started/)
+- [Azure Event Hubs](/integrations/cloud/azure/azure-event-hubs/azure-event-hubs-get-started/)
+- [Azure Key Vault](/integrations/cloud/azure/azure-key-vault/azure-key-vault-get-started/)
+- [Azure Log Analytics](/integrations/cloud/azure/azure-log-analytics/)
+- [Azure OpenAI](/integrations/cloud/azure/azure-openai/azure-openai-get-started/)
+- [Azure Service Bus](/integrations/cloud/azure/azure-service-bus/azure-service-bus-get-started/)
+- [Azure SQL Server](/integrations/cloud/azure/azure-sql-database/azure-sql-database-get-started/)
+- [Azure Storage](/integrations/cloud/azure/azure-storage-blobs/azure-storage-blobs-get-started/)
+
+#### Enforced vs. Learning mode
+
+By default, associations use **Enforced** mode—resources within the perimeter can communicate with each other, and any public traffic that doesn't match an access rule is blocked. You can switch to **Learning** mode to log violations without blocking traffic, which is helpful when onboarding resources to identify which access rules are needed before switching to enforced mode:
+
+```csharp title="C# — AppHost.cs"
+// Enforced mode (default) — blocks traffic that violates the rules
+storage.WithNetworkSecurityPerimeter(nsp);
+
+// Learning mode — logs violations without blocking
+keyVault.WithNetworkSecurityPerimeter(
+ nsp, NetworkSecurityPerimeterAssociationAccessMode.Learning);
+```
+
+
+
## Complete example
The following example demonstrates a complete network topology with a virtual network, subnets, NSG rules, a NAT gateway, private endpoints, and an Azure Container Apps environment:
@@ -293,5 +414,6 @@ builder.Build().Run();
- [Azure Virtual Network documentation](https://learn.microsoft.com/azure/virtual-network/)
- [Azure NAT Gateway documentation](https://learn.microsoft.com/azure/nat-gateway/)
- [Azure Private Link documentation](https://learn.microsoft.com/azure/private-link/)
+- [Azure Network Security Perimeter documentation](https://learn.microsoft.com/azure/private-link/network-security-perimeter-concepts)
- [Configure Azure Container Apps environments](/integrations/cloud/azure/configure-container-apps/)
- [Customize Azure resources](/integrations/cloud/azure/customize-resources/)
diff --git a/src/frontend/src/content/docs/integrations/cloud/azure/local-provisioning.mdx b/src/frontend/src/content/docs/integrations/cloud/azure/local-provisioning.mdx
index 20c2c1dfa..b11a3ba55 100644
--- a/src/frontend/src/content/docs/integrations/cloud/azure/local-provisioning.mdx
+++ b/src/frontend/src/content/docs/integrations/cloud/azure/local-provisioning.mdx
@@ -101,6 +101,7 @@ The following configuration options are available:
| `Azure:ResourceGroupPrefix` | Prefix used when Aspire generates a resource group name | `rg-aspire` |
| `Azure:CredentialSource` | Authentication method for local provisioning | `Default` |
| `Azure:AllowResourceGroupCreation` | Allow creating resource groups | `true` |
+| `Azure:CredentialProcessTimeoutSeconds` | Timeout in seconds for credential subprocess operations (must be between 5 and 600) | Azure SDK default |
## Resource group management
@@ -192,6 +193,22 @@ If you want to force a specific local credential, set `Azure:CredentialSource` t
| `AzurePowerShell` | Azure PowerShell sign-in |
| `InteractiveBrowser` | Browser-based sign-in prompt |
+### Credential process timeout
+
+Most local credential sources (Azure CLI, Azure PowerShell, Visual Studio, and Azure Developer CLI) acquire tokens by invoking a subprocess. On machines where these tools are slow to respond — for example, due to antivirus scanning or network latency — credential validation can time out before authentication completes.
+
+Use `Azure:CredentialProcessTimeoutSeconds` to override the credential subprocess timeout. The value must be between **5** and **600** seconds. When unset, the Azure SDK's default timeout is used.
+
+```json title="appsettings.Development.json"
+{
+ "Azure": {
+ "CredentialProcessTimeoutSeconds": 120
+ }
+}
+```
+
+This setting applies to the `AzureCli`, `AzurePowerShell`, `VisualStudio`, and `AzureDeveloperCli` credential sources, and to the development credential chain used when `CredentialSource` isn't explicitly set. It has no effect on `InteractiveBrowser` or `ManagedIdentity` credentials, which don't shell out to a subprocess.
+
## Provisioned resource naming
Aspire generates unique resource names following Azure naming conventions:
diff --git a/src/frontend/src/content/docs/integrations/compute/docker.mdx b/src/frontend/src/content/docs/integrations/compute/docker.mdx
index 3e1176c0b..3f5984ddb 100644
--- a/src/frontend/src/content/docs/integrations/compute/docker.mdx
+++ b/src/frontend/src/content/docs/integrations/compute/docker.mdx
@@ -4,7 +4,7 @@ description: Learn how to use the Aspire Docker hosting integration to deploy yo
---
import InstallPackage from '@components/InstallPackage.astro';
-import { Aside } from '@astrojs/starlight/components';
+import { Aside, Tabs, TabItem } from '@astrojs/starlight/components';
import { Image } from 'astro:assets';
import dockerIcon from '@assets/icons/docker.svg';
import OsAwareTabs from '@components/OsAwareTabs.astro';
@@ -23,7 +23,9 @@ The Aspire Docker hosting integration enables you to deploy your Aspire applicat
- Generating Docker Compose files from your app model for deployment
- Orchestrating multiple services, including an Aspire dashboard for telemetry visualization
- Configuring environment variables and service dependencies
+- Customizing generated Docker Compose files, `.env` files, dashboards, and service definitions
- Managing container networking and service discovery
+- Building images from existing or programmatically generated Dockerfiles
## Hosting integration
@@ -33,36 +35,69 @@ The Docker hosting integration is available in the [📦 Aspire.Hosting.Docker](
### Add Docker Compose environment resource
-The following example demonstrates how to add a Docker Compose environment to your app model using the `AddDockerComposeEnvironment` method:
+The following example demonstrates how to add a Docker Compose environment to your app model:
-```csharp title="C# — AppHost.cs"
+
+
+```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
var compose = builder.AddDockerComposeEnvironment("compose");
var cache = builder.AddRedis("cache")
- .PublishAsDockerComposeService((resource, service) =>
- {
- service.Name = "redis";
- });
+ .PublishAsDockerComposeService((resource, service) =>
+ {
+ service.Name = "redis";
+ });
var api = builder.AddProject("api")
- .WithReference(cache)
- .PublishAsDockerComposeService((resource, service) =>
- {
- service.Name = "api";
- });
+ .WithReference(cache)
+ .PublishAsDockerComposeService((resource, service) =>
+ {
+ service.Name = "api";
+ });
var web = builder.AddProject("web")
- .WithReference(cache)
- .WithReference(api)
- .PublishAsDockerComposeService((resource, service) =>
- {
- service.Name = "web";
- });
+ .WithReference(cache)
+ .WithReference(api)
+ .PublishAsDockerComposeService((resource, service) =>
+ {
+ service.Name = "web";
+ });
builder.Build().Run();
```
+
+
+```typescript title="apphost.ts" twoslash
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+await builder.addDockerComposeEnvironment("compose");
+
+const cache = await builder.addRedis("cache");
+await cache.publishAsDockerComposeService(async (resource, service) => {
+ await service.name.set("redis");
+});
+
+const api = await builder.addProject("api", "../Api/Api.csproj", "http");
+await api.withReference(cache);
+await api.publishAsDockerComposeService(async (resource, service) => {
+ await service.name.set("api");
+});
+
+const web = await builder.addProject("web", "../Web/Web.csproj", "http");
+await web.withReference(cache);
+await web.withReference(api);
+await web.publishAsDockerComposeService(async (resource, service) => {
+ await service.name.set("web");
+});
+
+await builder.build().run();
+```
+
+
The preceding code:
@@ -83,13 +118,31 @@ When a Docker Compose environment is present, all resources are automatically pu
You can configure various properties of the Docker Compose environment using the `WithProperties` method:
-```csharp
+
+
+```csharp title="AppHost.cs"
builder.AddDockerComposeEnvironment("compose")
- .WithProperties(env =>
- {
- env.DashboardEnabled = true;
- });
+ .WithProperties(env =>
+ {
+ env.DashboardEnabled = true;
+ });
+```
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const compose = await builder.addDockerComposeEnvironment("compose");
+await compose.withProperties(async (environment) => {
+ await environment.dashboardEnabled.set(true);
+});
+
+await builder.build().run();
```
+
+
The `DashboardEnabled` property determines whether to include an Aspire dashboard for telemetry visualization in this environment.
@@ -97,37 +150,116 @@ The `DashboardEnabled` property determines whether to include an Aspire dashboar
You can customize the generated Docker Compose file using the `ConfigureComposeFile` method:
-```csharp
+
+
+```csharp title="AppHost.cs"
builder.AddDockerComposeEnvironment("compose")
- .ConfigureComposeFile(composeFile =>
- {
- composeFile.Networks.Add("custom-network", new()
- {
- Driver = "bridge"
- });
- });
+ .ConfigureComposeFile(composeFile =>
+ {
+ composeFile.Name = "my-app";
+
+ var api = composeFile.Services["api"];
+ api.PullPolicy = "always";
+ });
+```
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const compose = await builder.addDockerComposeEnvironment("compose");
+await compose.configureComposeFile(async (composeFile) => {
+ await composeFile.name.set("my-app");
+
+ const api = await composeFile.services.get("api");
+ await api.pullPolicy.set("always");
+});
+
+await builder.build().run();
```
+
+
+
+The `ConfigureComposeFile` callback runs after Aspire generates the Docker Compose model and before the `docker-compose.yml` file is written.
### Add Aspire dashboard resource to environment
The Docker hosting integration includes an Aspire dashboard for telemetry visualization. You can configure or disable it using the `WithDashboard` method:
-```csharp
+
+
+```csharp title="AppHost.cs"
// Enable dashboard with custom configuration
builder.AddDockerComposeEnvironment("compose")
- .WithDashboard(dashboard =>
- {
- dashboard.WithHostPort(8080)
- .WithForwardedHeaders(enabled: true);
- });
+ .WithDashboard(dashboard =>
+ {
+ dashboard.WithHostPort(8080)
+ .WithForwardedHeaders(enabled: true);
+ });
// Disable dashboard
builder.AddDockerComposeEnvironment("compose")
- .WithDashboard(enabled: false);
+ .WithDashboard(enabled: false);
+```
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const compose = await builder.addDockerComposeEnvironment("compose");
+await compose.configureDashboard(async (dashboard) => {
+ await dashboard.withHostPort({ port: 8080 });
+ await dashboard.withForwardedHeaders({ enabled: true });
+});
+
+await compose.withDashboard({ enabled: false });
+
+await builder.build().run();
```
+
+
The `WithHostPort` method configures the port used to access the Aspire dashboard from a browser. The `WithForwardedHeaders` method enables forwarded headers processing when the dashboard is accessed through a reverse proxy or load balancer.
+### Resolve Docker Compose service host names
+
+Use `GetHostAddressExpression` to get the Docker Compose host name that another service should use for an endpoint. In Docker Compose deployments, this expression resolves to the generated service name on the Compose network.
+
+
+
+```csharp title="AppHost.cs"
+var builder = DistributedApplication.CreateBuilder(args);
+
+var compose = builder.AddDockerComposeEnvironment("compose");
+
+var api = builder.AddContainer("api", "nginx", "alpine")
+ .WithHttpEndpoint(name: "http", targetPort: 80);
+
+var apiHost = compose.Resource.GetHostAddressExpression(
+ api.GetEndpoint("http"));
+```
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const compose = await builder.addDockerComposeEnvironment("compose");
+
+const api = await builder.addContainer("api", "nginx:alpine");
+await api.withHttpEndpoint({ name: "http", targetPort: 80 });
+
+const apiEndpoint = await api.getEndpoint("http");
+const apiHost = await compose.getHostAddressExpression(apiEndpoint);
+```
+
+
+
### Publishing and deployment
For a complete guide on the Docker Compose deployment workflow — publishing artifacts, preparing environments, deploying, and cleaning up — see [Deploy to Docker Compose](/deployment/docker-compose/).
@@ -140,19 +272,42 @@ The Docker hosting integration captures environment variables from your app mode
For advanced scenarios, use `ConfigureEnvFile` to customize the generated `.env` file:
+
+
```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
+builder.AddContainer("api", "nginx", "alpine")
+ .WithBindMount("/host/path/data", "/container/data");
+
builder.AddDockerComposeEnvironment("compose")
.ConfigureEnvFile(env =>
{
- env["CUSTOM_VAR"] = new CapturedEnvironmentVariable
- {
- Name = "CUSTOM_VAR",
- DefaultValue = "my-value"
- };
+ env["API_BINDMOUNT_0"].Description = "Customized bind mount source";
+ env["API_BINDMOUNT_0"].DefaultValue = "./data";
});
```
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const api = await builder.addContainer("api", "nginx:alpine");
+await api.withBindMount("/host/path/data", "/container/data");
+
+const compose = await builder.addDockerComposeEnvironment("compose");
+await compose.configureEnvFile(async (envVars) => {
+ const bindMount = await envVars.get("API_BINDMOUNT_0");
+ await bindMount.description.set("Customized bind mount source");
+ await bindMount.defaultValue.set("./data");
+});
+
+await builder.build().run();
+```
+
+
This is useful when you need to add custom environment variables to the generated `.env` file or modify how environment variables are captured.
@@ -160,22 +315,123 @@ This is useful when you need to add custom environment variables to the generate
To customize the generated Docker Compose service for a specific resource, use the `PublishAsDockerComposeService` method. This is optional — all resources are automatically included in the Docker Compose output. Use this method only when you need to modify the generated service definition:
+
+
```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
builder.AddDockerComposeEnvironment("compose");
+var containerName = builder.AddParameter("container-name");
+
builder.AddContainer("cache", "redis", "latest")
.PublishAsDockerComposeService((resource, service) =>
{
// Customize the generated Docker Compose service
+ service.ContainerName =
+ containerName.AsEnvironmentPlaceholder(resource);
service.Labels.Add("com.example.team", "backend");
+ service.Restart = "unless-stopped";
});
builder.Build().Run();
```
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+await builder.addDockerComposeEnvironment("compose");
+
+const containerName = await builder.addParameter("container-name");
+const cache = await builder.addContainer("cache", "redis:latest");
+await cache.publishAsDockerComposeService(async (composeService, service) => {
+ await service.containerName.set(
+ await containerName.asEnvironmentPlaceholder(composeService)
+ );
+ await service.labels.set("com.example.team", "backend");
+ await service.restart.set("unless-stopped");
+});
+
+await builder.build().run();
+```
+
+
+
+The `configure` callback receives the `DockerComposeServiceResource` and the generated `Service` object, allowing you to modify properties like labels, restart policy, container name, or other Docker Compose service settings. Use `AsEnvironmentPlaceholder` for values that should be written as Compose environment variable placeholders in the generated YAML and `.env` files.
+
+### Generate Dockerfiles programmatically
+
+Use `AddDockerfileBuilder` to create a container resource from a Dockerfile generated by AppHost code. Use `WithDockerfileBuilder` when you want to apply the generated Dockerfile to an existing container resource.
+
+
+
+
+
+```csharp title="AppHost.cs"
+using Aspire.Hosting.ApplicationModel.Docker;
+
+var builder = DistributedApplication.CreateBuilder(args);
+
+#pragma warning disable ASPIREDOCKERFILEBUILDER001
+builder.AddDockerfileBuilder("frontend", "../frontend", context =>
+{
+ var build = context.Builder.From("node:22-alpine", "build");
+ build.WorkDir("/app")
+ .Copy("package*.json", "./")
+ .Run("npm ci")
+ .Copy(".", ".")
+ .Run("npm run build");
+
+ var runtime = context.Builder.From("nginx:alpine", "runtime");
+ runtime.CopyFrom("build", "/app/dist", "/usr/share/nginx/html")
+ .Expose(80);
-The `configure` callback receives the `DockerComposeServiceResource` and the generated `Service` object, allowing you to modify properties like labels, restart policy, or other Docker Compose service settings.
+ return Task.CompletedTask;
+}, stage: "runtime");
+#pragma warning restore ASPIREDOCKERFILEBUILDER001
+
+builder.Build().Run();
+```
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+import type { DockerfileBuilderCallbackContext } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const configureDockerfile = async (context: DockerfileBuilderCallbackContext) => {
+ const dockerfile = await context.builder();
+
+ await dockerfile
+ .from("node:22-alpine", { stageName: "build" })
+ .workDir("/app")
+ .copy("package*.json", "./")
+ .run("npm ci")
+ .copy(".", ".")
+ .run("npm run build");
+
+ await dockerfile
+ .from("nginx:alpine", { stageName: "runtime" })
+ .copyFrom("build", "/app/dist", "/usr/share/nginx/html")
+ .expose(80);
+};
+
+await builder.addDockerfileBuilder("frontend", "../frontend", configureDockerfile, {
+ stage: "runtime",
+});
+
+await builder.build().run();
+```
+
+
## Configure image pull policy
@@ -190,23 +446,55 @@ Container resources support an `ImagePullPolicy` that controls when the containe
Use the `WithImagePullPolicy` extension method to set the policy on a container resource:
+
+
```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
builder.AddContainer("mycontainer", "myimage:latest")
.WithImagePullPolicy(ImagePullPolicy.Always);
```
+
+
+```typescript title="apphost.ts" twoslash
+import { createBuilder, ImagePullPolicy } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const container = await builder.addContainer("mycontainer", "myimage:latest");
+await container.withImagePullPolicy(ImagePullPolicy.Always);
+
+await builder.build().run();
+```
+
+
### Use `Never` with locally-built images
The `Never` policy is useful when working with locally-built images that shouldn't be pulled from a registry. For example, when using a Dockerfile-based resource that you build and tag locally:
+
+
```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
builder.AddContainer("myapp", "my-local-image:dev")
.WithImagePullPolicy(ImagePullPolicy.Never);
```
+
+
+```typescript title="apphost.ts" twoslash
+import { createBuilder, ImagePullPolicy } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const app = await builder.addContainer("myapp", "my-local-image:dev");
+await app.withImagePullPolicy(ImagePullPolicy.Never);
+
+await builder.build().run();
+```
+
+
-```typescript title="TypeScript — apphost.ts" twoslash
+```typescript title="TypeScript — apphost.ts"
import { createBuilder } from './.modules/aspire.js';
const builder = await createBuilder();
@@ -565,15 +565,20 @@ builder.Build().Run();
```typescript title="TypeScript — apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+import { createBuilder, EndpointProperty } from './.modules/aspire.js';
const builder = await createBuilder();
const postgres = await builder.addPostgres("postgres");
const database = await postgres.addDatabase("myDatabase");
+const postgresEndpoint = await postgres.getEndpoint("tcp");
+const postgresHost = await postgresEndpoint.property(EndpointProperty.Host);
+const postgresPort = await postgresEndpoint.property(EndpointProperty.Port);
await builder.addNodeApp("my-app", "./app", "index.js")
.withReference(database)
+ .withEnvironment("POSTGRES_HOST", postgresHost)
+ .withEnvironment("POSTGRES_PORT", postgresPort)
.withEnvironment("POSTGRES_USER", postgres.userNameParameter)
.withEnvironment("POSTGRES_PASSWORD", postgres.passwordParameter)
.withEnvironment("POSTGRES_DATABASE", "myDatabase");
diff --git a/src/frontend/src/content/docs/integrations/devtools/browser-logs.mdx b/src/frontend/src/content/docs/integrations/devtools/browser-logs.mdx
new file mode 100644
index 000000000..814c42720
--- /dev/null
+++ b/src/frontend/src/content/docs/integrations/devtools/browser-logs.mdx
@@ -0,0 +1,192 @@
+---
+title: Browser logs
+description: Learn how to capture and configure browser console logs in Aspire using the BrowserLogs resource.
+---
+
+import { Tabs, TabItem } from '@astrojs/starlight/components';
+import InstallPackage from '@components/InstallPackage.astro';
+import LearnMore from '@components/LearnMore.astro';
+
+The browser logs feature in Aspire lets you attach a tracked Chromium browser session to any HTTP/HTTPS resource in your AppHost. When the session is active, browser console output (logs, errors, exceptions, and network events) flows into the resource's console log stream in the Aspire dashboard, and you can capture screenshots as command artifacts — all without leaving the dashboard.
+
+:::caution[Experimental API]
+The browser logs API is experimental. In C#, `WithBrowserLogs` is gated behind the [`ASPIREBROWSERLOGS001`](/diagnostics/aspirebrowserlogs001/) diagnostic ID, which you must suppress to use the API.
+:::
+
+## Prerequisites
+
+- A Chromium-based browser (Microsoft Edge or Google Chrome) installed on the development machine. The tracked browser uses an Aspire-managed user data directory and never touches the user's normal browser profile.
+- A parent resource that exposes at least one HTTP or HTTPS endpoint. Any resource with endpoints is supported — projects, containers, executables, or any custom resource — not just projects. HTTPS endpoints are preferred over HTTP when selecting the target URL.
+
+## Install the package
+
+The browser logs feature ships in the `Aspire.Hosting.Browsers` hosting package. Install it in your AppHost project:
+
+
+
+
+
+
+
+
+
+```bash title="Terminal"
+aspire add browsers
+```
+
+
+ Learn more about [`aspire add`](/reference/cli/commands/aspire-add/) in the command reference.
+
+
+This updates your `aspire.config.json` with the browser logs hosting integration package:
+
+```json title="aspire.config.json" ins={3}
+{
+ "packages": {
+ "Aspire.Hosting.Browsers": "13.3.0"
+ }
+}
+```
+
+
+
+
+## Add browser logs to a resource
+
+Browser logs work with **any resource that exposes an HTTP or HTTPS endpoint that can be rendered in a browser** — projects, containers, executables, or custom resources. The examples below use [Vite](https://vite.dev/) (from the [JavaScript integration](/integrations/frameworks/javascript/)) for illustration, but the same `WithBrowserLogs()` call applies to any browseable resource:
+
+
+
+```csharp title="C# — AppHost.cs"
+#pragma warning disable ASPIREBROWSERLOGS001
+
+var builder = DistributedApplication.CreateBuilder(args);
+
+// "../web" points at a sibling directory containing a Vite app.
+builder.AddViteApp("web", "../web")
+ .WithBrowserLogs();
+
+builder.Build().Run();
+```
+
+
+```typescript title="TypeScript — apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+// "../web" points at a sibling directory containing a Vite app.
+builder.addViteApp("web", "../web")
+ .withBrowserLogs();
+
+await builder.build().run();
+```
+
+
+
+This registers a child resource named `-browser-logs` (for the example above, `web-browser-logs`) of resource type `BrowserLogs` beneath the parent. The child resource appears in the Aspire dashboard and exposes the following commands:
+
+| Display name | Command ID | Description |
+|---|---|---|
+| **Open tracked browser** | `open-tracked-browser` | Launches a Chromium browser attached to the resource's primary URL. Console output streams to the resource's log view. |
+| **Configure tracked browser** | `configure-tracked-browser` | Opens a settings dialog to change the browser, scope, user data mode, and profile at runtime (see [Configure browser logs](#configure-browser-logs)). |
+| **Capture screenshot** | `capture-screenshot` | Captures a screenshot of the active browser session and attaches it as a command artifact. |
+
+The display names are the labels shown on the dashboard; the command IDs are the stable identifiers used when invoking commands programmatically.
+
+## Optional parameters
+
+Pin the browser, profile, or user data mode at design time:
+
+
+
+```csharp title="C# — AppHost.cs"
+builder.AddViteApp("web", "../web")
+ .WithBrowserLogs(
+ browser: "msedge",
+ userDataMode: BrowserUserDataMode.Isolated);
+```
+
+
+```typescript title="TypeScript — apphost.ts"
+import { BrowserUserDataMode } from './.modules/aspire.js';
+
+builder.addViteApp("web", "../web")
+ .withBrowserLogs({
+ browser: "msedge",
+ userDataMode: BrowserUserDataMode.Isolated,
+ });
+```
+
+
+
+| Parameter | Description |
+|---|---|
+| `browser` | Logical browser name (`"msedge"`, `"chrome"`) or an explicit executable path. Defaults to an installed Edge in `Shared` user data mode and an installed Chrome in `Isolated` user data mode, falling back to the other Chromium browser if the preferred one is missing, or to `"chrome"` if neither is detected. |
+| `profile` | Chromium profile name or directory name. Only valid when the effective user data mode is `Shared`. |
+| `userDataMode` | `Shared` (default) — a persistent user data directory shared across all AppHosts on the machine. `Isolated` — a per-AppHost persistent user data directory. Both modes use Aspire-managed paths; the user's normal browser profile is never modified. |
+
+## Configure via app settings
+
+Browser, profile, and user data mode can be supplied from configuration instead of code. Use these keys in _appsettings.json_ or environment variables:
+
+| Scope | Configuration key | Accepted values |
+|---|---|---|
+| Global (all resources) | `Aspire:Hosting:BrowserLogs:Browser` | Logical name (`msedge`, `chrome`) or executable path |
+| Global | `Aspire:Hosting:BrowserLogs:Profile` | Chromium profile name or directory name |
+| Global | `Aspire:Hosting:BrowserLogs:UserDataMode` | `Shared` or `Isolated` (case-insensitive) |
+| Per-resource | `Aspire:Hosting:BrowserLogs:{ResourceName}:Browser` | Same as global `Browser` |
+| Per-resource | `Aspire:Hosting:BrowserLogs:{ResourceName}:Profile` | Same as global `Profile` |
+| Per-resource | `Aspire:Hosting:BrowserLogs:{ResourceName}:UserDataMode` | Same as global `UserDataMode` |
+
+Explicit method arguments to `WithBrowserLogs` take precedence over configuration. Per-resource keys take precedence over global keys.
+
+## Configure browser logs
+
+The **Configure tracked browser** command opens an interactive settings dialog directly in the Aspire dashboard. Use it to adjust the browser session at runtime without restarting the AppHost.
+
+The dialog presents the following options:
+
+- **Scope** — Apply changes to the current resource only, or globally to all browser log resources in the AppHost.
+- **Browser** — Choose a detected Chromium browser (Edge or Chrome) or specify a custom executable path.
+- **User data mode** — `Shared` (cross-AppHost persistent data directory) or `Isolated` (per-AppHost persistent data directory).
+- **Profile** — Select a discovered Chromium profile from the Aspire-managed browser hive (available in `Shared` mode only).
+- **Save to user secrets** — Persist the selected settings to the AppHost's user secrets so they're restored on the next run. This option is only shown when the AppHost project has a user secrets ID configured. Run [`aspire secret set`](/reference/cli/commands/aspire-secret-set/) once in the AppHost project to initialize a user secrets store; subsequent **Save to user secrets** clicks then write the browser configuration into it.
+
+:::note
+The **Configure tracked browser** command is only available when the Aspire dashboard's interaction service is active (that is, when the dashboard is connected to the AppHost). It is disabled in standalone dashboard mode.
+:::
+
+After confirming the dialog, the new configuration takes effect immediately: the current browser session restarts with the updated settings.
+
+## Browser sessions, tabs, and process sharing
+
+Aspire shares one Chromium process across every tracked browser session that targets the same browser executable and user data directory. Each call to **Open tracked browser** opens a **new tab** (Chrome DevTools Protocol page target) inside that shared browser, not a new window or process.
+
+This means:
+
+- Multiple resources that all use the default settings share a single browser window with one tab per resource.
+- Switching `userDataMode` from `Shared` to `Isolated`, choosing a different `browser`, or selecting a different `profile` produces a different identity, which spins up a separate browser process.
+- Within the same user data directory, requesting a named `profile` while another session is already running with a different profile fails with a conflict error rather than silently mixing profiles.
+- The tracked browser persists across AppHost runs. When you stop the AppHost, the browser stays open; the next AppHost run adopts the existing browser via the Chrome DevTools Protocol endpoint and adds new tabs to it.
+
+## How browser logs are captured
+
+The tracked browser connects to the Aspire AppHost over the [Chrome DevTools Protocol (CDP)](https://chromedevtools.github.io/devtools-protocol/). Aspire subscribes to the following CDP domains and forwards events to the resource's console log stream:
+
+- **Runtime** — JavaScript console messages
+- **Log** — browser-level log entries
+- **Page** — page lifecycle events
+- **Network** — network requests and failures
+
+:::tip
+Browser logs are a complement to [browser telemetry](/dashboard/enable-browser-telemetry/), which uses the OpenTelemetry JavaScript SDK to send traces and metrics from within the browser app itself. The two features can be used together.
+:::
+
+## See also
+
+- [Browser sessions, tabs, and process sharing](#browser-sessions-tabs-and-process-sharing)
+- [Enable browser telemetry](/dashboard/enable-browser-telemetry/)
+- [Explore Aspire dashboard features](/dashboard/explore/)
+- [AppHost configuration](/app-host/configuration/)
+
diff --git a/src/frontend/src/content/docs/integrations/frameworks/javascript.mdx b/src/frontend/src/content/docs/integrations/frameworks/javascript.mdx
index 1c4b826b7..4ce9e1c2d 100644
--- a/src/frontend/src/content/docs/integrations/frameworks/javascript.mdx
+++ b/src/frontend/src/content/docs/integrations/frameworks/javascript.mdx
@@ -3,11 +3,10 @@ title: JavaScript integration
description: Learn how to orchestrate JavaScript applications with Aspire using the Aspire.Hosting.JavaScript package.
---
-import { Aside, Steps } from '@astrojs/starlight/components';
+import { Aside, Steps, TabItem, Tabs } from '@astrojs/starlight/components';
import { Image } from 'astro:assets';
import InstallPackage from '@components/InstallPackage.astro';
import LearnMore from '@components/LearnMore.astro';
-import ThemeImage from '@components/ThemeImage.astro';
import jsIcon from '@assets/icons/javascript.svg';
+
+
+```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
-var api = builder.AddProject();
+var api = builder
+ .AddNodeApp("api", "./api", "server.js")
+ .WithHttpEndpoint(port: 3001, env: "PORT");
var frontend = builder.AddJavaScriptApp("frontend", "./frontend")
.WithHttpEndpoint(port: 3000, env: "PORT")
@@ -57,6 +63,30 @@ var frontend = builder.AddJavaScriptApp("frontend", "./frontend")
// After adding all resources, run the app...
```
+
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const api = await builder
+ .addNodeApp('api', './api', 'server.js')
+ .withHttpEndpoint({ port: 3001, env: 'PORT' });
+
+const frontend = await builder
+ .addJavaScriptApp('frontend', './frontend')
+ .withHttpEndpoint({ port: 3000, env: 'PORT' })
+ .withReference(api);
+
+// After adding all resources, run the app...
+```
+
+
+
+
+
By default, `AddJavaScriptApp`:
- Uses npm as the package manager when `package.json` is present
@@ -74,32 +104,154 @@ The method accepts the following parameters:
For Node.js applications that don't use a package.json script runner, you can directly run a JavaScript file using the `AddNodeApp` extension method:
-```csharp title="C# — AppHost.cs"
+
+
+
+```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
-var api = builder.AddProject();
+var api = builder
+ .AddNodeApp("api", "./api", "server.js")
+ .WithHttpEndpoint(port: 3000, env: "PORT");
-var nodeApp = builder.AddNodeApp("node-app", "./node-app", "server.js")
- .WithHttpEndpoint(port: 3000, env: "PORT")
- .WithReference(api);
+// After adding all resources, run the app...
+```
+
+
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const api = await builder
+ .addNodeApp('api', './api', 'server.js')
+ .withHttpEndpoint({ port: 3000, env: 'PORT' });
// After adding all resources, run the app...
```
+
+
+
+
The `AddNodeApp` method requires:
- **name**: The name of the resource in the Aspire dashboard
- **appDirectory**: The path to the directory containing the node application.
- **scriptPath** The path to the script relative to the app directory to run.
+## Add Next.js application
+
+:::caution[Experimental]
+`AddNextJsApp` is marked `[Experimental]`. In C# AppHosts, suppress the `ASPIREJAVASCRIPT001` diagnostic when you use this API.
+:::
+
+For [Next.js](https://nextjs.org/) applications, use the `AddNextJsApp` extension method. It provides Next.js-specific defaults for both run mode and publish mode:
+
+
+
+
+```csharp title="AppHost.cs"
+#pragma warning disable ASPIREJAVASCRIPT001
+
+var builder = DistributedApplication.CreateBuilder(args);
+
+var api = builder
+ .AddNodeApp("api", "./api", "server.js")
+ .WithHttpEndpoint(port: 3001, env: "PORT");
+
+var nextApp = builder.AddNextJsApp("next-app", "./next-app")
+ .WithReference(api);
+
+// After adding all resources, run the app...
+```
+
+
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const api = await builder
+ .addNodeApp('api', './api', 'server.js')
+ .withHttpEndpoint({ port: 3001, env: 'PORT' });
+
+const nextApp = await builder
+ .addNextJsApp('next-app', './next-app')
+ .withReference(api);
+
+// After adding all resources, run the app...
+```
+
+
+
+
+
+`AddNextJsApp` configures:
+
+- **Run mode**: Starts `next dev` with the correct port binding (`-p` flag).
+- **Publish mode**: Generates a multi-stage Dockerfile using Next.js [standalone output](https://nextjs.org/docs/pages/api-reference/next-config-js/output).
+- **Deploy-time validation**: Checks `next.config.ts`, `next.config.js`, or `next.config.mjs` for `output: "standalone"` as a prerequisite step before building the container. Without standalone output, the generated Dockerfile will not work correctly.
+
+:::note
+You must set `output: "standalone"` in your Next.js configuration for `AddNextJsApp` publish mode to work correctly:
+
+```javascript title="next.config.js"
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ output: "standalone",
+};
+
+module.exports = nextConfig;
+```
+:::
+
+To opt out of the configuration validation step, call `DisableBuildValidation` / `disableBuildValidation`:
+
+
+
+
+```csharp title="AppHost.cs"
+#pragma warning disable ASPIREJAVASCRIPT001
+
+var nextApp = builder.AddNextJsApp("next-app", "./next-app")
+ .DisableBuildValidation();
+```
+
+
+
+
+```typescript title="apphost.ts"
+const nextApp = await builder
+ .addNextJsApp('next-app', './next-app')
+ .disableBuildValidation();
+```
+
+
+
+
+
+
+ For Next.js publish-method requirements (standalone output, copy shape, server components), see [Deploy JavaScript apps — Next.js gotchas](/deployment/javascript-apps/#nextjs).
+
+
## Add Vite application
For Vite applications, you can use the `AddViteApp` extension method which provides Vite-specific defaults and optimizations:
-```csharp title="C# — AppHost.cs"
+
+
+
+```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
-var api = builder.AddProject();
+var api = builder
+ .AddNodeApp("api", "./api", "server.js")
+ .WithHttpEndpoint(port: 3001, env: "PORT");
var viteApp = builder.AddViteApp("vite-app", "./vite-app")
.WithReference(api);
@@ -107,6 +259,29 @@ var viteApp = builder.AddViteApp("vite-app", "./vite-app")
// After adding all resources, run the app...
```
+
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const api = await builder
+ .addNodeApp('api', './api', 'server.js')
+ .withHttpEndpoint({ port: 3001, env: 'PORT' });
+
+const viteApp = await builder
+ .addViteApp('vite-app', './vite-app')
+ .withReference(api);
+
+// After adding all resources, run the app...
+```
+
+
+
+
+
`AddViteApp` automatically configures:
- **HTTP endpoint**: Registers an `http` endpoint and sets the `PORT` environment variable — you don't need to call `WithHttpEndpoint` yourself
@@ -121,7 +296,8 @@ Do _not_ call `.WithHttpEndpoint()` on a Vite resource. `AddViteApp` already reg
:::note
The Vite dev server is only used for local development. During publish, Aspire
builds the frontend assets, but another resource must serve those built files in
-production.
+production. Use [Deploy JavaScript apps](/deployment/javascript-apps/) to choose
+which resource owns the production HTTP surface.
:::
The method accepts the same parameters as `AddJavaScriptApp`:
@@ -130,6 +306,10 @@ The method accepts the same parameters as `AddJavaScriptApp`:
- **appDirectory**: The path to the directory containing the Vite app.
- **runScriptName** (optional): The name of the script that runs the Vite app. Defaults to "dev".
+
+ For framework-specific publish guidance — Vite/React/Vue, Angular, Astro, SvelteKit, TanStack Start, Nuxt, Remix, and Qwik — see [Deploy JavaScript apps — Framework-specific gotchas](/deployment/javascript-apps/#framework-specific-gotchas).
+
+
## Configure package managers
Aspire automatically detects and supports multiple JavaScript package managers with intelligent defaults for both development and production scenarios.
@@ -142,7 +322,10 @@ Package managers automatically install dependencies by default. This ensures dep
npm is the default package manager. If your project has a `package.json` file, Aspire will use npm unless you specify otherwise:
-```csharp title="C# — AppHost.cs" "WithNpm"
+
+
+
+```csharp title="AppHost.cs" "WithNpm"
var builder = DistributedApplication.CreateBuilder(args);
// npm is used by default
@@ -155,13 +338,39 @@ var customApp = builder.AddJavaScriptApp("custom-app", "./custom-app")
// After adding all resources, run the app...
```
+
+
+
+```typescript title="apphost.ts" "withNpm"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+// npm is used by default
+const app = await builder.addJavaScriptApp('app', './app');
+
+// Customize npm with additional flags
+const customApp = await builder
+ .addJavaScriptApp('custom-app', './custom-app')
+ .withNpm({ installCommand: 'ci', installArgs: ['--legacy-peer-deps'] });
+
+// After adding all resources, run the app...
+```
+
+
+
+
+
When publishing (production mode), Aspire automatically uses `npm ci` if `package-lock.json` exists, otherwise it uses `npm install` for deterministic builds.
### Use yarn
-To use yarn as the package manager, call the `WithYarn` extension method:
+To use yarn as the package manager, call `WithYarn` / `withYarn`:
-```csharp title="C# — AppHost.cs" "WithYarn"
+
+
+
+```csharp title="AppHost.cs" "WithYarn"
var builder = DistributedApplication.CreateBuilder(args);
var app = builder.AddJavaScriptApp("app", "./app")
@@ -174,6 +383,28 @@ var customApp = builder.AddJavaScriptApp("custom-app", "./custom-app")
// After adding all resources, run the app...
```
+
+
+
+```typescript title="apphost.ts" "withYarn"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const app = await builder.addJavaScriptApp('app', './app').withYarn();
+
+// Customize yarn with additional flags
+const customApp = await builder
+ .addJavaScriptApp('custom-app', './custom-app')
+ .withYarn({ installArgs: ['--immutable'] });
+
+// After adding all resources, run the app...
+```
+
+
+
+
+
When publishing, Aspire uses:
- `yarn install --immutable` if `yarn.lock` exists and yarn v2+ is detected
@@ -182,9 +413,12 @@ When publishing, Aspire uses:
### Use pnpm
-To use pnpm as the package manager, call the `WithPnpm` extension method:
+To use pnpm as the package manager, call `WithPnpm` / `withPnpm`:
-```csharp title="C# — AppHost.cs" "WithPnpm"
+
+
+
+```csharp title="AppHost.cs" "WithPnpm"
var builder = DistributedApplication.CreateBuilder(args);
var app = builder.AddJavaScriptApp("app", "./app")
@@ -197,13 +431,38 @@ var customApp = builder.AddJavaScriptApp("custom-app", "./custom-app")
// After adding all resources, run the app...
```
+
+
+
+```typescript title="apphost.ts" "withPnpm"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const app = await builder.addJavaScriptApp('app', './app').withPnpm();
+
+// Customize pnpm with additional flags
+const customApp = await builder
+ .addJavaScriptApp('custom-app', './custom-app')
+ .withPnpm({ installArgs: ['--frozen-lockfile'] });
+
+// After adding all resources, run the app...
+```
+
+
+
+
+
When publishing, Aspire uses `pnpm install --frozen-lockfile` if `pnpm-lock.yaml` exists, otherwise it uses `pnpm install`.
### Use Bun
-To use Bun as the package manager, call the `WithBun` extension method:
+To use Bun as the package manager, call `WithBun` / `withBun`:
-```csharp title="C# — AppHost.cs" "WithBun"
+
+
+
+```csharp title="AppHost.cs" "WithBun"
var builder = DistributedApplication.CreateBuilder(args);
var app = builder.AddViteApp("app", "./app")
@@ -216,13 +475,40 @@ var customApp = builder.AddViteApp("custom-app", "./custom-app")
// After adding all resources, run the app...
```
+
+
+
+```typescript title="apphost.ts" "withBun"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const app = await builder.addViteApp('app', './app').withBun();
+
+// Customize Bun with additional flags
+const customApp = await builder
+ .addViteApp('custom-app', './custom-app')
+ .withBun({ installArgs: ['--frozen-lockfile'] });
+
+// After adding all resources, run the app...
+```
+
+
+
+
+
When publishing, Aspire uses `bun install --frozen-lockfile` if `bun.lock` or `bun.lockb` exists, otherwise it uses `bun install`.
Bun supports passing script arguments without the `--` separator, so commands like `bun run dev --port 3000` work without needing `bun run dev -- --port 3000`.
-When publishing to a container, `WithBun()` automatically configures a Bun build image (`oven/bun:1`) since Bun is not available in the default Node.js base images. To use a specific Bun version, configure a custom build image:
+When publishing to a container, `WithBun` / `withBun` automatically configures a Bun build image (`oven/bun:1`) since Bun is not available in the default Node.js base images. To use a specific Bun version, configure a custom build image:
+
+
+
+
+```csharp title="AppHost.cs"
+#pragma warning disable ASPIREDOCKERFILEBUILDER001
-```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
var app = builder.AddViteApp("app", "./app")
@@ -232,11 +518,34 @@ var app = builder.AddViteApp("app", "./app")
// After adding all resources, run the app...
```
+
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const app = await builder
+ .addViteApp('app', './app')
+ .withBun()
+ .withDockerfileBaseImage({ buildImage: 'oven/bun:1.1' });
+
+// After adding all resources, run the app...
+```
+
+
+
+
+
## Customize scripts
You can customize which scripts run during development and build:
-```csharp title="C# — AppHost.cs"
+
+
+
+```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
// Use different script names
@@ -247,11 +556,35 @@ var app = builder.AddJavaScriptApp("app", "./app")
// After adding all resources, run the app...
```
+
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+// Use different script names
+const app = await builder
+ .addJavaScriptApp('app', './app')
+ .withRunScript('start') // Run "npm run start" during development instead of "dev"
+ .withBuildScript('prod'); // Run "npm run prod" during publish instead of "build"
+
+// After adding all resources, run the app...
+```
+
+
+
+
+
### Pass arguments to scripts
-To pass command-line arguments to your scripts, use the `WithArgs` extension method:
+To pass command-line arguments to your scripts, use `WithArgs` / `withArgs`:
+
+
+
-```csharp title="C# — AppHost.cs"
+```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
var app = builder.AddJavaScriptApp("app", "./app")
@@ -261,6 +594,26 @@ var app = builder.AddJavaScriptApp("app", "./app")
// After adding all resources, run the app...
```
+
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const app = await builder
+ .addJavaScriptApp('app', './app')
+ .withRunScript('dev')
+ .withArgs(['--port', '3000', '--host']);
+
+// After adding all resources, run the app...
+```
+
+
+
+
+
Alternatively, you can define custom scripts in your `package.json` with arguments baked in:
```json title="package.json"
@@ -274,7 +627,10 @@ Alternatively, you can define custom scripts in your `package.json` with argumen
Then reference the custom script:
-```csharp title="C# — AppHost.cs"
+
+
+
+```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
var app = builder.AddJavaScriptApp("app", "./app")
@@ -283,15 +639,37 @@ var app = builder.AddJavaScriptApp("app", "./app")
// After adding all resources, run the app...
```
+
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const app = await builder
+ .addJavaScriptApp('app', './app')
+ .withRunScript('dev:custom');
+
+// After adding all resources, run the app...
+```
+
+
+
+
+
## Configure endpoints
-JavaScript applications typically use environment variables to configure the port they listen on. Use `WithHttpEndpoint` to configure the port and set the environment variable:
+JavaScript applications typically use environment variables to configure the port they listen on. Use `WithHttpEndpoint` / `withHttpEndpoint` to configure the port and set the environment variable:
:::tip
`AddViteApp` already registers an `http` endpoint with the `PORT` environment variable. The following example applies to `AddJavaScriptApp` and `AddNodeApp` only.
:::
-```csharp title="C# — AppHost.cs"
+
+
+
+```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
var app = builder.AddJavaScriptApp("app", "./app")
@@ -300,6 +678,25 @@ var app = builder.AddJavaScriptApp("app", "./app")
// After adding all resources, run the app...
```
+
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const app = await builder
+ .addJavaScriptApp('app', './app')
+ .withHttpEndpoint({ port: 3000, env: 'PORT' });
+
+// After adding all resources, run the app...
+```
+
+
+
+
+
Common environment variables for JavaScript frameworks:
- **PORT**: Generic port configuration used by many frameworks (Express, Vite, Next.js)
@@ -310,7 +707,10 @@ Common environment variables for JavaScript frameworks:
For Vite applications, you can specify a custom configuration file if you need to override the default Vite configuration resolution behavior:
-```csharp title="C# — AppHost.cs"
+
+
+
+```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
var viteApp = builder.AddViteApp("vite-app", "./vite-app")
@@ -320,7 +720,27 @@ var viteApp = builder.AddViteApp("vite-app", "./vite-app")
// After adding all resources, run the app...
```
-The `WithViteConfig` method accepts:
+
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const viteApp = await builder
+ .addViteApp('vite-app', './vite-app')
+ // Path is relative to the Vite service project root
+ .withViteConfig('./vite.production.config.js');
+
+// After adding all resources, run the app...
+```
+
+
+
+
+
+The `WithViteConfig` / `withViteConfig` configuration accepts:
- **configPath**: The path to the Vite configuration file, relative to the Vite service project root.
@@ -330,7 +750,12 @@ This is useful when you have multiple Vite configuration files for different sce
Aspire automatically augments existing Vite configurations to enable HTTPS endpoints at runtime, eliminating manual certificate configuration for development. When you configure HTTPS endpoints on a Vite resource, Aspire dynamically injects the necessary HTTPS configuration:
-```csharp title="C# — AppHost.cs"
+
+
+
+```csharp title="AppHost.cs"
+#pragma warning disable ASPIRECERTIFICATES001
+
var builder = DistributedApplication.CreateBuilder(args);
var viteApp = builder.AddViteApp("vite-app", "./vite-app")
@@ -340,27 +765,77 @@ var viteApp = builder.AddViteApp("vite-app", "./vite-app")
// After adding all resources, run the app...
```
+
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const viteApp = await builder
+ .addViteApp('vite-app', './vite-app')
+ .withHttpsEndpoint({ env: 'PORT' })
+ .withHttpsDeveloperCertificate();
+
+// After adding all resources, run the app...
+```
+
+
+
+
+
The HTTPS configuration is automatically applied without modifying your `vite.config.js` file. For more information about certificate configuration, see [Certificate configuration](/app-host/certificate-configuration/).
## Pass API URLs to Vite apps
When your Vite app needs to communicate with a backend API, pass the API URL via an environment variable. Vite only exposes variables prefixed with `VITE_` to client-side code.
-In your AppHost, expose the API URL to the Vite app using `WithEnvironment`:
+In your AppHost, expose the API URL to the Vite app using `WithEnvironment` / `withEnvironment`:
-```csharp title="C# — AppHost.cs"
+
+
+
+```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
-var api = builder.AddProject("api")
+var api = builder
+ .AddNodeApp("api", "./api", "server.js")
+ .WithHttpEndpoint(port: 3001, env: "PORT")
.WithExternalHttpEndpoints();
var viteApp = builder.AddViteApp("vite-app", "./vite-app")
.WithReference(api)
- .WithEnvironment("VITE_API_BASE_URL", api.GetEndpoint("https"));
+ .WithEnvironment("VITE_API_BASE_URL", api.GetEndpoint("http"));
+
+// After adding all resources, run the app...
+```
+
+
+
+
+```typescript title="apphost.ts"
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+
+const api = await builder
+ .addNodeApp('api', './api', 'server.js')
+ .withHttpEndpoint({ port: 3001, env: 'PORT' })
+ .withExternalHttpEndpoints();
+
+const viteApp = await builder
+ .addViteApp('vite-app', './vite-app')
+ .withReference(api)
+ .withEnvironment('VITE_API_BASE_URL', await api.getEndpoint('http'));
// After adding all resources, run the app...
```
+
+
+
+
In your Vite app, read the variable from `import.meta.env`:
```typescript title="TypeScript — src/api.ts"
@@ -381,22 +856,27 @@ export async function fetchData() {
## Pass runtime configuration to SPA frontends
-Vite and other SPA build tools bake environment variables (such as `VITE_*`) into the JavaScript bundle at **build time** (for example, when building the client for production). However, Aspire sets environment variables at **runtime**. This means calling `.WithEnvironment("VITE_GOOGLE_CLIENT_ID", parameter)` on a Vite resource won't change values that were already baked into a previously built production bundle.
+Vite and other SPA build tools bake environment variables (such as `VITE_*`) into the JavaScript bundle at **build time** (for example, when building the client for production). However, Aspire sets environment variables at **runtime**. This means calling `WithEnvironment("VITE_GOOGLE_CLIENT_ID", parameter)` / `withEnvironment('VITE_GOOGLE_CLIENT_ID', parameter)` on a Vite resource won't change values that were already baked into a previously built production bundle.
-To bridge this gap, pass the parameter to your API project as a standard environment variable and expose it through a configuration endpoint that the SPA fetches at startup.
+To bridge this gap, pass the parameter to your API app as a standard environment variable and expose it through a configuration endpoint that the SPA fetches at startup.
1. **Pass the parameter to the API in the AppHost**
- Define the parameter in the AppHost and pass it to the API project using `WithEnvironment`. Then reference the API from the frontend so it can call the endpoint:
+ Define the parameter in the AppHost and pass it to the API app using `WithEnvironment` / `withEnvironment`. Then reference the API from the frontend so it can call the endpoint:
+
+
+
- ```csharp title="C# — AppHost.cs"
+ ```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
var googleClientId = builder.AddParameter("google-client-id");
- var api = builder.AddProject("api")
+ var api = builder
+ .AddNodeApp("api", "./api", "server.js")
+ .WithHttpEndpoint(port: 3001, env: "PORT")
.WithEnvironment("GOOGLE_CLIENT_ID", googleClientId);
var frontend = builder.AddViteApp("frontend", "./frontend")
@@ -406,19 +886,59 @@ To bridge this gap, pass the parameter to your API project as a standard environ
// After adding all resources, run the app...
```
+
+
+
+ ```typescript title="apphost.ts"
+ import { createBuilder } from './.modules/aspire.js';
+
+ const builder = await createBuilder();
+
+ const googleClientId = await builder.addParameter('google-client-id');
+
+ const api = await builder
+ .addNodeApp('api', './api', 'server.js')
+ .withHttpEndpoint({ port: 3001, env: 'PORT' })
+ .withEnvironment('GOOGLE_CLIENT_ID', googleClientId);
+
+ const frontend = await builder
+ .addViteApp('frontend', './frontend')
+ .withPnpm()
+ .withReference(api);
+
+ // After adding all resources, run the app...
+ ```
+
+
+
+
+
2. **Expose a config endpoint in the API**
- Create an endpoint in your API project that reads the environment variable from configuration and returns it to the frontend:
+ Create an endpoint in your API app that reads the environment variable from `process.env` and returns it to the frontend:
+
+ ```javascript title="JavaScript — api/server.js"
+ import http from 'node:http';
+
+ const port = process.env.PORT ?? 3000;
+ const clientId = process.env.GOOGLE_CLIENT_ID;
+
+ const server = http.createServer((request, response) => {
+ if (request.url !== '/api/config/google-client-id') {
+ response.writeHead(404).end();
+ return;
+ }
- ```csharp title="C# — Program.cs"
- app.MapGet("/api/config/google-client-id", (IConfiguration config) =>
- {
- var clientId = config["GOOGLE_CLIENT_ID"];
+ if (!clientId) {
+ response.writeHead(404).end();
+ return;
+ }
- return string.IsNullOrEmpty(clientId)
- ? Results.NotFound()
- : Results.Ok(new { clientId });
+ response.setHeader('content-type', 'application/json');
+ response.end(JSON.stringify({ clientId }));
});
+
+ server.listen(port);
```