diff --git a/.agents/skills/container-images/SKILL.md b/.agents/skills/container-images/SKILL.md
index 2aa3bea0a..ceecfc2c2 100644
--- a/.agents/skills/container-images/SKILL.md
+++ b/.agents/skills/container-images/SKILL.md
@@ -11,7 +11,7 @@ This skill extracts **every** container image reference from the [`microsoft/asp
src/frontend/src/data/container-images.json
```
-This JSON is consumed by Astro components on the aspire.dev site to render a browsable catalog of all container images used by .NET Aspire hosting integrations.
+This JSON is consumed by Astro components on the aspire.dev site to render a browsable catalog of all container images used by Aspire hosting integrations.
---
diff --git a/.github/agents/community-toolkit-integration-doc-writer.agent.md b/.github/agents/community-toolkit-integration-doc-writer.agent.md
index ab1a52b26..5f9b54f70 100644
--- a/.github/agents/community-toolkit-integration-doc-writer.agent.md
+++ b/.github/agents/community-toolkit-integration-doc-writer.agent.md
@@ -162,7 +162,7 @@ This step ensures that the documentation you've created is properly indexed and
### 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 synced `Tabs` (with `syncKey='aspire-lang'`) unless the feature is genuinely language-specific or TypeScript AppHost support does not exist yet.
+- Always show both C# AppHost (`AppHost.cs`) and TypeScript AppHost (`apphost.mts`) 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.
diff --git a/.github/astro.instructions.md b/.github/astro.instructions.md
index dcb471173..867d8936f 100644
--- a/.github/astro.instructions.md
+++ b/.github/astro.instructions.md
@@ -230,7 +230,7 @@ Bad:
```md
- **AppHost Project:** the orchestrator project
-- **integration**: A NuGet package that configures a service or client for use with .NET Aspire
+- **integration**: A NuGet package that configures a service or client for use with Aspire
```
### Parentheses
diff --git a/.github/skills/aspire/SKILL.md b/.github/skills/aspire/SKILL.md
index bbdc8be52..2efe9d826 100644
--- a/.github/skills/aspire/SKILL.md
+++ b/.github/skills/aspire/SKILL.md
@@ -5,7 +5,7 @@ description: "Orchestrates Aspire distributed applications using the Aspire CLI
# Aspire Skill
-This repository uses Aspire to orchestrate its distributed application. Resources are defined in the AppHost project (`apphost.cs` or `apphost.ts`).
+This repository uses Aspire to orchestrate its distributed application. Resources are defined in the AppHost project (`apphost.cs` or `apphost.mts`).
## CLI command reference
diff --git a/.github/skills/doc-writer/SKILL.md b/.github/skills/doc-writer/SKILL.md
index 0b782d768..3e3719734 100644
--- a/.github/skills/doc-writer/SKILL.md
+++ b/.github/skills/doc-writer/SKILL.md
@@ -73,8 +73,36 @@ description: A brief summary of the page content (required for SEO)
Optional frontmatter fields:
- `next: false` - Disable "Next page" link for terminal pages
+- `seoTitle` - Override the page's `og:title` / `twitter:title` only,
+ without touching the visible H1 or sidebar label. Use this **only**
+ when the natural H1 must stay short (commands, terse labels). When
+ set, the value is emitted verbatim — no `· Aspire` suffix is appended.
- Custom metadata as needed by Starlight theme
+#### SEO length targets
+
+The site uses Open Graph metadata to render social cards and feed SEO
+tooling. To keep previews scannable on every social network and to
+avoid the "title too short / description too long" lints that surface
+on Yoast, LinkedIn, and the search-console reports, follow these
+length targets when authoring frontmatter:
+
+| Field | Composed length target | Hard limit |
+| ------------- | ---------------------: | ---------: |
+| `title` | 41-51 characters | 70 characters |
+| `seoTitle` | 50-60 characters | 70 characters |
+| `description` | 110-160 characters | 200 characters (auto-truncated) |
+
+`title` becomes `og:title` composed as `${title} · Aspire`, so the
+target window leaves room for the 9-character suffix. `seoTitle`
+overrides the composition outright — write the full string yourself.
+
+Surface keywords from the article body itself in the description
+(verbs, integration names, API surfaces). The CI guard at
+`tests/unit/seo-lengths.vitest.test.ts` fails when any English page
+strays outside the wider 30-65 / 80-200 character guard ranges, so a
+draft can land slightly off-target and tighten in follow-ups.
+
### Required Imports
Import Starlight components at the top of your MDX file:
@@ -368,8 +396,8 @@ builder.Build().Run();
````
````mdx
-```typescript title="apphost.ts"
-import { createBuilder } from "./.modules/aspire.js";
+```typescript title="apphost.mts"
+import { createBuilder } from "./.aspire/modules/aspire.mjs";
const builder = await createBuilder();
@@ -407,7 +435,7 @@ For client/library packages:
## AppHost Language Parity (C# and TypeScript)
-Aspire supports both **C# AppHosts** (`AppHost.cs`) and **TypeScript AppHosts** (`apphost.ts`). Documentation must treat both languages as first-class citizens. **Always show both C# and TypeScript code samples for AppHost code unless the feature is genuinely language-specific or TypeScript support does not exist yet.** Never write AppHost or hosting-integration documentation with a C#-only bias.
+Aspire supports both **C# AppHosts** (`AppHost.cs`) and **TypeScript AppHosts** (`apphost.mts`). Documentation must treat both languages as first-class citizens. **Always show both C# and TypeScript code samples for AppHost code unless the feature is genuinely language-specific or TypeScript support does not exist yet.** Never write AppHost or hosting-integration documentation with a C#-only bias.
### Core Principles
@@ -440,8 +468,8 @@ builder.Build().Run();
-```typescript title="apphost.ts"
-import { createBuilder } from "./.modules/aspire.js";
+```typescript title="apphost.mts"
+import { createBuilder } from "./.aspire/modules/aspire.mjs";
const builder = await createBuilder();
@@ -467,10 +495,10 @@ If a section heading should appear in the **On this page** table of contents, ke
| Aspect | C# | TypeScript |
| ---------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
-| File title | `title="AppHost.cs"` | `title="apphost.ts"` |
+| File title | `title="AppHost.cs"` | `title="apphost.mts"` |
| 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();` |
+| Builder creation | `DistributedApplication.CreateBuilder(args)` | `import { createBuilder } from './.aspire/modules/aspire.mjs';` 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()` |
@@ -565,8 +593,8 @@ builder.Build().Run();
-```typescript title="apphost.ts"
-import { createBuilder } from "./.modules/aspire.js";
+```typescript title="apphost.mts"
+import { createBuilder } from "./.aspire/modules/aspire.mjs";
const builder = await createBuilder();
@@ -616,8 +644,8 @@ builder.Build().Run();
-```typescript title="apphost.ts"
-import { createBuilder } from "./.modules/aspire.js";
+```typescript title="apphost.mts"
+import { createBuilder } from "./.aspire/modules/aspire.mjs";
const builder = await createBuilder();
diff --git a/src/frontend/astro.config.mjs b/src/frontend/astro.config.mjs
index cccd546a6..16be03008 100644
--- a/src/frontend/astro.config.mjs
+++ b/src/frontend/astro.config.mjs
@@ -117,15 +117,17 @@ export default defineConfig({
starlightSidebarTopics(sidebarTopics, {
exclude: [
'**/includes/**/*',
- '/support',
- '/reference/api',
+ '/support',
+ '/diagnostics/aspireats001',
+ '/**/diagnostics/aspireats001',
+ '/reference/api',
'/reference/api/**'
],
}),
starlightLinksValidator({
errorOnRelativeLinks: false,
errorOnFallbackPages: false,
- exclude: ['/i18n/'],
+ exclude: ['/i18n/', '/reference/api', '/reference/api/**'],
}),
starlightScrollToTop({
// https://frostybee.github.io/starlight-scroll-to-top/svg-paths/
diff --git a/src/frontend/config/redirects.mjs b/src/frontend/config/redirects.mjs
index 43726d99c..cdde32fb2 100644
--- a/src/frontend/config/redirects.mjs
+++ b/src/frontend/config/redirects.mjs
@@ -58,6 +58,8 @@ export const redirects = {
'/install-aspire-cli/': '/get-started/install-cli/',
'/get-started/welcome/': '/docs/',
'/get-started/installation/': '/get-started/install-cli/',
+ '/app-host/persistent-containers/': '/app-host/resource-lifetimes/',
+ '/ja/app-host/persistent-containers/': '/ja/app-host/resource-lifetimes/',
// Integration -client → -connect rename
'/integrations/ai/github-models/github-models-client/': '/integrations/ai/github-models/github-models-connect/',
'/integrations/ai/ollama/ollama-client/': '/integrations/ai/ollama/ollama-connect/',
@@ -175,6 +177,9 @@ export const redirects = {
'/integrations/devtools/flagd/': '/integrations/devtools/flagd/flagd-get-started/',
'/integrations/devtools/goff/': '/integrations/devtools/goff/goff-get-started/',
'/integrations/devtools/mailpit/': '/integrations/devtools/mailpit/mailpit-get-started/',
+ '/integrations/frameworks/go/': '/integrations/frameworks/go/go-get-started/',
+ '/integrations/frameworks/go-apps/': '/integrations/frameworks/go/go-get-started/',
+ '/ja/integrations/frameworks/go-apps/': '/integrations/frameworks/go/go-get-started/',
'/integrations/frameworks/csharp-file-based-apps/': '/integrations/dotnet/csharp-file-based-apps/',
'/integrations/frameworks/maui/': '/integrations/dotnet/maui/',
'/fundamentals/service-defaults/': '/get-started/csharp-service-defaults/',
diff --git a/src/frontend/config/sidebar/docs.topics.ts b/src/frontend/config/sidebar/docs.topics.ts
index 419aa2e62..7c644758b 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.4',
+ slug: 'whats-new/aspire-13-4',
+ },
{
label: 'Aspire 13.3',
slug: 'whats-new/aspire-13-3',
@@ -901,11 +905,11 @@ export const docsTopics: StarlightSidebarTopicsUserConfig = {
collapsed: true,
items: [
{
- label: 'Persistent containers',
- slug: 'app-host/persistent-containers',
+ label: 'Resource lifetimes',
+ slug: 'app-host/resource-lifetimes',
translations: {
- en: 'Persistent containers',
- ja: '永続コンテナー',
+ en: 'Resource lifetimes',
+ ja: 'リソースのライフタイム',
},
},
{
diff --git a/src/frontend/config/sidebar/integrations.topics.ts b/src/frontend/config/sidebar/integrations.topics.ts
index 6a845b82b..9dc395801 100644
--- a/src/frontend/config/sidebar/integrations.topics.ts
+++ b/src/frontend/config/sidebar/integrations.topics.ts
@@ -1248,7 +1248,7 @@ export const integrationTopics: StarlightSidebarTopicsUserConfig = {
},
],
},
- {
+ {
label: 'SQLite',
collapsed: true,
items: [
@@ -1332,13 +1332,35 @@ export const integrationTopics: StarlightSidebarTopicsUserConfig = {
{ label: 'C# file-based apps', slug: 'integrations/dotnet/csharp-file-based-apps' },
{ label: 'Launch profiles', slug: 'integrations/dotnet/launch-profiles' },
{ label: '.NET tool resources', slug: 'integrations/dotnet/dotnet-tool-resources' },
+ {
+ label: 'Blazor',
+ collapsed: true,
+ items: [
+ { label: 'Get started', slug: 'integrations/dotnet/blazor-get-started' },
+ { label: 'Set up Blazor hosting in the AppHost', slug: 'integrations/dotnet/blazor-hosting' },
+ { label: 'Connect Blazor apps and APIs', slug: 'integrations/dotnet/blazor-connect' },
+ ],
+ },
{ label: '.NET MAUI', slug: 'integrations/dotnet/maui' },
{ label: 'WPF and Windows Forms', slug: 'integrations/frameworks/wpf-winforms' },
{ label: 'Orleans', slug: 'integrations/frameworks/orleans' },
],
},
{ label: 'Dapr', slug: 'integrations/frameworks/dapr' },
- { label: 'Go', slug: 'integrations/frameworks/go-apps' },
+ {
+ label: 'Go',
+ collapsed: true,
+ items: [
+ {
+ label: 'Get started',
+ slug: 'integrations/frameworks/go/go-get-started',
+ },
+ {
+ label: 'Set up Go apps in the AppHost',
+ slug: 'integrations/frameworks/go/go-host',
+ },
+ ],
+ },
{ label: 'Java', slug: 'integrations/frameworks/java' },
{
label: 'JavaScript and Node.js',
diff --git a/src/frontend/config/sidebar/reference.topics.ts b/src/frontend/config/sidebar/reference.topics.ts
index 91ec7435f..7f8024471 100644
--- a/src/frontend/config/sidebar/reference.topics.ts
+++ b/src/frontend/config/sidebar/reference.topics.ts
@@ -570,10 +570,6 @@ export const referenceTopics: StarlightSidebarTopicsUserConfig[number] = {
{ label: 'ASPIRE002', link: '/diagnostics/aspire002' },
{ label: 'ASPIRE003', link: '/diagnostics/aspire003' },
{ label: 'ASPIRE004', link: '/diagnostics/aspire004' },
- {
- label: 'ASPIREATS001',
- link: '/diagnostics/aspireats001',
- },
{
label: 'ASPIREEXPORT005',
link: '/diagnostics/aspireexport005',
@@ -602,6 +598,10 @@ export const referenceTopics: StarlightSidebarTopicsUserConfig[number] = {
label: 'ASPIREEXPORT013',
link: '/diagnostics/aspireexport013',
},
+ {
+ label: 'ASPIREEXPORT016',
+ link: '/diagnostics/aspireexport016',
+ },
{
label: 'ASPIRECERTIFICATES001',
link: '/diagnostics/aspirecertificates001',
@@ -666,6 +666,10 @@ export const referenceTopics: StarlightSidebarTopicsUserConfig[number] = {
label: 'ASPIREPOSTGRES001',
link: '/diagnostics/aspirepostgres001',
},
+ {
+ label: 'ASPIREPROCESSCOMMAND001',
+ link: '/diagnostics/aspireprocesscommand001',
+ },
{
label: 'ASPIREUSERSECRETS001',
link: '/diagnostics/aspireusersecrets001',
@@ -708,6 +712,10 @@ export const referenceTopics: StarlightSidebarTopicsUserConfig[number] = {
label: 'ASPIREEXPORT004',
link: '/diagnostics/aspireexport004',
},
+ {
+ label: 'ASPIREEXPORT015',
+ link: '/diagnostics/aspireexport015',
+ },
{
label: 'ASPIREHOSTINGPYTHON001',
link: '/diagnostics/aspirehostingpython001',
diff --git a/src/frontend/config/twoslash.config.mjs b/src/frontend/config/twoslash.config.mjs
index 0d1f09879..0ab1c1ac5 100644
--- a/src/frontend/config/twoslash.config.mjs
+++ b/src/frontend/config/twoslash.config.mjs
@@ -41,7 +41,8 @@ export const TWOSLASH_ENABLED = true;
* `typescript` package from `ec.config.mjs`).
*
* - `moduleResolution: 100` → `ts.ModuleResolutionKind.Bundler` so
- * `./.modules/aspire.js` falls through to the virtual `.modules/aspire.ts`.
+ * `./.aspire/modules/aspire.mjs` falls through to the virtual
+ * `.aspire/modules/aspire.mts`.
* - `module: 99` → `ts.ModuleKind.ESNext` (paired with bundler resolution).
* - `target: 99` → `ts.ScriptTarget.ESNext` so `lib.esnext.full.d.ts` is the
* default `lib`, pulling in `Date`, `URL`, DOM, and friends via TS's
@@ -79,12 +80,12 @@ export function readAspireTypes() {
/**
* Returns the `extraFiles` map twoslash should mount in its VFS. Returns an
* empty object when the SDK bundle is missing so twoslash can still compile
- * blocks that don't import from `./.modules/aspire.js` (they'll just see
+ * blocks that don't import from `./.aspire/modules/aspire.mjs` (they'll just see
* `any` for the missing module — same fallback `ec.config.mjs` had inline).
*/
export function getTwoslashExtraFiles() {
const { source } = readAspireTypes();
- return source ? { '.modules/aspire.ts': source } : {};
+ return source ? { '.aspire/modules/aspire.mts': source } : {};
}
/**
diff --git a/src/frontend/scripts/generate-twoslash-types.ts b/src/frontend/scripts/generate-twoslash-types.ts
index d5575b795..8cd874691 100644
--- a/src/frontend/scripts/generate-twoslash-types.ts
+++ b/src/frontend/scripts/generate-twoslash-types.ts
@@ -105,13 +105,10 @@ function cleanType(raw: string | undefined): string {
// Collapse fully-qualified namespaced identifiers (with '.' and/or '/' separators)
// to their trailing simple-name segment, in-place — so occurrences inside generic
// args are handled without swallowing surrounding brackets.
- s = s.replace(
- /[A-Za-z_][A-Za-z0-9_]*(?:[./][A-Za-z_][A-Za-z0-9_]*)+/g,
- (m) => {
- const afterSlash = m.includes('/') ? m.slice(m.lastIndexOf('/') + 1) : m;
- return lastDotted(afterSlash);
- }
- );
+ s = s.replace(/[A-Za-z_][A-Za-z0-9_]*(?:[./][A-Za-z_][A-Za-z0-9_]*)+/g, (m) => {
+ const afterSlash = m.includes('/') ? m.slice(m.lastIndexOf('/') + 1) : m;
+ return lastDotted(afterSlash);
+ });
// recover from known junk produced by the upstream generator (stray `]]`)
s = s.replace(/\]\]+/g, '');
return s || 'unknown';
@@ -130,8 +127,22 @@ function camelCase(name: string): string {
function sanitizeIdentifier(name: string): string {
// JS reserved words we might collide with when a param is named e.g. 'default'
const reserved = new Set([
- 'default', 'function', 'class', 'new', 'return', 'delete', 'enum', 'package',
- 'private', 'protected', 'public', 'static', 'interface', 'in', 'of', 'as'
+ 'default',
+ 'function',
+ 'class',
+ 'new',
+ 'return',
+ 'delete',
+ 'enum',
+ 'package',
+ 'private',
+ 'protected',
+ 'public',
+ 'static',
+ 'interface',
+ 'in',
+ 'of',
+ 'as',
]);
return reserved.has(name) ? `_${name}` : name;
}
@@ -156,9 +167,19 @@ function formatParams(params: Parameter[]): string {
// Primitive types we're safe to collapse into an options-object overload. If any
// param uses a richer type (resource/handle/enum) we keep the positional form.
const PRIMITIVE_TYPES = new Set([
- 'string', 'number', 'boolean', 'bigint', 'symbol', 'unknown', 'any',
- 'string[]', 'number[]', 'boolean[]',
- 'Array', 'Array', 'Array',
+ 'string',
+ 'number',
+ 'boolean',
+ 'bigint',
+ 'symbol',
+ 'unknown',
+ 'any',
+ 'string[]',
+ 'number[]',
+ 'boolean[]',
+ 'Array',
+ 'Array',
+ 'Array',
]);
function isPrimitiveParamType(t: string): boolean {
@@ -193,6 +214,14 @@ const PARAM_TYPE_OVERRIDES: Record> = {
name: 'string | ParameterResource',
resourceGroup: 'string | ParameterResource',
},
+ // Current Aspire TS SDKs accept connection-string resources as wait
+ // dependencies; the 13.3 ts-modules snapshot still reports IResource only.
+ waitFor: {
+ dependency: 'IResource | IResourceWithConnectionString',
+ },
+ waitForStart: {
+ dependency: 'IResource | IResourceWithConnectionString',
+ },
};
// Global "broaden this type wherever it appears as a parameter" rules. The
@@ -330,6 +359,107 @@ const genericArity = new Map();
const FREE_FUNCTION_NAMES = new Set(['createBuilder', 'createBuilderWithOptions']);
+// Confirmed Aspire 13.4 API surface that may be absent from the checked-in
+// 13.3 ts-modules snapshot until package data is refreshed. Keep these shims
+// narrow and remove them when update-ts-api brings the APIs into
+// src/data/ts-modules.
+const POST_SNAPSHOT_FREE_FUNCTIONS = [
+ `/**
+ * Creates a reference expression from a tagged template literal
+ */
+export declare function refExpr(strings: TemplateStringsArray, ...values: unknown[]): ReferenceExpression;`,
+];
+
+const POST_SNAPSHOT_DECLARATIONS = [
+ `/**
+ * Enum Aspire.Hosting.ApplicationModel.InputType
+ */
+export type InputType = "Text" | "Number" | "Choice" | "SecretText";
+export declare const InputType: {
+ readonly Text: "Text";
+ readonly Number: "Number";
+ readonly Choice: "Choice";
+ readonly SecretText: "SecretText";
+};`,
+ `export interface ParameterCustomInputOptions {
+ inputType?: InputType;
+ label?: string;
+ placeholder?: string;
+ options?: Record;
+}`,
+ `export interface BeforePublishEvent extends IDistributedApplicationEvent {
+ model: PropertyAccessor;
+ services: PropertyAccessor;
+}`,
+ `export interface AfterPublishEvent extends IDistributedApplicationEvent {
+ model: PropertyAccessor;
+ services: PropertyAccessor;
+}`,
+];
+
+const POST_SNAPSHOT_AUGMENTATIONS = [
+ `export interface IDistributedApplicationBuilder {
+ /**
+ * Subscribes to the BeforePublish event
+ */
+ subscribeBeforePublish(callback: (arg: BeforePublishEvent) => Promise): DistributedApplicationEventSubscription;
+ /**
+ * Subscribes to the AfterPublish event
+ */
+ subscribeAfterPublish(callback: (arg: AfterPublishEvent) => Promise): DistributedApplicationEventSubscription;
+}`,
+ `export interface EventingSubscriberRegistrationContext {
+ /**
+ * Subscribes an eventing subscriber to the BeforePublish event
+ */
+ onBeforePublish(callback: (arg: BeforePublishEvent) => Promise): DistributedApplicationEventSubscription;
+ /**
+ * Subscribes an eventing subscriber to the AfterPublish event
+ */
+ onAfterPublish(callback: (arg: AfterPublishEvent) => Promise): DistributedApplicationEventSubscription;
+}`,
+ `export interface IResource {
+ /**
+ * Assigns Microsoft Foundry roles for this resource
+ */
+ withFoundryRoleAssignments(target: FoundryResource, roles: FoundryRole[]): this;
+ /**
+ * Configures a resource to use a session lifetime
+ */
+ withSessionLifetime(): this;
+ /**
+ * Configures a resource to use a persistent lifetime
+ */
+ withPersistentLifetime(): this;
+ /**
+ * Configures a resource to match the lifetime of another resource
+ */
+ withLifetimeOf(source: IResource): this;
+ /**
+ * Configures a resource to use a persistent lifetime that ends when a parent process exits
+ */
+ withParentProcessLifetime(parentProcessId: number): this;
+}`,
+ `export interface ParameterResource {
+ /**
+ * Customizes the parameter input shown by the dashboard
+ */
+ withCustomInput(options: ParameterCustomInputOptions): this;
+}`,
+ `export interface ComposeFile {
+ /**
+ * Adds a top-level Docker Compose volume
+ */
+ addVolume(name: string, options?: { driver?: string; configure?: (volume: Volume) => Promise }): Promise;
+}`,
+ `export interface Service {
+ /**
+ * Adds a Docker Compose volume mount to a service
+ */
+ addVolume(source: string, target: string, options?: { isReadOnly?: boolean }): Promise;
+}`,
+];
+
// Built-in generic container types the ATS tool emits as pseudo-targets — these
// aren't useful in user-facing docs samples (Dict is already modeled as Record,
// List as Array), so we skip them when assigning methods to target interfaces.
@@ -388,9 +518,8 @@ for (const mod of modules) {
function visitFn(fn: FunctionEntry): void {
extractTypeIdentifiers(cleanType(fn.returnType), referencedTypes);
for (const p of fn.parameters) {
- const t = p.isCallback && p.callbackSignature
- ? cleanType(p.callbackSignature)
- : cleanType(p.type);
+ const t =
+ p.isCallback && p.callbackSignature ? cleanType(p.callbackSignature) : cleanType(p.type);
extractTypeIdentifiers(t, referencedTypes);
// track generic arity: look for Name<...> and count top-level commas
const match = t.match(/([A-Za-z_][A-Za-z0-9_]*)<([^<>]*(?:<[^<>]*>[^<>]*)*)>/g);
@@ -448,13 +577,40 @@ for (const fn of freeFunctions) scanExprForGenerics(cleanType(fn.returnType));
// ---------- emit ----------
const BUILT_IN = new Set([
- 'string', 'number', 'boolean', 'any', 'unknown', 'void', 'never', 'null', 'undefined',
- 'object', 'true', 'false', 'this', 'symbol', 'bigint',
+ 'string',
+ 'number',
+ 'boolean',
+ 'any',
+ 'unknown',
+ 'void',
+ 'never',
+ 'null',
+ 'undefined',
+ 'object',
+ 'true',
+ 'false',
+ 'this',
+ 'symbol',
+ 'bigint',
// JS globals we pass through
- 'Promise', 'Array', 'Map', 'Set', 'Date', 'Error', 'RegExp', 'Record', 'Partial',
- 'Readonly', 'Required', 'Pick', 'Omit', 'Exclude', 'Extract',
+ 'Promise',
+ 'Array',
+ 'Map',
+ 'Set',
+ 'Date',
+ 'Error',
+ 'RegExp',
+ 'Record',
+ 'Partial',
+ 'Readonly',
+ 'Required',
+ 'Pick',
+ 'Omit',
+ 'Exclude',
+ 'Extract',
// Our utility aliases / internal helpers declared at the top of the file
- 'Dict', 'PropertyAccessor',
+ 'Dict',
+ 'PropertyAccessor',
]);
const declaredTypes = new Set([
@@ -467,7 +623,7 @@ const declaredTypes = new Set([
const parts: string[] = [];
parts.push(`// Auto-generated by scripts/generate-twoslash-types.ts — do not edit.`);
parts.push(`// This file is consumed by expressive-code-twoslash to provide hover tooltips`);
-parts.push(`// for TypeScript code samples in the docs that import './.modules/aspire.js'.`);
+parts.push(`// for TypeScript code samples in the docs that import './.aspire/modules/aspire.mjs'.`);
parts.push(``);
parts.push(`declare global {`);
parts.push(` type Dict = Record;`);
@@ -481,7 +637,9 @@ parts.push(`// \`builder.executionContext.isRunMode\` — nested direct
parts.push(`// Model as an intersection covering all three. For object T we include`);
parts.push(`// T directly so nested-property access resolves; for primitives we only`);
parts.push(`// need the callable + accessor surface (primitive intersections collapse).`);
-parts.push(`export type PropertyAccessor = (T extends object ? T : unknown) & (() => Promise) & {`);
+parts.push(
+ `export type PropertyAccessor = (T extends object ? T : unknown) & (() => Promise) & {`
+);
parts.push(` get(): Promise;`);
parts.push(` set(value: T): Promise;`);
parts.push(` // Dict-valued accessors are sometimes set entry-wise in docs`);
@@ -490,6 +648,10 @@ parts.push(` set(key: string, value: unknown): Promise;`);
parts.push(`};`);
parts.push(``);
parts.push(`// ---- enums ----`);
+for (const declaration of POST_SNAPSHOT_DECLARATIONS) {
+ parts.push(declaration);
+ parts.push('');
+}
for (const en of enumTypes) {
parts.push(jsdoc([`Enum ${en.fullName}`]));
const members = en.members.map((m) => JSON.stringify(m)).join(' | ') || 'string';
@@ -499,9 +661,7 @@ for (const en of enumTypes) {
// upstream dump only gives us string-valued members, which matches how the
// generated SDK surfaces .NET enums to TS.
if (en.members.length > 0) {
- const fields = en.members
- .map((m) => ` readonly ${m}: ${JSON.stringify(m)};`)
- .join('\n');
+ const fields = en.members.map((m) => ` readonly ${m}: ${JSON.stringify(m)};`).join('\n');
parts.push(`export declare const ${en.name}: {\n${fields}\n};`);
}
parts.push('');
@@ -547,9 +707,7 @@ function isContainerBacked(h: HandleType): boolean {
if (NON_CONTAINER_HANDLES.has(h.name)) return false;
const pkg = handlePackage.get(h.name);
if (pkg && NON_CONTAINER_PACKAGES.has(pkg)) return false;
- const ifaces = new Set(
- (h.implementedInterfaces ?? []).map((i) => lastDotted(i).split('<')[0])
- );
+ const ifaces = new Set((h.implementedInterfaces ?? []).map((i) => lastDotted(i).split('<')[0]));
return (
ifaces.has('IComputeResource') &&
ifaces.has('IResourceWithArgs') &&
@@ -581,8 +739,7 @@ for (const h of handleTypes) {
}
ancestor = classBaseByName.get(ancestor);
}
- const implementsClause =
- uniqueParents.length > 0 ? ` extends ${uniqueParents.join(', ')}` : '';
+ const implementsClause = uniqueParents.length > 0 ? ` extends ${uniqueParents.join(', ')}` : '';
for (const i of uniqueParents) referencedTypes.add(i);
parts.push(jsdoc([`Handle ${h.fullName}`]));
parts.push(`export interface ${h.name}${implementsClause} {`);
@@ -658,8 +815,7 @@ function emitMember(fn: FunctionEntry, indent: string, out: string[]): void {
// polymorphic `this` so the type flows through. Applies to any method
// flagged `returnsBuilder` that isn't actually creating a new resource.
const isFluentPrefix = /^(with|wait|on|publish)[A-Z0-9]/.test(memberName);
- const ret =
- fn.returnsBuilder && isFluentPrefix ? 'this' : declaredRet;
+ const ret = fn.returnsBuilder && isFluentPrefix ? 'this' : declaredRet;
const effectiveParams = applyParamOverrides(memberName, fn.parameters);
// Record referenced types from overridden param types too, so the stubber sees them.
for (const p of effectiveParams) extractTypeIdentifiers(paramType(p), referencedTypes);
@@ -704,6 +860,16 @@ for (const fn of freeFunctions) {
parts.push(`export declare function ${fn.name}(${formatParams(effectiveParams)}): ${ret};`);
parts.push('');
}
+for (const declaration of POST_SNAPSHOT_FREE_FUNCTIONS) {
+ parts.push(declaration);
+ parts.push('');
+}
+
+parts.push(`// ---- confirmed post-snapshot API augmentations ----`);
+for (const declaration of POST_SNAPSHOT_AUGMENTATIONS) {
+ parts.push(declaration);
+ parts.push('');
+}
// ---- stubs for referenced-but-undeclared types ----
const missing: string[] = [];
@@ -727,7 +893,9 @@ for (const name of missing) {
const extendsClause =
/^IResourceWith/.test(name) && name !== 'IResource' ? ' extends IResource' : '';
if (arity > 0) {
- const generics = Array.from({ length: arity }, (_, i) => `T${i === 0 ? '' : i} = unknown`).join(', ');
+ const generics = Array.from({ length: arity }, (_, i) => `T${i === 0 ? '' : i} = unknown`).join(
+ ', '
+ );
parts.push(`export interface ${name}<${generics}>${extendsClause} {}`);
} else {
parts.push(`export interface ${name}${extendsClause} {}`);
@@ -740,6 +908,10 @@ parts.push(`export {};`);
mkdirSync(OUTPUT_DIR, { recursive: true });
writeFileSync(OUTPUT_FILE, parts.join('\n'), 'utf8');
console.log(`✅ Wrote ${OUTPUT_FILE}`);
-console.log(` - ${dtoTypes.length} DTOs, ${enumTypes.length} enums, ${handleTypes.length} handle types`);
-console.log(` - ${methodsByTarget.size} target interfaces, ${freeFunctions.length} free functions`);
+console.log(
+ ` - ${dtoTypes.length} DTOs, ${enumTypes.length} enums, ${handleTypes.length} handle types`
+);
+console.log(
+ ` - ${methodsByTarget.size} target interfaces, ${freeFunctions.length} free functions`
+);
console.log(` - ${missing.length} stub interfaces for referenced SDK types`);
diff --git a/src/frontend/scripts/update-integrations.ts b/src/frontend/scripts/update-integrations.ts
index 193046c8a..14963e432 100644
--- a/src/frontend/scripts/update-integrations.ts
+++ b/src/frontend/scripts/update-integrations.ts
@@ -22,6 +22,7 @@ const EXCLUDED_PACKAGES = [
'Aspire.RabbitMQ.Client.v7',
'CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps',
'CommunityToolkit.Aspire.Hosting.EventStore',
+ 'CommunityToolkit.Aspire.Hosting.Golang',
'CommunityToolkit.Aspire.EventStore',
];
const OUTPUT_PATH = './src/data/aspire-integrations.json';
diff --git a/src/frontend/src/components/AppHostBuilder.astro b/src/frontend/src/components/AppHostBuilder.astro
index 1f5a00098..404a17370 100644
--- a/src/frontend/src/components/AppHostBuilder.astro
+++ b/src/frontend/src/components/AppHostBuilder.astro
@@ -445,7 +445,7 @@ builder.Build().Run();`,
// Define all possible code combinations — TypeScript
const tsCodes: Record = {
- empty: `import { createBuilder } from './.modules/aspire.js';
+ empty: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -455,7 +455,7 @@ const builder = await createBuilder();
await builder.build().run();`,
- frontend: `import { createBuilder } from './.modules/aspire.js';
+ frontend: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -466,7 +466,7 @@ const frontend = await builder
await builder.build().run();`,
- frontendContainer: `import { createBuilder } from './.modules/aspire.js';
+ frontendContainer: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -482,7 +482,7 @@ const customContainer = await builder
await builder.build().run();`,
- databaseFrontend: `import { createBuilder } from './.modules/aspire.js';
+ databaseFrontend: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -498,7 +498,7 @@ const frontend = await builder
await builder.build().run();`,
- databaseFrontendContainer: `import { createBuilder } from './.modules/aspire.js';
+ databaseFrontendContainer: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -519,7 +519,7 @@ const customContainer = await builder
await builder.build().run();`,
- apiFrontend: `import { createBuilder } from './.modules/aspire.js';
+ apiFrontend: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -536,7 +536,7 @@ const frontend = await builder
await builder.build().run();`,
- apiFrontendContainer: `import { createBuilder } from './.modules/aspire.js';
+ apiFrontendContainer: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -558,7 +558,7 @@ const customContainer = await builder
await builder.build().run();`,
- databaseApiFrontend: `import { createBuilder } from './.modules/aspire.js';
+ databaseApiFrontend: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -581,7 +581,7 @@ const frontend = await builder
await builder.build().run();`,
- databaseApiFrontendContainer: `import { createBuilder } from './.modules/aspire.js';
+ databaseApiFrontendContainer: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -609,7 +609,7 @@ const customContainer = await builder
await builder.build().run();`,
- database: `import { createBuilder } from './.modules/aspire.js';
+ database: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -620,7 +620,7 @@ const postgres = await builder
await builder.build().run();`,
- databaseContainer: `import { createBuilder } from './.modules/aspire.js';
+ databaseContainer: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -636,7 +636,7 @@ const customContainer = await builder
await builder.build().run();`,
- api: `import { createBuilder } from './.modules/aspire.js';
+ api: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -647,7 +647,7 @@ const api = await builder
await builder.build().run();`,
- apiContainer: `import { createBuilder } from './.modules/aspire.js';
+ apiContainer: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -663,7 +663,7 @@ const customContainer = await builder
await builder.build().run();`,
- databaseApi: `import { createBuilder } from './.modules/aspire.js';
+ databaseApi: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -680,7 +680,7 @@ const api = await builder
await builder.build().run();`,
- databaseApiContainer: `import { createBuilder } from './.modules/aspire.js';
+ databaseApiContainer: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -702,7 +702,7 @@ const customContainer = await builder
await builder.build().run();`,
- container: `import { createBuilder } from './.modules/aspire.js';
+ container: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -713,7 +713,7 @@ const customContainer = await builder
await builder.build().run();`,
- frontendDeployment: `import { createBuilder } from './.modules/aspire.js';
+ frontendDeployment: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -726,7 +726,7 @@ const frontend = await builder
await builder.build().run();`,
- frontendContainerDeployment: `import { createBuilder } from './.modules/aspire.js';
+ frontendContainerDeployment: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -745,7 +745,7 @@ const customContainer = await builder
await builder.build().run();`,
- databaseFrontendDeployment: `import { createBuilder } from './.modules/aspire.js';
+ databaseFrontendDeployment: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -765,7 +765,7 @@ const frontend = await builder
await builder.build().run();`,
- databaseFrontendContainerDeployment: `import { createBuilder } from './.modules/aspire.js';
+ databaseFrontendContainerDeployment: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -791,7 +791,7 @@ const customContainer = await builder
await builder.build().run();`,
- apiFrontendDeployment: `import { createBuilder } from './.modules/aspire.js';
+ apiFrontendDeployment: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -811,7 +811,7 @@ const frontend = await builder
await builder.build().run();`,
- apiFrontendContainerDeployment: `import { createBuilder } from './.modules/aspire.js';
+ apiFrontendContainerDeployment: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -837,7 +837,7 @@ const customContainer = await builder
await builder.build().run();`,
- databaseApiFrontendDeployment: `import { createBuilder } from './.modules/aspire.js';
+ databaseApiFrontendDeployment: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -866,7 +866,7 @@ const frontend = await builder
await builder.build().run();`,
- databaseApiFrontendContainerDeployment: `import { createBuilder } from './.modules/aspire.js';
+ databaseApiFrontendContainerDeployment: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -901,7 +901,7 @@ const customContainer = await builder
await builder.build().run();`,
- databaseDeployment: `import { createBuilder } from './.modules/aspire.js';
+ databaseDeployment: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -914,7 +914,7 @@ const postgres = await builder
await builder.build().run();`,
- databaseContainerDeployment: `import { createBuilder } from './.modules/aspire.js';
+ databaseContainerDeployment: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -933,7 +933,7 @@ const customContainer = await builder
await builder.build().run();`,
- apiDeployment: `import { createBuilder } from './.modules/aspire.js';
+ apiDeployment: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -945,7 +945,7 @@ const api = await builder
await builder.build().run();`,
- apiContainerDeployment: `import { createBuilder } from './.modules/aspire.js';
+ apiContainerDeployment: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -963,7 +963,7 @@ const customContainer = await builder
await builder.build().run();`,
- databaseApiDeployment: `import { createBuilder } from './.modules/aspire.js';
+ databaseApiDeployment: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -984,7 +984,7 @@ const api = await builder
await builder.build().run();`,
- databaseApiContainerDeployment: `import { createBuilder } from './.modules/aspire.js';
+ databaseApiContainerDeployment: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -1011,7 +1011,7 @@ const customContainer = await builder
await builder.build().run();`,
- containerDeployment: `import { createBuilder } from './.modules/aspire.js';
+ containerDeployment: `import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -1215,7 +1215,7 @@ await builder.build().run();`,
data-variant={key}
style={key === 'frontend' ? '' : 'display: none;'}
>
-
+
))
}
diff --git a/src/frontend/src/components/SimpleAppHostCode.shared.ts b/src/frontend/src/components/SimpleAppHostCode.shared.ts
index fac55e695..e50a97b20 100644
--- a/src/frontend/src/components/SimpleAppHostCode.shared.ts
+++ b/src/frontend/src/components/SimpleAppHostCode.shared.ts
@@ -184,7 +184,7 @@ builder.Build().Run();
`;
const csharpTypeScript = `
-import { createBuilder } from './.modules/aspire.js';
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -207,7 +207,7 @@ await builder.build().run();
`;
const pythonTypeScript = `
-import { createBuilder } from './.modules/aspire.js';
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -231,7 +231,7 @@ await builder.build().run();
`;
const nodejsTypeScript = `
-import { createBuilder } from './.modules/aspire.js';
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
diff --git a/src/frontend/src/content.config.ts b/src/frontend/src/content.config.ts
index 9bd2819f9..34c8b58bb 100644
--- a/src/frontend/src/content.config.ts
+++ b/src/frontend/src/content.config.ts
@@ -31,6 +31,18 @@ export const collections = {
* site-wide `og-image.png` is used in social cards instead.
*/
og: z.boolean().optional(),
+ /**
+ * SEO-only title override. Used **verbatim** as the page's
+ * `og:title` and `twitter:title` (no `· Aspire` suffix is
+ * appended) so authors can tune the social-card title to the
+ * 50–60 character optimal range without bloating the visible
+ * `
` or sidebar label. Falls back to `title` when unset.
+ *
+ * Prefer rewriting the visible `title` when the natural H1 can
+ * accommodate the longer string. Use `seoTitle` only when the
+ * sidebar/H1 must stay short (commands, terse labels, etc.).
+ */
+ seoTitle: z.string().optional(),
/**
* The date the release was published to NuGet. Used on What's New
* pages to display the release date near the top of the page.
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 57385d665..85ecdd4ec 100644
--- a/src/frontend/src/content/docs/app-host/certificate-configuration.mdx
+++ b/src/frontend/src/content/docs/app-host/certificate-configuration.mdx
@@ -1,6 +1,7 @@
---
title: Certificate configuration
-description: Learn how to configure HTTPS endpoints and certificate trust for resources in Aspire to enable secure communication.
+seoTitle: Aspire HTTPS certificate configuration for AppHost
+description: Configure HTTPS endpoints and certificate trust for Aspire resources to enable secure local development, container-to-container TLS, and trusted browser connections.
---
import { Aside, Tabs, TabItem } from '@astrojs/starlight/components';
@@ -72,11 +73,13 @@ 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)
+### Developer certificate for DCP communication
-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.
+By default, Aspire uses the ASP.NET Core developer certificate to secure communication with its internal Developer Control Plane (DCP) server. This replaces the ephemeral localhost certificate that DCP would otherwise generate itself, and avoids certificate trust errors caused by that 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:
+If no trusted developer certificate is found, Aspire automatically falls back to DCP's ephemeral certificate.
+
+To opt out and use DCP's default ephemeral certificate instead, set `ASPIRE_DCP_USE_DEVELOPER_CERTIFICATE` to `false` in your AppHost's `launchSettings.json` or as an environment variable:
```json title="Properties/launchSettings.json"
{
@@ -84,23 +87,17 @@ Set the `ASPIRE_DCP_USE_DEVELOPER_CERTIFICATE` environment variable to `true` in
"https": {
"commandName": "Project",
"environmentVariables": {
- "ASPIRE_DCP_USE_DEVELOPER_CERTIFICATE": "true"
+ "ASPIRE_DCP_USE_DEVELOPER_CERTIFICATE": "false"
}
}
}
}
```
-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
@@ -135,8 +132,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -217,8 +214,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -333,8 +330,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -416,8 +413,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder, CertificateTrustScope } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder, CertificateTrustScope } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -473,8 +470,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder, CertificateTrustScope } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder, CertificateTrustScope } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -504,8 +501,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder, CertificateTrustScope } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder, CertificateTrustScope } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -621,8 +618,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -687,8 +684,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -723,8 +720,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder, CertificateTrustScope } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder, CertificateTrustScope } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
diff --git a/src/frontend/src/content/docs/app-host/configuration.mdx b/src/frontend/src/content/docs/app-host/configuration.mdx
index 4d665aef4..c66c3dc32 100644
--- a/src/frontend/src/content/docs/app-host/configuration.mdx
+++ b/src/frontend/src/content/docs/app-host/configuration.mdx
@@ -1,6 +1,7 @@
---
title: AppHost configuration
-description: Learn about the Aspire AppHost configuration options.
+seoTitle: Aspire AppHost configuration reference and overview
+description: Configure the Aspire AppHost — environment variables, launch profiles, network ports, container runtime selection, and the options that change orchestration behavior.
---
import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
@@ -51,7 +52,7 @@ In TypeScript AppHosts, profiles live in `aspire.config.json`:
```json title="aspire.config.json"
{
"appHost": {
- "path": "apphost.ts",
+ "path": "apphost.mts",
"language": "typescript/nodejs"
},
"profiles": {
@@ -79,7 +80,7 @@ In TypeScript AppHosts, profiles live in `aspire.config.json`:
| ---------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `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_DCP_USE_DEVELOPER_CERTIFICATE` | `true` | When `true` (the default), Aspire uses the ASP.NET Core developer certificate to secure the internal DCP server instead of an ephemeral certificate generated by DCP. On Windows, Aspire passes the certificate thumbprint to DCP. On macOS and Linux, Aspire passes the certificate and private key file paths (plus the thumbprint) so DCP can verify the loaded certificate. Set to `false` to opt out and use DCP's default ephemeral certificate. If no trusted developer certificate is found, Aspire automatically falls back to the ephemeral certificate. 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. |
diff --git a/src/frontend/src/content/docs/app-host/container-files.mdx b/src/frontend/src/content/docs/app-host/container-files.mdx
index ea2186027..ba1dab14c 100644
--- a/src/frontend/src/content/docs/app-host/container-files.mdx
+++ b/src/frontend/src/content/docs/app-host/container-files.mdx
@@ -1,6 +1,7 @@
---
title: Container files
-description: Learn how to inject files and directories into containers at development time and publish time using the container file APIs in Aspire.
+seoTitle: Inject container files in your Aspire AppHost project
+description: Inject files and directories into Aspire container resources at development and publish time using WithContainerFiles, with options for source paths and permissions.
---
import { Aside, Tabs, TabItem } from '@astrojs/starlight/components';
@@ -346,8 +347,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -385,8 +386,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -431,4 +432,4 @@ Use `ClearContainerFilesSources` to remove any previously configured source path
- [Certificate configuration](/app-host/certificate-configuration/)
- [Add Dockerfiles to your app model](/app-host/withdockerfile/)
-- [Persistent container lifetimes](/app-host/persistent-containers/)
+- [Resource lifetimes](/app-host/resource-lifetimes/)
diff --git a/src/frontend/src/content/docs/app-host/container-registry.mdx b/src/frontend/src/content/docs/app-host/container-registry.mdx
index f93eefb79..3a8efc658 100644
--- a/src/frontend/src/content/docs/app-host/container-registry.mdx
+++ b/src/frontend/src/content/docs/app-host/container-registry.mdx
@@ -1,6 +1,7 @@
---
title: Container registry configuration
-description: Learn how to configure container registries for your Aspire applications, including generic registries and Azure Container Registry.
+seoTitle: Configure container registries for your Aspire AppHost
+description: Configure container registries for Aspire — generic registries, Docker Hub, Azure Container Registry, GitHub Container Registry, and per-resource image tagging.
---
import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
@@ -45,8 +46,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -84,8 +85,8 @@ var api = builder.AddProject("api")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -113,8 +114,8 @@ var api = builder.AddProject("api")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -144,8 +145,8 @@ var api = builder.AddProject("api")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -185,8 +186,8 @@ var api = builder.AddProject("api")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -270,8 +271,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -323,8 +324,8 @@ var api = builder.AddProject("api")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -426,8 +427,8 @@ var internalApi = builder.AddProject("internal-api")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -472,8 +473,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -543,8 +544,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
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 999e63780..fa440161d 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
@@ -1,6 +1,6 @@
---
-title: Docker Compose to Aspire AppHost
-description: Quick reference for converting Docker Compose YAML syntax to Aspire AppHost API calls.
+title: Docker Compose to Aspire AppHost reference
+description: Quick reference for converting Docker Compose YAML syntax to Aspire AppHost API calls — services, networks, volumes, environment variables, and health checks.
---
import LearnMore from '@components/LearnMore.astro';
diff --git a/src/frontend/src/content/docs/app-host/eventing.mdx b/src/frontend/src/content/docs/app-host/eventing.mdx
index deaae65d9..7afd6e2c8 100644
--- a/src/frontend/src/content/docs/app-host/eventing.mdx
+++ b/src/frontend/src/content/docs/app-host/eventing.mdx
@@ -1,6 +1,7 @@
---
title: AppHost eventing APIs
-description: Learn how to use the Aspire AppHost eventing features for lifecycle events, custom event publishing, and event-driven resource orchestration.
+seoTitle: AppHost eventing APIs in your Aspire AppHost project
+description: Use the Aspire AppHost eventing APIs for lifecycle events, custom event publishing, and reactive integrations that respond to resource state transitions at runtime.
---
import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
@@ -16,7 +17,7 @@ The following events are available in the AppHost and occur in the following ord
1. `BeforeStartEvent`: This event is raised before the AppHost starts.
1. `ResourceEndpointsAllocatedEvent`: This event is raised per resource after its endpoints are allocated.
-1. `AfterResourcesCreatedEvent`: This event is raised after the AppHost created resources.
+1. `AfterResourcesCreatedEvent`: This event is raised after resources are created.
@@ -24,8 +25,9 @@ The following events are available in the AppHost and occur in the following ord
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:
-
-
+
+
+
```csharp title="AppHost.cs"
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -39,16 +41,46 @@ builder.OnBeforeStart(static (@event, cancellationToken) =>
return Task.CompletedTask;
});
+builder.OnAfterResourcesCreated(static (@event, cancellationToken) =>
+{
+ var logger = @event.Services.GetRequiredService>();
+ logger.LogInformation("AfterResourcesCreatedEvent");
+ return Task.CompletedTask;
+});
+
builder.Build().Run();
```
+
+
+
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+await builder.subscribeBeforeStart(async () => {
+ console.log('BeforeStartEvent');
+});
+
+await builder.subscribeAfterResourcesCreated(async () => {
+ console.log('AfterResourcesCreatedEvent');
+});
+
+await 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 |
+| Method | Event |
+| ------------------------- | ----------------------------------------------------------------- |
+| `OnBeforeStart` | `BeforeStartEvent` — raised before the AppHost starts |
+| `OnAfterResourcesCreated` | `AfterResourcesCreatedEvent` — raised after resources are created |
+| `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:
@@ -67,61 +99,39 @@ builder.Eventing.Subscribe(
var logger = @event.Services.GetRequiredService>();
logger.LogInformation("AfterResourcesCreatedEvent");
return Task.CompletedTask;
-
-builder.Build().Run();
+ });
```
-
-
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
-
-const builder = await createBuilder();
-const cache = await builder.addRedis("cache");
-
-await builder.subscribeBeforeStart(async (event) => {
- console.log("BeforeStartEvent");
-});
-
-await builder.subscribeAfterResourcesCreated(async (event) => {
- console.log("AfterResourcesCreatedEvent");
-});
-
-await builder.build().run();
-```
-
-
+
When the AppHost is run, by the time the Aspire dashboard is displayed, you should see the following log output in the console:
-```plaintext {2,10,12,14,16,22} data-disable-copy
+```plaintext {2,12} data-disable-copy
info: Program[0]
- 1. BeforeStartEvent
+ BeforeStartEvent
info: Aspire.Hosting.DistributedApplication[0]
Aspire version: 13.1.0
info: Aspire.Hosting.DistributedApplication[0]
Distributed application starting.
info: Aspire.Hosting.DistributedApplication[0]
Application host directory is: ../AspireApp/AspireApp.AppHost
-info: Program[0]
- 2. "cache" ResourceEndpointsAllocatedEvent
-info: Program[0]
- 2. "apiservice" ResourceEndpointsAllocatedEvent
-info: Program[0]
- 2. "webfrontend" ResourceEndpointsAllocatedEvent
-info: Program[0]
- 2. "aspire-dashboard" ResourceEndpointsAllocatedEvent
info: Aspire.Hosting.DistributedApplication[0]
Now listening on: https://localhost:17178
info: Aspire.Hosting.DistributedApplication[0]
Login to the dashboard at https://localhost:17178/login?t=
info: Program[0]
- 3. AfterResourcesCreatedEvent
+ AfterResourcesCreatedEvent
info: Aspire.Hosting.DistributedApplication[0]
Distributed application started. Press Ctrl+C to shut down.
```
-The log output confirms that event handlers are executed in the order of the AppHost life cycle events. The subscription order doesn't affect execution order. The `BeforeStartEvent` is triggered first, followed by each resource's `ResourceEndpointsAllocatedEvent`, and finally `AfterResourcesCreatedEvent`.
+The log output confirms that event handlers are executed in the order of the AppHost life cycle events. The subscription order doesn't affect execution order. The `BeforeStartEvent` is triggered before the AppHost starts, and `AfterResourcesCreatedEvent` is triggered after resources are created.
## Resource eventing
@@ -139,7 +149,10 @@ In addition to the AppHost events, you can also subscribe to resource events. Re
### Subscribe to resource events
-To subscribe to resource events, use the convenience-based extension methods—`On*`. After you have a distributed application builder instance, and a resource builder, walk up to the instance and chain a call to the desired `On*` event API. Consider the following sample _AppHost.cs_ file:
+To subscribe to resource events, use the convenience-based extension methods. After you have a distributed application builder instance, and a resource builder, walk up to the instance and chain a call to the desired event API:
+
+
+
```csharp title="AppHost.cs"
using Microsoft.Extensions.DependencyInjection;
@@ -206,13 +219,58 @@ builder.AddProject("webfrontend")
builder.Build().Run();
```
-The preceding code subscribes to the `InitializeResourceEvent`, `ResourceReadyEvent`, `ResourceEndpointsAllocatedEvent`, `ConnectionStringAvailableEvent`, and `BeforeResourceStartedEvent` events on the `cache` resource. When `AddRedis` is called, it returns an `IResourceBuilder` where `T` is a `RedisResource`. Chain calls to the `On*` methods to subscribe to the events. The `On*` methods return the same `IResourceBuilder` instance, so you can chain multiple calls:
+
+
-- `OnInitializeResource`: Subscribes to the `InitializeResourceEvent`.
-- `OnResourceEndpointsAllocated`: Subscribes to the `ResourceEndpointsAllocatedEvent` event.
-- `OnConnectionStringAvailable`: Subscribes to the `ConnectionStringAvailableEvent` event.
-- `OnBeforeResourceStarted`: Subscribes to the `BeforeResourceStartedEvent` event.
-- `OnResourceReady`: Subscribes to the `ResourceReadyEvent` event.
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+const cache = await builder.addRedis('cache');
+
+await cache.onInitializeResource(async () => {
+ console.log('1. onInitializeResource');
+});
+
+await cache.onResourceEndpointsAllocated(async (event) => {
+ const resource = await event.resource();
+ console.log(`2. endpoints allocated for ${resource.getResourceName()}`);
+});
+
+await cache.onConnectionStringAvailable(async (event) => {
+ const resource = await event.resource();
+ console.log(
+ `3. connection string available for ${resource.getResourceName()}`
+ );
+});
+
+await cache.onBeforeResourceStarted(async () => {
+ console.log('4. onBeforeResourceStarted');
+});
+
+await cache.onResourceReady(async () => {
+ console.log('5. onResourceReady');
+});
+
+await builder.build().run();
+```
+
+
+
+
+The preceding code subscribes to the `InitializeResourceEvent`, `ResourceReadyEvent`, `ResourceEndpointsAllocatedEvent`, `ConnectionStringAvailableEvent`, and `BeforeResourceStartedEvent` events on the `cache` resource. Chain calls to the event methods to subscribe to multiple events on the same resource.
+
+
+
+- `OnInitializeResource` / `onInitializeResource`: Subscribes to the `InitializeResourceEvent`.
+- `OnResourceEndpointsAllocated` / `onResourceEndpointsAllocated`: Subscribes to the `ResourceEndpointsAllocatedEvent` event.
+- `OnConnectionStringAvailable` / `onConnectionStringAvailable`: Subscribes to the `ConnectionStringAvailableEvent` event.
+- `OnBeforeResourceStarted` / `onBeforeResourceStarted`: Subscribes to the `BeforeResourceStartedEvent` event.
+- `OnResourceReady` / `onResourceReady`: Subscribes to the `ResourceReadyEvent` event.
When the AppHost is run, by the time the Aspire dashboard is displayed, you should see the following log output in the console:
@@ -242,7 +300,11 @@ info: Aspire.Hosting.DistributedApplication[0]
```
## Publish events
@@ -312,6 +374,30 @@ internal sealed class LifecycleLoggerSubscriber(ILogger
+ C# eventing subscriber service classes are specific to the C# AppHost hosting
+ model. TypeScript AppHosts can register callback-based subscribers with
+ `addEventingSubscriber` or `tryAddEventingSubscriber`.
+
+
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+builder.addEventingSubscriber(async (events) => {
+ events.onBeforeStart(async () => {
+ console.log('1. BeforeStartEvent');
+ });
+
+ events.onAfterResourcesCreated(async () => {
+ console.log('3. AfterResourcesCreatedEvent');
+ });
+});
+
+await builder.build().run();
+```
+
The subscriber approach keeps builder code minimal while still letting you respond to the same lifecycle moments as inline subscriptions:
- `AddEventingSubscriber()` (or `TryAddEventingSubscriber()`) ensures the subscriber participates whenever the AppHost starts.
@@ -324,12 +410,12 @@ Use this pattern whenever you previously relied on `IDistributedApplicationLifec
If you're migrating from the deprecated `IDistributedApplicationLifecycleHook` interface, use the following mapping:
-| Old pattern (deprecated) | New pattern |
-|--------------------------|-------------|
-| `BeforeStartAsync()` | Subscribe to `BeforeStartEvent` |
+| Old pattern (deprecated) | New pattern |
+| -------------------------------- | ---------------------------------------------- |
+| `BeforeStartAsync()` | Subscribe to `BeforeStartEvent` |
| `AfterEndpointsAllocatedAsync()` | Subscribe to `ResourceEndpointsAllocatedEvent` |
-| `AfterResourcesCreatedAsync()` | Subscribe to `AfterResourcesCreatedEvent` |
-| `TryAddLifecycleHook()` | `TryAddEventingSubscriber()` |
+| `AfterResourcesCreatedAsync()` | Subscribe to `AfterResourcesCreatedEvent` |
+| `TryAddLifecycleHook()` | `TryAddEventingSubscriber()` |
**Before (deprecated):**
@@ -374,7 +460,9 @@ builder.Services.TryAddEventingSubscriber();
```
## Additional events
@@ -385,10 +473,13 @@ Beyond the core lifecycle events, Aspire provides additional events for specific
When publishing your application (generating deployment manifests), these events are raised:
-| Event | When raised | Purpose |
-|-------|-------------|---------|
-| `BeforePublishEvent` | Before publishing begins | Validate or modify resources before manifest generation |
-| `AfterPublishEvent` | After publishing completes | Perform cleanup or post-publish actions |
+| Event | When raised | Purpose |
+| -------------------- | -------------------------- | ------------------------------------------------------- |
+| `BeforePublishEvent` | Before publishing begins | Validate or modify resources before manifest generation |
+| `AfterPublishEvent` | After publishing completes | Perform cleanup or post-publish actions |
+
+
+
```csharp title="AppHost.cs"
builder.OnBeforePublish((@event, ct) =>
@@ -404,14 +495,40 @@ builder.OnAfterPublish((@event, ct) =>
});
```
+
+
+
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+await builder.subscribeBeforePublish(async (event) => {
+ const model = await event.model();
+ console.log(`Publishing ${model.getResources().length} resources`);
+});
+
+await builder.subscribeAfterPublish(async (event) => {
+ const services = await event.services();
+ console.log('Publish completed', services);
+});
+```
+
+
+
+
-For details on what happens during publishing, see [Publishing and deployment overview](/deployment/deploy-with-aspire/).
+ For details on what happens during publishing, see [Publishing and deployment
+ overview](/deployment/deploy-with-aspire/).
### Resource stopped event
The `ResourceStoppedEvent` is raised when a resource stops execution:
+
+
+
```csharp title="AppHost.cs"
builder.Eventing.Subscribe(
cache,
@@ -422,8 +539,29 @@ builder.Eventing.Subscribe(
});
```
+
+
+
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+const cache = await builder.addRedis('cache');
+
+await cache.onResourceStopped(async (event) => {
+ const resource = await event.resource();
+ console.log(`Resource ${resource.getResourceName()} stopped`);
+});
+```
+
+
+
+
## See also
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 036fec721..505629e87 100644
--- a/src/frontend/src/content/docs/app-host/executable-resources.mdx
+++ b/src/frontend/src/content/docs/app-host/executable-resources.mdx
@@ -1,6 +1,6 @@
---
title: Host external executables in Aspire
-description: Learn how to host external executable applications in your Aspire AppHost using AddExecutable.
+description: Host external executable applications in your Aspire AppHost using AddExecutable — model CLI tools, daemons, and language runtimes alongside containers and projects.
---
import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
@@ -43,8 +43,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts"
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts"
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -78,8 +78,8 @@ var app = builder.AddExecutable(
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -107,8 +107,8 @@ var app = builder.AddExecutable("worker", "python", ".", "worker.py")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -146,8 +146,8 @@ var app = builder.AddExecutable("app", "node", ".", "app.js")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder, EndpointProperty } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder, EndpointProperty } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -187,8 +187,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -225,8 +225,8 @@ var e2eTests = builder.AddExecutable("playwright", "npx", ".", "playwright", "te
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -261,8 +261,8 @@ var app = builder.AddExecutable(
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -289,8 +289,8 @@ var app = builder.AddExecutable(
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -328,8 +328,8 @@ var app = builder.AddExecutable("frontend", "npm", ".", "start")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
diff --git a/src/frontend/src/content/docs/app-host/hot-reload-and-watch.mdx b/src/frontend/src/content/docs/app-host/hot-reload-and-watch.mdx
index cee1742e2..8b65e3886 100644
--- a/src/frontend/src/content/docs/app-host/hot-reload-and-watch.mdx
+++ b/src/frontend/src/content/docs/app-host/hot-reload-and-watch.mdx
@@ -1,6 +1,7 @@
---
title: Hot Reload and watch
-description: Learn how hot reload works in Aspire.
+seoTitle: Aspire AppHost hot reload and aspire watch overview
+description: "Learn how hot reload works in Aspire and how `aspire watch` rebuilds and restarts resources automatically when project files change during development."
---
import { Tabs, TabItem } from '@astrojs/starlight/components';
@@ -78,7 +79,7 @@ TypeScript AppHost watch doesn't automatically provide hot reload for every reso
Use this workflow when changes affect:
-- The AppHost model in `apphost.ts`.
+- The AppHost model in `apphost.mts`.
- Resource configuration, endpoints, parameters, or integration setup.
- Multiple services that need to be restarted together under Aspire orchestration.
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 cb8b862ea..79f109372 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
@@ -1,6 +1,6 @@
---
title: Migrate from Docker Compose to Aspire
-description: Learn how to migrate your Docker Compose applications to Aspire and understand the key conceptual differences.
+description: Migrate your Docker Compose applications to Aspire — map services, volumes, networks, and environment variables to AppHost APIs and modernize your developer workflow.
---
import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
@@ -124,8 +124,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -238,8 +238,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -329,8 +329,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -382,7 +382,7 @@ var app = builder.AddContainer("app", "myapp", "latest")
```
-```typescript title="apphost.ts"
+```typescript title="apphost.mts"
const dbPassword = builder.addParameter("dbPassword", { secret: true });
const db = (await builder.addPostgres("db", { password: dbPassword }))
@@ -440,8 +440,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -578,7 +578,7 @@ var app = builder.AddContainer("app", "myapp", "latest")
```
-```typescript title="apphost.ts"
+```typescript title="apphost.mts"
const dbPassword = builder.addParameter("dbPassword", { secret: true });
const postgres = (await builder.addPostgres("db", { password: dbPassword }))
@@ -607,7 +607,7 @@ var api = builder.AddProject("api")
```
-```typescript title="apphost.ts"
+```typescript title="apphost.mts"
const api = await builder.addProject("api", "./Api/Api.csproj", "https")
.withReference(database) // Service discovery
.waitFor(database); // Startup ordering
@@ -635,7 +635,7 @@ var redis = builder.AddRedis("cache")
```
-```typescript title="apphost.ts"
+```typescript title="apphost.mts"
const redis = await builder.addRedis("cache")
.withHostPort(6379);
```
@@ -656,7 +656,7 @@ var api = builder.AddProject("api")
```
-```typescript title="apphost.ts"
+```typescript title="apphost.mts"
const api = await builder.addProject("api", "./Api/Api.csproj", "https")
.withHttpHealthCheck("/health");
```
@@ -685,7 +685,7 @@ var app = builder.AddProject("app")
```
-```typescript title="apphost.ts"
+```typescript title="apphost.mts"
const rabbit = await builder.addContainer("rabbitmq", "rabbitmq", "4.1.4-management-alpine")
.withHealthCheck("rabbitmq-health");
diff --git a/src/frontend/src/content/docs/app-host/persistent-containers.mdx b/src/frontend/src/content/docs/app-host/persistent-containers.mdx
deleted file mode 100644
index ed4f97630..000000000
--- a/src/frontend/src/content/docs/app-host/persistent-containers.mdx
+++ /dev/null
@@ -1,191 +0,0 @@
----
-title: Persistent container lifetimes
-description: Learn how to configure containers to persist and be re-used between Aspire AppHost runs.
----
-
-import { Aside, Tabs, TabItem } from '@astrojs/starlight/components';
-import { Image } from 'astro:assets';
-import persistentContainer from '@assets/whats-new/aspire-9/persistent-container.png';
-import persistentContainerDocker from '@assets/whats-new/aspire-9/persistent-container-docker-desktop.png';
-
-In Aspire, containers follow a typical lifecycle where they're created when the AppHost starts and destroyed when it stops. However, you can specify that you want to use **persistent containers**, which deviate from this standard lifecycle. Persistent containers are created and started by the Aspire orchestrator but aren't destroyed when the AppHost stops, allowing them to persist between runs.
-
-This feature is particularly beneficial for containers that have long startup times, such as databases, as it eliminates the need to wait for these services to initialize on every AppHost restart.
-
-
-
-## Configure a persistent container
-
-To configure a container resource with a persistent lifetime, use the `WithLifetime` method and pass `ContainerLifetime.Persistent`:
-
-
-
-```csharp title="AppHost.cs"
-var builder = DistributedApplication.CreateBuilder(args);
-
-var postgres = builder.AddPostgres("postgres")
- .WithLifetime(ContainerLifetime.Persistent)
- .WithDataVolume();
-
-var db = postgres.AddDatabase("inventorydb");
-
-builder.AddProject("inventory")
- .WithReference(db);
-
-builder.Build().Run();
-```
-
-
-```typescript title="apphost.ts" twoslash
-import { createBuilder, ContainerLifetime } from './.modules/aspire.js';
-
-const builder = await createBuilder();
-
-const postgres = await builder.addPostgres("postgres")
- .withLifetime(ContainerLifetime.Persistent)
- .withDataVolume();
-
-const db = postgres.addDatabase("inventorydb");
-
-await builder.addProject("inventory", "./InventoryService/InventoryService.csproj")
- .withReference(db);
-
-await builder.build().run();
-```
-
-
-
-In the preceding example, the PostgreSQL container is configured to persist between AppHost runs, and `WithDataVolume()` ensures database data is stored in a named volume that survives container recreation. The `inventory` project references the database as normal.
-
-## Dashboard visualization
-
-The Aspire dashboard shows persistent containers with a distinctive pin icon (📌) to help you identify them:
-
-
-
-After the AppHost stops, persistent containers continue running and can be seen in your container runtime (such as Docker Desktop):
-
-
-
-## Configuration change detection
-
-Persistent containers are automatically recreated when the AppHost detects meaningful configuration changes. Aspire tracks a hash of the configuration used to create each container and compares it to the current configuration on subsequent runs. If the configuration differs, the container is recreated with the new settings.
-
-This mechanism ensures that persistent containers stay synchronized with your AppHost configuration without requiring manual intervention.
-
-## Container naming and uniqueness
-
-By default, persistent containers use a naming pattern that combines:
-
-- The service name you specify in your AppHost.
-- A postfix based on a hash of the AppHost project path.
-
-This naming scheme ensures that persistent containers are unique to each AppHost project, preventing conflicts when multiple Aspire projects use the same service names.
-
-For example, if you have a service named `"postgres"` in an AppHost project located at `/path/to/MyApp.AppHost`, the container name might be `postgres-abc123def` where `abc123def` is derived from the project path hash.
-
-### Custom container names
-
-For advanced scenarios, you can set a custom container name using the `WithContainerName` method:
-
-
-
-```csharp title="AppHost.cs"
-var builder = DistributedApplication.CreateBuilder(args);
-
-var postgres = builder.AddPostgres("postgres")
- .WithLifetime(ContainerLifetime.Persistent)
- .WithContainerName("my-shared-postgres");
-
-builder.Build().Run();
-```
-
-
-```typescript title="apphost.ts" twoslash
-import { createBuilder, ContainerLifetime } from './.modules/aspire.js';
-
-const builder = await createBuilder();
-
-const postgres = await builder.addPostgres("postgres")
- .withLifetime(ContainerLifetime.Persistent)
- .withContainerName("my-shared-postgres");
-
-await builder.build().run();
-```
-
-
-
-When you specify a custom container name, Aspire first checks if a container with that name already exists. If a container with that name exists and was previously created by Aspire, it follows the normal persistent container behavior and can be automatically recreated if the configuration changes. If a container with that name exists but wasn't created by Aspire, it won't be managed or recreated by the AppHost. If no container with the custom name exists, Aspire creates a new one.
-
-## Manual cleanup
-
-
-
-You can clean up persistent containers using Docker CLI commands:
-
-```bash
-# Stop the container
-docker stop my-container-name
-
-# Remove the container
-docker rm my-container-name
-```
-
-Alternatively, you can use Docker Desktop or your preferred container management tool to stop and remove persistent containers.
-
-## Use cases and benefits
-
-Persistent containers are ideal for:
-
-- **Database services**: PostgreSQL, SQL Server, MySQL, and other databases that take time to initialize and load data.
-- **Message brokers**: RabbitMQ, Redis, and similar services that benefit from maintaining state between runs.
-- **Development data**: Containers with test data or configurations that you want to preserve during development iterations.
-- **Shared services**: Services that multiple AppHosts or development team members can share.
-
-## Container lifetime vs. data durability
-
-`ContainerLifetime.Persistent` and `WithDataVolume()` serve different purposes and are often used together. The following table summarizes the behavior of each combination:
-
-| Configuration | Container behavior | Data behavior |
-|---|---|---|
-| Neither (default) | Created on start, destroyed on stop | Lost every time the AppHost stops |
-| `WithLifetime(ContainerLifetime.Persistent)` only | Stays running between AppHost runs | Survives AppHost restarts, but **lost if the container is recreated** (config change, pruning, image update) |
-| `WithDataVolume()` only | Created on start, destroyed on stop | Persists in a named volume—**survives container recreation** |
-| Both (recommended for databases) | Stays running between AppHost runs | Persists in a named volume—survives container recreation |
-
-For **databases and other stateful services**, use both APIs together so you get fast startup (the container stays running) _and_ data safety (a volume protects data even if the container is recreated):
-
-
-
-```csharp title="AppHost.cs"
-var postgres = builder.AddPostgres("postgres")
- .WithLifetime(ContainerLifetime.Persistent)
- .WithDataVolume();
-```
-
-
-```typescript title="apphost.ts"
-const postgres = await builder.addPostgres("postgres")
- .withLifetime(ContainerLifetime.Persistent)
- .withDataVolume();
-```
-
-
-
-For **caches or other ephemeral state**, `WithLifetime(ContainerLifetime.Persistent)` alone may be sufficient because losing data on container recreation is acceptable.
-
-
diff --git a/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx b/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx
new file mode 100644
index 000000000..3335e6aa7
--- /dev/null
+++ b/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx
@@ -0,0 +1,452 @@
+---
+title: Configure resource lifetimes in Aspire
+description: Learn how session, persistent, resource-scoped, and parent-process lifetimes control Aspire containers, executables, and projects.
+---
+
+import { Tabs, TabItem } from '@astrojs/starlight/components';
+import { Image } from 'astro:assets';
+import persistentContainer from '@assets/whats-new/aspire-9/persistent-container.png';
+import persistentContainerDocker from '@assets/whats-new/aspire-9/persistent-container-docker-desktop.png';
+
+Aspire resources support a number of different lifetime modes. For example, the default **session lifetime** starts a resource when the AppHost starts and shuts it down when the AppHost exits. A **persistent lifetime** leaves a resource running when the AppHost exits and can reuse the same instance on the next run.
+
+Resource lifetimes apply to containers, executables, and projects. Persistent executable and project lifetimes are experimental in Aspire 13.4. You can use different lifetimes for resources that take time to initialize, need stable local endpoints, should remain available while you restart or rebuild the AppHost, or need to match another resource's lifetime.
+
+:::caution[Experimental shared lifetime APIs]
+The shared lifetime APIs are experimental and emit diagnostic `ASPIREPERSISTENCE001`. The existing container-specific `WithLifetime(ContainerLifetime.Persistent)` and `WithLifetime(ContainerLifetime.Session)` APIs remain supported for container resources.
+:::
+
+## Lifetime modes
+
+Use the shared lifetime APIs for new code. They support container, executable, and project resources.
+
+### Session lifetime
+
+A session lifetime creates the resource when the AppHost starts and disposes of it when the AppHost stops. This is the default lifetime for resources, so you usually don't need to configure it explicitly.
+
+Use session lifetime for resources that should only exist while the AppHost is running, such as local test dependencies, temporary containers, or processes that don't need stable state across runs. Session resources also default to proxied endpoints, which are available while the AppHost is running.
+
+If you previously configured another lifetime and want to return a resource to the default behavior, call `WithSessionLifetime()`. For container resources, `WithLifetime(ContainerLifetime.Session)` is still supported.
+
+### Persistent lifetime
+
+A persistent lifetime reuses a previously created resource when possible and doesn't dispose of it when the AppHost stops. Configure this behavior with `WithPersistentLifetime()`, or with the existing container-specific `WithLifetime(ContainerLifetime.Persistent)` API for container resources.
+
+Use persistent lifetime for resources that are expensive to initialize, need stable local endpoints, or should remain available while you restart or rebuild the AppHost. Common examples include databases, message brokers, emulators, long-running executables, and project resources that should continue running after the AppHost exits.
+
+:::caution[Configuration changes can recreate persistent resources]
+Persistent resources are automatically recreated when the AppHost detects meaningful configuration changes. If the configuration differs, the resource is recreated with the new settings.
+:::
+
+Persistent resources default to proxyless endpoints so a stable local endpoint can remain reachable after the AppHost stops. You can still configure endpoint proxy behavior explicitly. For example, set `isProxied: true` or `IsProxied = true` when you need Aspire's proxy for a specific endpoint, or disable endpoint proxy support when you intentionally want every endpoint on a resource to be proxyless. Persistent executable endpoints must have a concrete `port` or `targetPort`; automatically persisted random executable ports aren't supported.
+
+:::danger[Persistent container ≠ persistent data]
+Persistent container lifetime doesn't guarantee data durability. For details, see [Container lifetime vs. data durability](#container-lifetime-vs-data-durability).
+:::
+
+:::note[Persistent resources and replicas]
+Persistent resources don't support replicas because they depend on a single unique resource identifier to be resolved across AppHost runs.
+
+Persistent resources aren't compatible with Aspire IDE debugging sessions. If you need to debug a persistent executable or project, use your debugger's attach mode if one is available.
+:::
+
+### Parent-process lifetime
+
+A parent-process lifetime keeps a resource available across AppHost restarts, but scopes cleanup to a parent process. Configure this behavior with `WithParentProcessLifetime(processId)`.
+
+Use parent-process lifetime for resources that should outlive an individual AppHost run but still be cleaned up when a broader development tool, IDE, or other owning process exits.
+
+Parent-process lifetime resources share persistent resource behavior across AppHost runs. If Aspire detects meaningful configuration changes on a subsequent run, the resource is recreated with the new settings.
+
+The parent process ID must be the valid ID of a running process. Aspire records both the process ID and the process identity timestamp so cleanup follows the specific process instance instead of accidentally matching a reused process ID.
+
+### Resource-scoped lifetime
+
+A resource-scoped lifetime configures one resource to use another resource's effective lifetime. Configure this behavior with `WithLifetimeOf(resource)`.
+
+Use resource-scoped lifetime when a companion resource should follow the lifetime choice of another resource. This is useful for sidecars, helper executables, or child resources that should become persistent only when the resource they support is persistent.
+
+Aspire evaluates the source resource's lifetime when it prepares the application model, so later lifetime changes to the source resource are reflected by the dependent resource. The source and dependent resources must both support lifetime configuration.
+
+## Configure a persistent container
+
+For new code, configure a persistent container with `WithPersistentLifetime()`:
+
+
+
+
+```csharp title="AppHost.cs"
+var builder = DistributedApplication.CreateBuilder(args);
+
+var postgres = builder.AddPostgres("postgres")
+ .WithPersistentLifetime()
+ .WithDataVolume();
+
+var db = postgres.AddDatabase("inventorydb");
+
+builder.AddProject("inventory")
+ .WithReference(db);
+
+builder.Build().Run();
+```
+
+
+
+
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+const postgres = await builder.addPostgres('postgres');
+await postgres.withPersistentLifetime();
+await postgres.withDataVolume();
+
+const db = postgres.addDatabase('inventorydb');
+
+const inventory = await builder.addProject(
+ 'inventory',
+ './InventoryService/InventoryService.csproj'
+);
+await inventory.withReference(db);
+
+await builder.build().run();
+```
+
+
+
+
+In the preceding example, the PostgreSQL container persists between AppHost runs, and `WithDataVolume()` stores database data in a named volume that survives container recreation. The `inventory` project references the database as normal.
+
+## Configure a persistent executable
+
+Executable resources can also use persistent lifetimes. Persistent executables are useful for local services that have expensive startup, need stable process identity, or should remain reachable while the AppHost restarts.
+
+
+
+
+```csharp title="AppHost.cs"
+var builder = DistributedApplication.CreateBuilder(args);
+
+var worker = builder.AddExecutable("worker", "node", "../worker", "server.js")
+ .WithHttpEndpoint(port: 5050, targetPort: 5050)
+ .WithPersistentLifetime();
+
+builder.Build().Run();
+```
+
+
+
+
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+const worker = await builder.addExecutable('worker', 'node', '../worker', [
+ 'server.js',
+]);
+await worker.withHttpEndpoint({ port: 5050, targetPort: 5050 });
+await worker.withPersistentLifetime();
+
+await builder.build().run();
+```
+
+
+
+
+Configure a concrete `port` or `targetPort` for persistent executable endpoints; automatically persisted random executable ports aren't supported.
+
+## Configure a persistent project
+
+Project resources can use persistent lifetimes when you want the project process to continue running after the AppHost exits.
+
+
+
+
+```csharp title="AppHost.cs"
+var builder = DistributedApplication.CreateBuilder(args);
+
+var api = builder.AddProject("api")
+ .WithPersistentLifetime();
+
+builder.Build().Run();
+```
+
+
+
+
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+const api = await builder.addProject('api', '../ApiService/ApiService.csproj');
+await api.withPersistentLifetime();
+
+await builder.build().run();
+```
+
+
+
+
+Persistent project and executable resources are run by Aspire's orchestrator so it can manage their lifecycle consistently. Persistent project and executable resources don't support replicas.
+
+## Match another resource's lifetime
+
+Use `WithLifetimeOf` when a companion resource should follow another resource's lifetime. This is useful when a sidecar, helper process, or supporting service should become persistent only when its source resource is persistent.
+
+
+
+
+```csharp title="AppHost.cs"
+var builder = DistributedApplication.CreateBuilder(args);
+
+var database = builder.AddPostgres("postgres")
+ .WithPersistentLifetime()
+ .WithDataVolume();
+
+var companion = builder.AddExecutable("companion", "dotnet", "../Companion", "Companion.dll")
+ .WithLifetimeOf(database);
+
+builder.Build().Run();
+```
+
+
+
+
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+const database = await builder.addPostgres('postgres');
+await database.withPersistentLifetime();
+await database.withDataVolume();
+
+const companion = await builder.addExecutable(
+ 'companion',
+ 'dotnet',
+ '../Companion',
+ ['Companion.dll']
+);
+await companion.withLifetimeOf(database);
+
+await builder.build().run();
+```
+
+
+
+
+The dependent resource's lifetime is evaluated when Aspire prepares the application model, so later changes to the source resource's lifetime are reflected by the dependent resource.
+
+## Scope cleanup to a parent process
+
+Use `WithParentProcessLifetime` when a resource should survive AppHost restarts but be cleaned up when another process exits. Aspire records the parent process identity instead of retaining a live process handle, so the cleanup scope follows the specific process instance instead of a reused process ID.
+
+
+
+
+```csharp title="AppHost.cs"
+var builder = DistributedApplication.CreateBuilder(args);
+
+var parentProcessId = int.Parse(builder.Configuration["RESOURCE_PARENT_PROCESS_ID"]!);
+
+var worker = builder.AddExecutable("scoped-worker", "node", "../worker", "server.js")
+ .WithParentProcessLifetime(parentProcessId);
+
+builder.Build().Run();
+```
+
+
+
+
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+const parentProcessId = 1234;
+
+const worker = await builder.addExecutable(
+ 'scoped-worker',
+ 'node',
+ '../worker',
+ ['server.js']
+);
+await worker.withParentProcessLifetime(parentProcessId);
+
+await builder.build().run();
+```
+
+
+
+
+The parent process ID must be greater than zero and identify a running process.
+
+## Use the container-specific lifetime API
+
+The older container-specific lifetime API is still supported. Use `WithLifetime(ContainerLifetime.Persistent)` to keep a container running across AppHost restarts, or `WithLifetime(ContainerLifetime.Session)` to explicitly use the default session behavior.
+
+For new code, prefer the shared `WithPersistentLifetime()` and `WithSessionLifetime()` APIs because they work consistently across containers, executables, and projects.
+
+
+
+
+```csharp title="AppHost.cs"
+var builder = DistributedApplication.CreateBuilder(args);
+
+var postgres = builder.AddPostgres("postgres")
+ .WithLifetime(ContainerLifetime.Persistent)
+ .WithDataVolume();
+
+builder.Build().Run();
+```
+
+
+
+
+```typescript title="apphost.mts" twoslash
+import { ContainerLifetime, createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+const postgres = await builder.addPostgres('postgres');
+await postgres.withLifetime(ContainerLifetime.Persistent);
+await postgres.withDataVolume();
+
+await builder.build().run();
+```
+
+
+
+
+## Dashboard visualization
+
+The Aspire dashboard shows persistent resources with a distinctive pin icon to help you identify them:
+
+
+
+After the AppHost stops, persistent containers continue running and can be seen in your container runtime (such as Docker Desktop):
+
+
+
+## Container naming and uniqueness
+
+By default, persistent containers use a naming pattern that combines:
+
+- The service name you specify in your AppHost.
+- A postfix based on a hash of the AppHost project path.
+
+This naming scheme ensures that persistent containers are unique to each AppHost project, preventing conflicts when multiple Aspire projects use the same service names.
+
+For example, if you have a service named `"postgres"` in an AppHost project located at `/path/to/MyApp.AppHost`, the container name might be `postgres-abc123def` where `abc123def` is derived from the project path hash.
+
+### Custom container names
+
+For advanced scenarios, you can set a custom container name using the `WithContainerName` method:
+
+
+
+
+```csharp title="AppHost.cs"
+var builder = DistributedApplication.CreateBuilder(args);
+
+var postgres = builder.AddPostgres("postgres")
+ .WithPersistentLifetime()
+ .WithContainerName("my-shared-postgres");
+
+builder.Build().Run();
+```
+
+
+
+
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+const postgres = await builder.addPostgres('postgres');
+await postgres.withPersistentLifetime();
+await postgres.withContainerName('my-shared-postgres');
+
+await builder.build().run();
+```
+
+
+
+
+When you specify a custom container name, Aspire first checks if a container with that name already exists. If a container with that name exists and was previously created by Aspire, it follows the normal persistent container behavior and can be automatically recreated if the configuration changes. If a container with that name exists but wasn't created by Aspire, it won't be managed or recreated by the AppHost. If no container with the custom name exists, Aspire creates a new one.
+
+## Executable and project naming and uniqueness
+
+Persistent executable and project resources are scoped to a specific AppHost instance and uniquely identified by their resource name within that scope. Two executable or project resources with the same name in different AppHosts don't collide with each other; they result in separate process instances.
+
+## Manual cleanup
+
+:::caution
+Persistent resources aren't automatically removed when you stop the AppHost. To delete them, stop and remove the underlying container or process with the resource's runtime or operating system tools.
+:::
+
+For persistent containers, use Docker CLI commands, Docker Desktop, or your preferred container management tool to stop and remove the container:
+
+```bash title="Stop and remove a persistent container"
+# Stop the container
+docker stop my-container-name
+
+# Remove the container
+docker rm my-container-name
+```
+
+For persistent executable and project resources, stop the running process with your operating system process manager or terminal tools. You can also stop a persistent resource from the Aspire dashboard if the runtime-specific cleanup option isn't straightforward.
+
+## Container lifetime vs. data durability
+
+`WithPersistentLifetime()` and `WithDataVolume()` serve different purposes and are often used together. The following table summarizes the behavior of each combination for container resources:
+
+| Configuration | Container behavior | Data behavior |
+| -------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------ |
+| Neither (default) | Created on start, destroyed on stop | Lost every time the AppHost stops |
+| `WithPersistentLifetime()` only | Stays running between AppHost runs | Survives AppHost restarts, but **lost if the container is recreated** (config change, pruning, image update) |
+| `WithDataVolume()` only | Created on start, destroyed on stop | Persists in a named volume—**survives container recreation** |
+| Both (recommended for databases) | Stays running between AppHost runs | Persists in a named volume—survives container recreation |
+
+For **databases and other stateful services**, use both APIs together so you get fast startup (the container stays running) _and_ data safety (a volume protects data even if the container is recreated):
+
+
+
+
+```csharp title="AppHost.cs"
+var postgres = builder.AddPostgres("postgres")
+ .WithPersistentLifetime()
+ .WithDataVolume();
+```
+
+
+
+
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+const postgres = await builder.addPostgres('postgres');
+await postgres.withPersistentLifetime();
+await postgres.withDataVolume();
+```
+
+
+
+
+For **caches or other ephemeral state**, `WithPersistentLifetime()` alone may be sufficient because losing data on container recreation is acceptable.
+
+:::tip
+For more details on volumes and bind mounts, see [Persist data using volumes](/fundamentals/persist-data-volumes/).
+:::
diff --git a/src/frontend/src/content/docs/app-host/typescript-apphost.mdx b/src/frontend/src/content/docs/app-host/typescript-apphost.mdx
index 37009fc3b..dc936b3fa 100644
--- a/src/frontend/src/content/docs/app-host/typescript-apphost.mdx
+++ b/src/frontend/src/content/docs/app-host/typescript-apphost.mdx
@@ -1,24 +1,43 @@
---
title: TypeScript AppHost project structure
-description: Learn about the files and configuration that make up a TypeScript AppHost project.
+seoTitle: Aspire TypeScript AppHost project structure overview
+description: Learn the files and configuration that make up a TypeScript AppHost project — entry point, package manifest, dependencies, and how the AppHost runs Aspire resources.
---
import { Aside, FileTree, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
import LearnMore from '@components/LearnMore.astro';
-When you create a TypeScript AppHost with `aspire new` or `aspire init --language typescript`, the CLI scaffolds a project with the following structure:
+When you create a TypeScript AppHost with `aspire new`, the CLI scaffolds a project with the following structure:
- my-apphost/
- - .modules/ Generated TypeScript SDK (do not edit)
- - aspire.ts
- - base.ts
- - transport.ts
- - apphost.ts Your AppHost entry point
+ - .aspire/modules/ Generated TypeScript SDK (do not edit)
+ - aspire.mts
+ - base.mts
+ - transport.mts
+ - apphost.mts Your AppHost entry point
- aspire.config.json Aspire configuration
- package.json
- - tsconfig.json
+ - tsconfig.apphost.json
+
+
+
+When you run `aspire init --language typescript` in an existing JavaScript or TypeScript app that already has a root `package.json`, Aspire creates the AppHost in a nested `aspire-apphost/` package. The root `aspire.config.json` points to `aspire-apphost/apphost.mts`, and the root `package.json` gets Aspire delegate scripts so the existing app package keeps its own module and toolchain settings:
+
+
+
+- my-existing-app/
+ - aspire-apphost/
+ - .aspire/modules/ Generated TypeScript SDK (do not edit)
+ - aspire.mts
+ - base.mts
+ - transport.mts
+ - apphost.mts Your AppHost entry point
+ - package.json
+ - tsconfig.apphost.json
+ - aspire.config.json Aspire configuration
+ - package.json Existing app package with Aspire delegate scripts
@@ -29,7 +48,7 @@ The `aspire.config.json` file is the central configuration for your AppHost. It
```json title="aspire.config.json"
{
"appHost": {
- "path": "apphost.ts",
+ "path": "apphost.mts",
"language": "typescript/nodejs"
},
"packages": {
@@ -51,20 +70,20 @@ The `aspire.config.json` file is the central configuration for your AppHost. It
| Section | Description |
|---------|-------------|
-| `appHost.path` | Path to your AppHost entry point (`apphost.ts`) |
+| `appHost.path` | Path to your AppHost entry point (`apphost.mts`) |
| `appHost.language` | Language runtime (`typescript/nodejs`) |
| `packages` | Hosting integration packages and their versions. Added automatically by `aspire add`. |
| `profiles` | Launch profiles with dashboard URLs and environment variables |
-### Adding integrations
+### Add and restore integrations
-When you run `aspire add`, the CLI adds the package to the `packages` section and regenerates the TypeScript SDK:
+Use `aspire add` from the AppHost root to add hosting integrations. The CLI adds the package to the `packages` section, restores AppHost dependencies, and regenerates the TypeScript SDK in `.aspire/modules/`:
```bash title="Add an integration"
aspire add redis
```
-This updates `aspire.config.json`:
+This updates `aspire.config.json` so the package is restored the next time the AppHost runs:
```json title="aspire.config.json" ins={4}
{
@@ -75,6 +94,12 @@ This updates `aspire.config.json`:
}
```
+Run `aspire restore` when you want to regenerate `.aspire/modules/` without starting the AppHost, such as after switching branches, updating package versions, or preparing a CI job:
+
+```bash title="Restore a TypeScript AppHost"
+aspire restore
+```
+
### Project references for local development
You can reference a local hosting integration project by using a `.csproj` path instead of a version:
@@ -91,15 +116,15 @@ You can reference a local hosting integration project by using a `.csproj` path
See [Multi-language integrations](/extensibility/multi-language-integration-authoring/) for details on building hosting integrations that work with TypeScript AppHosts.
-## .modules/ directory
+## .aspire/modules/ directory
-The `.modules/` directory contains the generated TypeScript SDK. It's created and updated automatically by the Aspire CLI — **do not edit these files**.
+The `.aspire/modules/` directory under the AppHost root contains the generated TypeScript SDK. It's created and updated automatically by the Aspire CLI — **do not edit these files**.
| File | Purpose |
|------|---------|
-| `aspire.ts` | Generated typed API for all your installed integrations |
-| `base.ts` | Base types and handle infrastructure |
-| `transport.ts` | JSON-RPC transport layer |
+| `aspire.mts` | Generated typed API for all your installed integrations |
+| `base.mts` | Base types and handle infrastructure |
+| `transport.mts` | JSON-RPC transport layer |
The SDK regenerates when:
@@ -107,22 +132,22 @@ The SDK regenerates when:
- You run `aspire run` or `aspire start` and the package list has changed
- You run `aspire restore` to manually regenerate
-Your `apphost.ts` imports from this SDK:
+Your `apphost.mts` imports from this SDK:
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
```
-## apphost.ts
+## apphost.mts
The entry point for your AppHost. This is where you define your application's resources and their relationships:
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -136,23 +161,124 @@ const api = await builder
await builder.build().run();
```
+## Legacy `apphost.ts` projects (pre-13.4)
+
+TypeScript AppHosts scaffolded by Aspire CLI versions earlier than 13.4 use a slightly different layout: the entry point was `apphost.ts` (not `apphost.mts`), and the generated TypeScript SDK lives in `./.modules/` (not `./.aspire/modules/`):
+
+
+
+- my-apphost/
+ - .modules/ Generated TypeScript SDK (do not edit)
+ - aspire.ts
+ - base.ts
+ - transport.ts
+ - apphost.ts Your AppHost entry point
+ - aspire.config.json Aspire configuration
+ - package.json
+ - tsconfig.apphost.json
+
+
+
+The pre-13.4 entry point imports the SDK with the `.js` extension:
+
+```typescript title="apphost.ts (pre-13.4)"
+import { createBuilder } from './.modules/aspire.js';
+```
+
+### Compatibility in 13.4
+
+The 13.4 Aspire CLI keeps these projects working without any changes:
+
+- When the CLI sees an `apphost.ts` file with no `apphost.mts` alongside it — either because `aspire.config.json` sets `appHost.path` to `apphost.ts`, or because a disk scan finds `apphost.ts` and no `apphost.mts` — it switches to the legacy output layout for that project.
+- Generated SDK files are written to `./.modules/` instead of `./.aspire/modules/`.
+- Generated files are rewritten from `.mts`/`.mjs` to `.ts`/`.js`, including the inter-module import specifiers inside the SDK, so the existing `./.modules/aspire.js` import in your `apphost.ts` continues to resolve.
+
+`aspire add`, `aspire restore`, `aspire run`, and `aspire start` all continue to work against an existing `apphost.ts` project with no edits required.
+
+
+
+### Migrating to the new `apphost.mts` layout
+
+If you'd like to move an existing project to the new default layout, the migration is mechanical. The steps below assume an AppHost root with `apphost.ts`, `./.modules/`, and the legacy `appHost.path` value:
+
+
+
+1. **Rename the entry point file.**
+
+ ```bash
+ git mv apphost.ts apphost.mts
+ ```
+
+2. **Update the SDK import** in `apphost.mts` to use the new folder and the `.mjs` extension:
+
+ ```typescript title="apphost.mts" del={1} ins={2}
+ import { createBuilder } from './.modules/aspire.js';
+ import { createBuilder } from './.aspire/modules/aspire.mjs';
+ ```
+
+3. **Update `appHost.path`** in `aspire.config.json`:
+
+ ```json title="aspire.config.json" del={3} ins={4}
+ {
+ "appHost": {
+ "path": "apphost.ts",
+ "path": "apphost.mts",
+ "language": "typescript/nodejs"
+ }
+ }
+ ```
+
+4. **Update `tsconfig.apphost.json`** so its `include` (and any related glob) entries point at the new file and folder. Replace references to `apphost.ts` with `apphost.mts`, and references to `.modules/` with `.aspire/modules/`. For example:
+
+ ```json title="tsconfig.apphost.json" del={4,5} ins={6,7}
+ {
+ "compilerOptions": { /* ... */ },
+ "include": [
+ "apphost.ts",
+ ".modules/**/*.ts",
+ "apphost.mts",
+ ".aspire/modules/**/*.mts"
+ ]
+ }
+ ```
+
+5. **Delete the old generated SDK folder and regenerate.** The CLI now writes the SDK under `./.aspire/modules/`, so the old folder is no longer used:
+
+ ```bash
+ rm -rf .modules
+ aspire restore
+ ```
+
+ If your `.gitignore` ignores `.modules/`, replace that entry with `.aspire/` (or `.aspire/modules/`) to match the new location.
+
+
+
+After migrating, `aspire run` behaves exactly the same as before — the CLI now uses the modern output path because `apphost.mts` is present.
+
## Package managers
-The Aspire CLI automatically detects which package manager your TypeScript AppHost uses by inspecting lock files and the `packageManager` field in `package.json`. The following package managers are supported:
+The Aspire CLI supports the following package managers at the **AppHost root** — the directory that contains your `apphost.mts` and `aspire.config.json`. The CLI selects between them by inspecting package manager signals, including the `packageManager` field in `package.json`, lock files, and package manager configuration in the AppHost root.
-| Package manager | Lock file detected | Notes |
+| Package manager | Detection signals | Version expectation |
|---|---|---|
-| npm | `package-lock.json` | Default; no extra setup required |
-| pnpm | `pnpm-lock.yaml` | |
-| Yarn (v4+) | `yarn.lock` | Must be Yarn 4 or later (Berry) |
-| Bun | `bun.lock` / `bun.lockb` | |
+| npm | `packageManager`, `package-lock.json`, or no other signal | npm 10 or later; npm is the default |
+| pnpm | `packageManager` or `pnpm-lock.yaml` | pnpm 10 or later |
+| Yarn | `packageManager`, `yarn.lock`, `.yarnrc.yml`, or `.yarn/` | Yarn 4 or later (Berry) |
+| Bun | `packageManager`, `bun.lock`, or `bun.lockb` | Bun 1.2 or later |
+| Yarn Classic (v1) | `yarn.lock` with `# yarn lockfile v1` or `packageManager` with `yarn@1.x` | Not supported |
+
+This policy governs the **AppHost root only**. Apps the AppHost orchestrates — for example, a Node.js service added with `addNodeApp`, a Bun guest app, or a workspace package — can use any package manager their own tooling requires; they are independent of the AppHost-root toolchain.
+
+Aspire end-to-end tests cover TypeScript AppHosts with representative `packageManager` pins such as `npm@10.0.0`, `pnpm@10.0.0`, `yarn@4.14.1`, and `bun@1.2.0`. These tested versions are representative points within the supported ranges, not the only versions you can use.
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -285,8 +353,8 @@ var container = builder.AddDockerfile("mygoapp", "relative/context/path")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -337,8 +405,8 @@ var container = builder.AddDockerfile("myapp", "relative/context/path")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
diff --git a/src/frontend/src/content/docs/architecture/multi-language-architecture.mdx b/src/frontend/src/content/docs/architecture/multi-language-architecture.mdx
index ef2c28aab..0709757d1 100644
--- a/src/frontend/src/content/docs/architecture/multi-language-architecture.mdx
+++ b/src/frontend/src/content/docs/architecture/multi-language-architecture.mdx
@@ -1,6 +1,7 @@
---
title: Multi-language architecture
-description: Understand how Aspire uses a guest/host architecture, ATS, SDK generation, and local token-based authentication to support multi-language AppHosts.
+seoTitle: 'Aspire multi-language architecture: C# and TypeScript'
+description: Understand how Aspire uses a guest/host architecture, ATS, SDK generation, and local token-based auth to support TypeScript AppHosts.
---
import LearnMore from '@components/LearnMore.astro';
@@ -17,7 +18,7 @@ Instead, guest languages only declare resources such as `addRedis`, `addPostgres
When you run a TypeScript AppHost, the Aspire CLI orchestrates two processes:
-- **Guest**: your `apphost.ts` process, running in Node.js
+- **Guest**: your `apphost.mts` process, running in Node.js
- **Host**: the Aspire orchestration server, running on .NET
The guest communicates with the host via JSON-RPC over a local transport: Unix sockets on macOS and Linux, and named pipes on Windows. Your code calls methods such as `addRedis()` or `withReference()`, and the generated SDK translates those calls into RPC requests.
@@ -25,8 +26,8 @@ The guest communicates with the host via JSON-RPC over a local transport: Unix s
```mermaid
flowchart TB
subgraph Guest["Guest Process (Node.js)"]
- TS["apphost.ts"]
- SDK["Generated SDK (.modules/)"]
+ TS["apphost.mts"]
+ SDK["Generated SDK (.aspire/modules/)"]
Client["JSON-RPC Client"]
TS --> SDK --> Client
end
@@ -51,7 +52,7 @@ flowchart TB
## Startup sequence
1. The CLI prepares the host process with the required hosting packages.
-2. The ATS scanner inspects assemblies for exports and generates the TypeScript SDK into `.modules/`.
+2. The ATS scanner inspects assemblies for exports and generates the TypeScript SDK into `.aspire/modules/`.
3. The CLI starts the host process and creates the local socket or pipe endpoint.
4. The CLI starts the guest process and passes connection details through environment variables.
5. The guest connects and invokes capabilities such as `createBuilder`, `addRedis`, `build`, and `run`.
@@ -107,7 +108,7 @@ The TypeScript SDK is generated from hosting integration assemblies. When you ad
1. Loads the integration assembly.
2. Scans exported methods and types.
3. Applies ATS rules, including polymorphism flattening.
-4. Emits typed TypeScript wrappers into `.modules/`.
+4. Emits typed TypeScript wrappers into `.aspire/modules/`.
This keeps the SDK in sync with the .NET implementation. Integration authors do not hand-write TypeScript bindings; they export their .NET APIs and the CLI generates the guest surface automatically.
@@ -134,4 +135,4 @@ The resulting dashboard, service discovery behavior, health checks, and deployme
- [Build your first app](/get-started/first-app/?lang=typescript) — get started with a TypeScript AppHost
- [Resource model](/architecture/resource-model/) — understand how Aspire models resources and relationships
-- [Multi-language integrations](/extensibility/multi-language-integration-authoring/) — make your integration work with multi-language AppHosts
+- [Multi-language integrations](/extensibility/multi-language-integration-authoring/) — make your integration work with TypeScript AppHosts
diff --git a/src/frontend/src/content/docs/architecture/overview.mdx b/src/frontend/src/content/docs/architecture/overview.mdx
index 56f9dedc4..98ec17134 100644
--- a/src/frontend/src/content/docs/architecture/overview.mdx
+++ b/src/frontend/src/content/docs/architecture/overview.mdx
@@ -1,6 +1,6 @@
---
-title: Aspire architecture
-description: Learn about the overall architecture of Aspire, including its integrations, orchestration, and networking capabilities.
+title: Aspire architecture overview
+description: Learn the overall architecture of Aspire — the AppHost, resource model, orchestration, networking, dashboard, and how multi-language integrations fit together.
---
diff --git a/src/frontend/src/content/docs/architecture/resource-api-patterns.mdx b/src/frontend/src/content/docs/architecture/resource-api-patterns.mdx
index 5a71d18c7..9b48d32c5 100644
--- a/src/frontend/src/content/docs/architecture/resource-api-patterns.mdx
+++ b/src/frontend/src/content/docs/architecture/resource-api-patterns.mdx
@@ -1,6 +1,7 @@
---
title: Resource API Patterns
-description: Discover common API resource patterns in Aspire, including how to add and configure resources, use annotations, and implement custom value objects.
+seoTitle: Aspire resource API patterns for AppHost integrations
+description: Discover common API resource patterns in Aspire, including how to add and configure resources, use annotations, and compose hosting and client integrations.
---
import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
diff --git a/src/frontend/src/content/docs/architecture/resource-examples.mdx b/src/frontend/src/content/docs/architecture/resource-examples.mdx
index d2318d703..339d522a2 100644
--- a/src/frontend/src/content/docs/architecture/resource-examples.mdx
+++ b/src/frontend/src/content/docs/architecture/resource-examples.mdx
@@ -1,6 +1,7 @@
---
title: Examples
-description: Explore complete, runnable examples demonstrating key concepts in Aspire.
+seoTitle: Aspire resource model examples for AppHost authors
+description: "Explore complete, runnable examples that demonstrate Aspire's resource model — custom container resources, connection strings, health checks, and event subscriptions."
next: false
---
diff --git a/src/frontend/src/content/docs/architecture/resource-hierarchies.mdx b/src/frontend/src/content/docs/architecture/resource-hierarchies.mdx
index 9eab24541..2cb76b7b8 100644
--- a/src/frontend/src/content/docs/architecture/resource-hierarchies.mdx
+++ b/src/frontend/src/content/docs/architecture/resource-hierarchies.mdx
@@ -1,6 +1,7 @@
---
title: Resource Hierarchies
-description: Learn how Aspire manages resources and their dependencies in your application.
+seoTitle: Aspire resource parent-child hierarchies in AppHost
+description: Model parent-child resource relationships in Aspire to express ownership, lifecycle containment, and dashboard grouping for distributed application topologies.
---
import { Aside, Tabs, TabItem } from '@astrojs/starlight/components';
@@ -433,8 +434,8 @@ builder.AddProject("worker")
-```typescript title="TypeScript — apphost.ts"
-import { createBuilder, EndpointProperty } from './.modules/aspire.js';
+```typescript title="TypeScript — apphost.mts"
+import { createBuilder, EndpointProperty } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
diff --git a/src/frontend/src/content/docs/architecture/resource-model.mdx b/src/frontend/src/content/docs/architecture/resource-model.mdx
index 09b0a180b..32db862bc 100644
--- a/src/frontend/src/content/docs/architecture/resource-model.mdx
+++ b/src/frontend/src/content/docs/architecture/resource-model.mdx
@@ -1,6 +1,7 @@
---
title: Resource Model
-description: Learn how Aspire's resource model simplifies the development and management of distributed applications.
+seoTitle: Aspire resource model and dependency DAG explained
+description: "Learn how Aspire's resource model represents distributed apps as a directed acyclic graph of services, containers, executables, and integrations in the AppHost."
---
import { Aside, Steps } from '@astrojs/starlight/components';
diff --git a/src/frontend/src/content/docs/architecture/resource-publishing.mdx b/src/frontend/src/content/docs/architecture/resource-publishing.mdx
index f1663df27..682c9b71d 100644
--- a/src/frontend/src/content/docs/architecture/resource-publishing.mdx
+++ b/src/frontend/src/content/docs/architecture/resource-publishing.mdx
@@ -1,5 +1,7 @@
---
title: Resource Publishing
+seoTitle: Publish Aspire resource manifests from your AppHost
+description: Learn how Aspire publishes resource manifests as JSON for deployment tooling, including manifest annotations, value providers, and structured field expressions.
---
import { Steps } from '@astrojs/starlight/components';
diff --git a/src/frontend/src/content/docs/aspireconf/index.mdx b/src/frontend/src/content/docs/aspireconf/index.mdx
index 3b5c9d6de..d2aead041 100644
--- a/src/frontend/src/content/docs/aspireconf/index.mdx
+++ b/src/frontend/src/content/docs/aspireconf/index.mdx
@@ -1,5 +1,6 @@
---
title: That's a wrap!
+seoTitle: Aspire Conf 2026 — celebrating the Aspire 13.3 release
prev: false
next: false
giscus: false
@@ -7,7 +8,7 @@ crumbs: false
head:
- tag: title
content: Aspire Conf — Thank you for watching!
-description: Thank you for joining us and celebrating the release of Aspire 13.3. Watch the replay on YouTube and connect with us on Discord, X, and BlueSky.
+description: "Watch the Aspire Conf replays celebrating Aspire 13.3. Connect with the team and community across Discord, X, and BlueSky for sessions, demos, and Q&A."
template: splash
editUrl: false
hero:
diff --git a/src/frontend/src/content/docs/community/contributor-guide.mdx b/src/frontend/src/content/docs/community/contributor-guide.mdx
index 8ec3336b3..1f476bfc6 100644
--- a/src/frontend/src/content/docs/community/contributor-guide.mdx
+++ b/src/frontend/src/content/docs/community/contributor-guide.mdx
@@ -1,5 +1,6 @@
---
title: Contributor guide for aspire.dev
+description: "Learn how to contribute to aspire.dev: clone the repo, run the docs site locally, write Starlight MDX, follow the style guide, and open a pull request."
tableOfContents:
minHeadingLevel: 2
maxHeadingLevel: 3
@@ -751,8 +752,8 @@ builder.Build().Run();
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
diff --git a/src/frontend/src/content/docs/community/contributors.mdx b/src/frontend/src/content/docs/community/contributors.mdx
index 172c77f01..04c15e21c 100644
--- a/src/frontend/src/content/docs/community/contributors.mdx
+++ b/src/frontend/src/content/docs/community/contributors.mdx
@@ -1,6 +1,7 @@
---
-title: Contributors 🤝
-description: We're grateful for the contributions of our community members who help make Aspire better every day.
+title: Contributors 🤝
+seoTitle: Aspire contributors and the open-source community team
+description: Meet the contributors who help make Aspire better every day. Explore the global community of engineers, writers, and translators powering the Aspire ecosystem.
lastUpdated: false
editUrl: false
giscus: false
diff --git a/src/frontend/src/content/docs/community/index.mdx b/src/frontend/src/content/docs/community/index.mdx
index baa7a9536..ec45a3b67 100644
--- a/src/frontend/src/content/docs/community/index.mdx
+++ b/src/frontend/src/content/docs/community/index.mdx
@@ -1,10 +1,10 @@
---
-title: Aspire Community
+title: Aspire community channels and links
prev:
link: /reference/overview/
label: Reference
next: false
-description: Connect with the Aspire team and community across social channels, streams, and contribution platforms.
+description: Connect with the Aspire team and community across Discord, GitHub Discussions, livestreams, social channels, and contribution platforms for distributed apps.
banner:
content: |
Aspire 13.3 is here! — See what's new
diff --git a/src/frontend/src/content/docs/community/thanks.mdx b/src/frontend/src/content/docs/community/thanks.mdx
index d663c2560..331807b5e 100644
--- a/src/frontend/src/content/docs/community/thanks.mdx
+++ b/src/frontend/src/content/docs/community/thanks.mdx
@@ -1,6 +1,7 @@
---
title: Thank you
-description: Celebrating the open-source projects and communities that make Aspire possible.
+seoTitle: Open-source projects that power the Aspire ecosystem
+description: Celebrate the open-source projects, libraries, and communities that make Aspire possible, from OpenTelemetry to the broader cloud-native ecosystem.
editUrl: false
giscus: false
tableOfContents: false
@@ -803,7 +804,7 @@ Aspire speaks many languages — thanks to these communities.
alongside Aspire.
Orkestrér frontends, APIs, containere og databaser ubesværet—ingen omskrivninger, ingen grænser. Udvid Aspire til at drive ethvert projekt.
image:
diff --git a/src/frontend/src/content/docs/dashboard/ai-coding-agents.mdx b/src/frontend/src/content/docs/dashboard/ai-coding-agents.mdx
index 09e89ce91..57862efee 100644
--- a/src/frontend/src/content/docs/dashboard/ai-coding-agents.mdx
+++ b/src/frontend/src/content/docs/dashboard/ai-coding-agents.mdx
@@ -1,6 +1,7 @@
---
title: Dashboard and AI coding agents
-description: Learn how AI coding agents use the Aspire CLI and MCP server to fetch logs and telemetry from the Aspire dashboard when adding features and fixing bugs.
+seoTitle: Aspire dashboard and AI coding agents for distributed apps
+description: Learn how AI coding agents use the Aspire CLI and MCP server to read logs and telemetry from the Aspire dashboard, diagnose failures, and propose code changes.
---
import { Aside, Steps } from '@astrojs/starlight/components';
diff --git a/src/frontend/src/content/docs/dashboard/apis.mdx b/src/frontend/src/content/docs/dashboard/apis.mdx
index 3c951dc43..a6e1e205a 100644
--- a/src/frontend/src/content/docs/dashboard/apis.mdx
+++ b/src/frontend/src/content/docs/dashboard/apis.mdx
@@ -1,6 +1,7 @@
---
title: APIs and data access
-description: Reference for the Aspire dashboard write and read APIs, including OTLP endpoints, HTTP telemetry API, and Aspire CLI commands.
+seoTitle: Aspire dashboard APIs and OpenTelemetry data access
+description: Reference for the Aspire dashboard read and write APIs — OTLP endpoints, HTTP telemetry APIs, gRPC resource service, and authentication for programmatic access.
---
import { Aside } from '@astrojs/starlight/components';
diff --git a/src/frontend/src/content/docs/dashboard/configuration.mdx b/src/frontend/src/content/docs/dashboard/configuration.mdx
index 68d0f5d74..2ea5a498a 100644
--- a/src/frontend/src/content/docs/dashboard/configuration.mdx
+++ b/src/frontend/src/content/docs/dashboard/configuration.mdx
@@ -1,6 +1,6 @@
---
-title: Aspire dashboard configuration
-description: Aspire dashboard configuration options
+title: Aspire dashboard configuration reference
+description: Configuration reference for the Aspire dashboard — OTLP endpoints, frontend authentication, resource service, telemetry limits, and standalone deployment options.
---
import { Aside } from '@astrojs/starlight/components';
@@ -282,6 +282,7 @@ Telemetry limits have different scopes depending upon the telemetry type:
| `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. |
+| `Dashboard:UI:DisableAgentHelp` Default: `false` | Disables the **AI Agents** button in the dashboard header. When `false`, a button is shown in the header that opens a dialog with instructions for using AI coding agents with the dashboard. |
## Next steps
diff --git a/src/frontend/src/content/docs/dashboard/enable-browser-telemetry.mdx b/src/frontend/src/content/docs/dashboard/enable-browser-telemetry.mdx
index ad86055dd..f381161cb 100644
--- a/src/frontend/src/content/docs/dashboard/enable-browser-telemetry.mdx
+++ b/src/frontend/src/content/docs/dashboard/enable-browser-telemetry.mdx
@@ -1,6 +1,8 @@
---
title: Enable browser telemetry
-description: Learn how to enable browser telemetry in the Aspire dashboard.
+seoTitle: Enable browser telemetry in the Aspire dashboard today
+description: Enable browser telemetry in the Aspire dashboard to capture client-side OpenTelemetry logs, traces, and metrics from front-end JavaScript apps.
+
---
import LearnMore from '@components/LearnMore.astro';
diff --git a/src/frontend/src/content/docs/dashboard/explore.mdx b/src/frontend/src/content/docs/dashboard/explore.mdx
index 9701ba241..e008f263a 100644
--- a/src/frontend/src/content/docs/dashboard/explore.mdx
+++ b/src/frontend/src/content/docs/dashboard/explore.mdx
@@ -1,6 +1,6 @@
---
-title: Explore Aspire dashboard
-description: Explore the Aspire dashboard features through the Aspire Starter app.
+title: Explore the Aspire dashboard with the Starter app
+description: Explore Aspire dashboard features — resource graph, console logs, traces, metrics, and the GenAI visualizer — using the Aspire Starter app as a guided tour.
---
import { Image } from 'astro:assets';
@@ -568,7 +568,7 @@ The dashboard includes a notification center accessible via the bell icon in the
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.
+- A resource command starts, succeeds, fails, or is canceled. The notification center retains entries for all completed command states — success (information), failure (error), and canceled (warning).
- 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.
diff --git a/src/frontend/src/content/docs/dashboard/index.mdx b/src/frontend/src/content/docs/dashboard/index.mdx
index 2413337cb..f40e68950 100644
--- a/src/frontend/src/content/docs/dashboard/index.mdx
+++ b/src/frontend/src/content/docs/dashboard/index.mdx
@@ -1,12 +1,12 @@
---
-title: Aspire Dashboard
+title: Aspire dashboard for distributed apps
prev:
link: /integrations/
label: Integrations
next:
link: /deployment/
label: Deployment
-description: Monitor, debug, and manage your distributed applications with the Aspire Dashboard — real-time telemetry, structured logs, traces, and AI-powered insights.
+description: Monitor, debug, and manage your distributed applications with the Aspire dashboard — real-time telemetry, resource graphs, console logs, and OpenTelemetry support.
editUrl: false
tableOfContents: false
pageActions: false
diff --git a/src/frontend/src/content/docs/dashboard/microsoft-collected-dashboard-telemetry.mdx b/src/frontend/src/content/docs/dashboard/microsoft-collected-dashboard-telemetry.mdx
index fdff8f2c6..57bbcf6c5 100644
--- a/src/frontend/src/content/docs/dashboard/microsoft-collected-dashboard-telemetry.mdx
+++ b/src/frontend/src/content/docs/dashboard/microsoft-collected-dashboard-telemetry.mdx
@@ -1,7 +1,8 @@
---
title: Microsoft-collected dashboard telemetry
+seoTitle: Microsoft-collected Aspire dashboard telemetry overview
next: false
-description: Learn about what telemetry the Aspire dashboard sends and how to opt out.
+description: Learn what telemetry the Aspire dashboard sends to Microsoft, how the data is used to improve the product, and how to opt out in standalone and AppHost deployments.
---
import OsAwareTabs from '@components/OsAwareTabs.astro';
diff --git a/src/frontend/src/content/docs/dashboard/overview.mdx b/src/frontend/src/content/docs/dashboard/overview.mdx
index c118fff8f..8777c3707 100644
--- a/src/frontend/src/content/docs/dashboard/overview.mdx
+++ b/src/frontend/src/content/docs/dashboard/overview.mdx
@@ -1,6 +1,6 @@
---
-title: Aspire dashboard overview
-description: Overview of Aspire dashboard and getting started.
+title: Aspire dashboard overview and getting started
+description: Overview of the Aspire dashboard — what it shows, how the AppHost wires it up, and how to start using it for telemetry, resource management, and debugging.
---
import { Image } from 'astro:assets';
diff --git a/src/frontend/src/content/docs/dashboard/security-considerations.mdx b/src/frontend/src/content/docs/dashboard/security-considerations.mdx
index 626b798f2..97d2bbee0 100644
--- a/src/frontend/src/content/docs/dashboard/security-considerations.mdx
+++ b/src/frontend/src/content/docs/dashboard/security-considerations.mdx
@@ -1,6 +1,6 @@
---
title: Aspire dashboard security considerations
-description: Security considerations for running the Aspire dashboard
+description: Security considerations for running the Aspire dashboard — authentication, network exposure, OTLP and resource service endpoints, and standalone hardening guidance.
---
import { Tabs, TabItem } from '@astrojs/starlight/components';
diff --git a/src/frontend/src/content/docs/dashboard/standalone-for-nodejs.mdx b/src/frontend/src/content/docs/dashboard/standalone-for-nodejs.mdx
index 736a08671..ae93b0c02 100644
--- a/src/frontend/src/content/docs/dashboard/standalone-for-nodejs.mdx
+++ b/src/frontend/src/content/docs/dashboard/standalone-for-nodejs.mdx
@@ -1,6 +1,6 @@
---
-title: Use the Aspire dashboard with Node.js apps
-description: How to use the Aspire Dashboard in a Node.js application.
+title: Aspire dashboard standalone for Node.js apps
+description: Use the Aspire dashboard standalone with Node.js applications — wire up OpenTelemetry, point OTLP exporters at the dashboard, and visualize logs, traces, and metrics.
---
diff --git a/src/frontend/src/content/docs/dashboard/standalone-for-python.mdx b/src/frontend/src/content/docs/dashboard/standalone-for-python.mdx
index c05c794e9..6e03702de 100644
--- a/src/frontend/src/content/docs/dashboard/standalone-for-python.mdx
+++ b/src/frontend/src/content/docs/dashboard/standalone-for-python.mdx
@@ -1,6 +1,6 @@
---
-title: Use the Aspire dashboard with Python apps
-description: How to use the Aspire Dashboard in a Python application.
+title: Aspire dashboard standalone for Python apps
+description: Use the Aspire dashboard standalone with Python applications — configure OpenTelemetry exporters, send OTLP data to the dashboard, and inspect telemetry in real time.
---
import { Image } from 'astro:assets';
diff --git a/src/frontend/src/content/docs/dashboard/standalone.mdx b/src/frontend/src/content/docs/dashboard/standalone.mdx
index 9861d38d4..e8832d74e 100644
--- a/src/frontend/src/content/docs/dashboard/standalone.mdx
+++ b/src/frontend/src/content/docs/dashboard/standalone.mdx
@@ -1,6 +1,6 @@
---
-title: Standalone Aspire dashboard
-description: How to use the Aspire dashboard standalone.
+title: Run the Aspire dashboard standalone
+description: Run the Aspire dashboard as a standalone container or executable — collect OTLP telemetry from any language, then view logs, traces, and metrics without the AppHost.
---
import { Image } from 'astro:assets';
diff --git a/src/frontend/src/content/docs/de/index.mdx b/src/frontend/src/content/docs/de/index.mdx
index 7b0871904..da9dd1177 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.3 wurde veröffentlicht! — Was ist neu in Aspire 13.3.
+ 🚀 Aspire 13.4 wurde veröffentlicht! — Was ist neu in Aspire 13.4.
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/app-lifecycle.mdx b/src/frontend/src/content/docs/deployment/app-lifecycle.mdx
index e6af5307d..ef60cea8d 100644
--- a/src/frontend/src/content/docs/deployment/app-lifecycle.mdx
+++ b/src/frontend/src/content/docs/deployment/app-lifecycle.mdx
@@ -1,9 +1,16 @@
---
title: Example app lifecycle workflow
-description: Follow a worked example that uses GitHub Actions to publish Aspire artifacts and Docker Compose to deploy them later.
+seoTitle: Aspire deployment lifecycle workflow from dev to prod
+description: Follow a worked example that uses GitHub Actions to publish Aspire artifacts and Docker Compose to deploy — a complete deployment lifecycle from commit to production.
---
-import { Aside, FileTree, Steps } from '@astrojs/starlight/components';
+import {
+ Aside,
+ FileTree,
+ Steps,
+ Tabs,
+ TabItem,
+} from '@astrojs/starlight/components';
import LearnMore from '@components/LearnMore.astro';
This guide is a worked example of one CI/CD workflow for Aspire. It uses GitHub Actions to build and publish release artifacts, GitHub Container Registry to store container images, and Docker Compose to run the published output later.
@@ -38,6 +45,9 @@ The following example uses a C# AppHost, but the same workflow shape applies to
Consider [this example](https://github.com/BethMassi/VolumeMount/). You have a distributed application that consists of a Blazor web project that relies on a SQL Server database with a persistent data volume as well as a persistent writable file volume to capture user file uploads.
You want to distribute your Blazor app as a Docker container image via the GitHub Container Registry. You need the [Aspire.Hosting.Docker](/integrations/compute/docker/) and [Aspire.Hosting.SqlServer](/integrations/databases/sql-server/sql-server-get-started/) integrations.
+
+
+
```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -103,6 +113,74 @@ var blazorweb = builder.AddProject("blazorweb")
builder.Build().Run();
```
+
+
+
+```typescript title="apphost.mts" twoslash
+import { ContainerLifetime, createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+const compose = await builder.addDockerComposeEnvironment('volumemount-env');
+await compose
+ .withProperties(async (env) => {
+ env.setDashboardEnabled(true);
+ })
+ .configureComposeFile(async (composeFile) => {
+ await composeFile.addVolume('volumemount-blazor-uploads', {
+ driver: 'local',
+ });
+ });
+
+const endpoint = await builder.addParameter('registry-endpoint');
+const repository = await builder.addParameter('registry-repository');
+await builder.addContainerRegistry('container-registry', endpoint, {
+ repository,
+});
+
+const sqlPassword = await builder.addParameter('sqlserver-password', {
+ secret: true,
+});
+const sqlServer = await builder
+ .addSqlServer('sqlserver', { password: sqlPassword })
+ .withDataVolume('volumemount-sqlserver-data')
+ .withLifetime(ContainerLifetime.Persistent);
+
+const sqlDatabase = await sqlServer.addDatabase('sqldb');
+
+const blazorWeb = await builder.addProject(
+ 'blazorweb',
+ '../VolumeMount.BlazorWeb/VolumeMount.BlazorWeb.csproj'
+);
+await blazorWeb
+ .withExternalHttpEndpoints()
+ .withReference(sqlDatabase)
+ .waitFor(sqlDatabase)
+ .publishAsDockerComposeService(async (_resource, service) => {
+ await service.addVolume(
+ 'volumemount-blazor-uploads',
+ '/app/wwwroot/uploads'
+ );
+ await service.user.set('root');
+ await service.command.set([
+ '/bin/sh',
+ '-c',
+ "chown -R app:app /app/wwwroot/uploads && chmod -R 755 /app/wwwroot/uploads && exec su app -c 'dotnet /app/VolumeMount.BlazorWeb.dll'",
+ ]);
+ });
+
+await builder.build().run();
+```
+
+
+
+
+
+
## Phase 1: Develop locally
### Run the app locally
@@ -298,9 +376,8 @@ The `aspire publish` command does the following:
- Outputs artifacts to `./aspire-output/` directory
- - aspire-output/
- - docker-compose.yaml Service definitions for all containers
- - .env Template for required environment variables
+ - aspire-output/ - docker-compose.yaml Service definitions for all containers
+ - .env Template for required environment variables
@@ -363,7 +440,7 @@ After the workflow completes, you have everything needed for production deployme
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 43b63176d..258a1f3df 100644
--- a/src/frontend/src/content/docs/deployment/azure/app-service.mdx
+++ b/src/frontend/src/content/docs/deployment/azure/app-service.mdx
@@ -1,6 +1,7 @@
---
title: Deploy to Azure App Service
-description: Learn how to deploy Aspire applications to Azure App Service with the aspire deploy command.
+seoTitle: Deploy Aspire AppHost projects to Azure App Service
+description: "Deploy Aspire applications to Azure App Service with the `aspire deploy` command — plans, containers, configuration, slot deployments, and CI/CD integration."
---
import { Badge, Tabs, TabItem } from '@astrojs/starlight/components';
@@ -56,8 +57,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -122,8 +123,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -196,8 +197,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -245,8 +246,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -280,7 +281,7 @@ builder.AddProject("api")
```
-```typescript title="apphost.ts"
+```typescript title="apphost.mts"
await builder
.addProject("api", "../Api/Api.csproj", "https")
.publishAsAzureAppServiceWebsite()
@@ -328,8 +329,8 @@ builder.AddAzureAppServiceEnvironment("app-service-env")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -355,8 +356,8 @@ builder.AddAzureAppServiceEnvironment("app-service-env")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -388,8 +389,8 @@ builder.AddAzureAppServiceEnvironment("app-service-env")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
diff --git a/src/frontend/src/content/docs/deployment/azure/azure-developer-cli.mdx b/src/frontend/src/content/docs/deployment/azure/azure-developer-cli.mdx
index f613087da..9a271f313 100644
--- a/src/frontend/src/content/docs/deployment/azure/azure-developer-cli.mdx
+++ b/src/frontend/src/content/docs/deployment/azure/azure-developer-cli.mdx
@@ -1,6 +1,7 @@
---
title: Use existing azd workflows
-description: Learn the Aspire-specific compatibility details that still matter when you keep an existing Azure Developer CLI workflow.
+seoTitle: Use Aspire with existing Azure Developer CLI workflows
+description: Learn the Aspire-specific compatibility details that still matter when you keep an existing Azure Developer CLI (azd) workflow — manifest handoff, hooks, and parameters.
next: false
tableOfContents: false
topic: deployment
@@ -48,8 +49,8 @@ builder.AddAzureContainerAppEnvironment("env")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
diff --git a/src/frontend/src/content/docs/deployment/azure/azure-security-best-practices.mdx b/src/frontend/src/content/docs/deployment/azure/azure-security-best-practices.mdx
index 31db78f90..dcf3f31b4 100644
--- a/src/frontend/src/content/docs/deployment/azure/azure-security-best-practices.mdx
+++ b/src/frontend/src/content/docs/deployment/azure/azure-security-best-practices.mdx
@@ -1,7 +1,7 @@
---
-title: Azure security best practices for Aspire deployments
+title: "Aspire on Azure: security best practices"
next: false
-description: Learn how to harden Aspire deployments to Azure with private networking, managed identities, secret management, and monitoring.
+description: Harden Aspire deployments to Azure with private networking, managed identities, secret stores, Key Vault references, and policy guardrails for production workloads.
---
import { Tabs, TabItem } from '@astrojs/starlight/components';
@@ -62,8 +62,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -123,8 +123,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -157,8 +157,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -191,8 +191,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
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 6735fccda..377d8234d 100644
--- a/src/frontend/src/content/docs/deployment/azure/container-apps.mdx
+++ b/src/frontend/src/content/docs/deployment/azure/container-apps.mdx
@@ -1,6 +1,7 @@
---
title: Deploy to Azure Container Apps
-description: Learn how to deploy Aspire applications to Azure Container Apps with the aspire deploy command.
+seoTitle: Deploy Aspire apps to Azure Container Apps with azd
+description: "Deploy Aspire applications to Azure Container Apps with the `aspire deploy` command — environments, revisions, scaling, ingress, and secret-backed configuration."
---
import { Tabs, TabItem } from '@astrojs/starlight/components';
@@ -56,8 +57,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -133,8 +134,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -212,8 +213,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -259,8 +260,8 @@ The `ConfigureCustomDomain` API is experimental and may change or be removed in
:::
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -295,7 +296,7 @@ builder.AddProject("api")
```
-```typescript title="apphost.ts"
+```typescript title="apphost.mts"
const api = await builder.addProject("api", "../Api/Api.csproj", "http");
await api.withExternalHttpEndpoints();
```
@@ -320,8 +321,8 @@ builder.AddAzureContainerAppEnvironment("aspire-env")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -350,8 +351,8 @@ builder.AddAzureContainerAppEnvironment("aspire-env")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
diff --git a/src/frontend/src/content/docs/deployment/azure/index.mdx b/src/frontend/src/content/docs/deployment/azure/index.mdx
index 889132f40..0182a367b 100644
--- a/src/frontend/src/content/docs/deployment/azure/index.mdx
+++ b/src/frontend/src/content/docs/deployment/azure/index.mdx
@@ -1,6 +1,7 @@
---
title: Deploy to Azure
-description: Learn how Azure deployment works in Aspire, the shared prerequisites, and how to choose between Azure Container Apps and Azure App Service.
+seoTitle: Deploy Aspire apps to Azure with the Azure Developer CLI
+description: Learn how Azure deployment works in Aspire, the shared prerequisites, and how to choose between Azure App Service, Container Apps, and AKS for production workloads.
---
import { CardGrid, LinkCard, Steps } from '@astrojs/starlight/components';
diff --git a/src/frontend/src/content/docs/deployment/azure/manifest-format.mdx b/src/frontend/src/content/docs/deployment/azure/manifest-format.mdx
index 3372f6c43..766602d45 100644
--- a/src/frontend/src/content/docs/deployment/azure/manifest-format.mdx
+++ b/src/frontend/src/content/docs/deployment/azure/manifest-format.mdx
@@ -1,6 +1,7 @@
---
title: Legacy deployment manifest format
-description: Learn when the legacy Aspire deployment manifest still matters and why most deployments should treat it as an implementation detail.
+seoTitle: Aspire legacy deployment manifest format reference
+description: Learn when the legacy Aspire deployment manifest still matters and why most deployments should treat it as an intermediate artifact for tooling and migration support.
next: false
tableOfContents: false
topic: deployment
diff --git a/src/frontend/src/content/docs/deployment/ci-cd.mdx b/src/frontend/src/content/docs/deployment/ci-cd.mdx
index eb348d1a5..aeb661ca8 100644
--- a/src/frontend/src/content/docs/deployment/ci-cd.mdx
+++ b/src/frontend/src/content/docs/deployment/ci-cd.mdx
@@ -1,6 +1,7 @@
---
title: CI/CD overview
-description: Use the Aspire CLI as the source of truth in CI/CD and design workflows around environments, parameters, and target-specific deployment guidance.
+seoTitle: Aspire CI/CD overview, deployment, and release patterns
+description: Use the Aspire CLI as the source of truth in CI/CD and design workflows around environments, parameters, secrets, container registries, and reproducible deployments.
---
import { CardGrid, LinkCard, Steps } from '@astrojs/starlight/components';
diff --git a/src/frontend/src/content/docs/deployment/custom-deployments.mdx b/src/frontend/src/content/docs/deployment/custom-deployments.mdx
index f9d8ccad8..257478541 100644
--- a/src/frontend/src/content/docs/deployment/custom-deployments.mdx
+++ b/src/frontend/src/content/docs/deployment/custom-deployments.mdx
@@ -1,6 +1,7 @@
---
title: Building custom deployment pipelines
-description: Learn how to build container images from your Aspire resources and create custom deployment pipelines.
+seoTitle: Build custom Aspire deployment pipelines for your apps
+description: Build container images from your Aspire resources and create custom deployment pipelines — extend publishing, target new platforms, and integrate with existing CD tools.
---
import { Aside, Tabs, TabItem } from '@astrojs/starlight/components';
diff --git a/src/frontend/src/content/docs/deployment/deploy-with-aspire.mdx b/src/frontend/src/content/docs/deployment/deploy-with-aspire.mdx
index 44f800d93..dcaaaa643 100644
--- a/src/frontend/src/content/docs/deployment/deploy-with-aspire.mdx
+++ b/src/frontend/src/content/docs/deployment/deploy-with-aspire.mdx
@@ -1,6 +1,6 @@
---
-title: Deploy with Aspire
-description: 'Learn how Aspire deployment works: targets add pipeline steps, commands enter the pipeline, and compute environments, parameters, and environments shape each run.'
+title: How Aspire deployment works
+description: Learn how Aspire deployment works — targets add pipeline steps, commands enter the pipeline, and components compose into a publishable artifact for any environment.
---
import {
@@ -33,8 +33,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -111,8 +111,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
diff --git a/src/frontend/src/content/docs/deployment/deployment-state-caching.mdx b/src/frontend/src/content/docs/deployment/deployment-state-caching.mdx
index 596535b16..d3dd3f182 100644
--- a/src/frontend/src/content/docs/deployment/deployment-state-caching.mdx
+++ b/src/frontend/src/content/docs/deployment/deployment-state-caching.mdx
@@ -1,6 +1,7 @@
---
title: Deployment state caching
-description: Learn how the aspire deploy command manages deployment state through cached configuration files.
+seoTitle: Aspire deployment state caching across publish runs
+description: "Learn how the `aspire deploy` command manages deployment state through cached configuration files — keys, invalidation, and per-environment override behavior."
---
import { Aside, Steps } from '@astrojs/starlight/components';
diff --git a/src/frontend/src/content/docs/deployment/docker-compose.mdx b/src/frontend/src/content/docs/deployment/docker-compose.mdx
index 5dda9d57e..053f81ead 100644
--- a/src/frontend/src/content/docs/deployment/docker-compose.mdx
+++ b/src/frontend/src/content/docs/deployment/docker-compose.mdx
@@ -1,6 +1,7 @@
---
title: Deploy to Docker Compose
-description: Learn how to publish and deploy your Aspire application using Docker Compose with Docker or Podman.
+seoTitle: Deploy Aspire apps with Docker Compose to any host
+description: Publish and deploy your Aspire application using Docker Compose with Docker or Podman — generate compose files, build images, and orchestrate multi-service production runs.
---
import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
@@ -72,8 +73,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts" {5} twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" {5} twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -255,9 +256,9 @@ builder.Build().Run();
```
-```typescript title="apphost.ts"
-import { createBuilder } from './.modules/aspire.js';
-import type { DockerfileBuilderCallbackContext } from './.modules/aspire.js';
+```typescript title="apphost.mts"
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+import type { DockerfileBuilderCallbackContext } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -321,8 +322,8 @@ builder.AddDockerComposeEnvironment("env")
```
-```typescript title="apphost.ts"
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts"
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -367,8 +368,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts"
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts"
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -414,8 +415,8 @@ builder.Build().Run();
```
-```typescript title="apphost.ts"
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts"
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -457,8 +458,8 @@ var apiHost = compose.Resource.GetHostAddressExpression(
```
-```typescript title="apphost.ts"
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts"
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -487,8 +488,8 @@ builder.AddContainer("mycontainer", "myimage:latest")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder, ImagePullPolicy } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder, ImagePullPolicy } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -538,8 +539,8 @@ var api = builder.AddProject("api")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -617,8 +618,8 @@ var api = builder.AddProject("api")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -671,8 +672,8 @@ var api = builder.AddProject("api")
```
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
diff --git a/src/frontend/src/content/docs/deployment/environments.mdx b/src/frontend/src/content/docs/deployment/environments.mdx
index 669b62f01..49b6366cb 100644
--- a/src/frontend/src/content/docs/deployment/environments.mdx
+++ b/src/frontend/src/content/docs/deployment/environments.mdx
@@ -1,6 +1,7 @@
---
title: Environments
-description: Learn how to use Aspire environments to configure your application for development, staging, and production deployments.
+seoTitle: Aspire deployment environments and target configuration
+description: Use Aspire environments to configure your application for development, staging, and production — parameters, secrets, container tags, and per-environment overrides.
---
import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
@@ -87,8 +88,8 @@ if (builder.Environment.IsEnvironment("Testing"))
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const env = await builder.environment.get();
@@ -150,8 +151,8 @@ builder.Build().Run();
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const env = await builder.environment.get();
@@ -223,8 +224,8 @@ builder.Build().Run();
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -321,8 +322,8 @@ builder.Build().Run();
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const isRunMode = await builder.executionContext.isRunMode();
diff --git a/src/frontend/src/content/docs/deployment/index.mdx b/src/frontend/src/content/docs/deployment/index.mdx
index b1ec0c595..4d2fc28ea 100644
--- a/src/frontend/src/content/docs/deployment/index.mdx
+++ b/src/frontend/src/content/docs/deployment/index.mdx
@@ -1,12 +1,12 @@
---
-title: Aspire Deployment
+title: Aspire deployment overview
prev:
link: /dashboard/
label: Dashboard
next:
link: /reference/overview/
label: Reference
-description: Ship your Aspire applications to Azure, Docker, Kubernetes, and beyond — with confidence and consistency.
+description: Ship your Aspire applications to Azure, Docker, Kubernetes, and beyond — choose a target, publish artifacts, and deploy with the Aspire CLI from a single AppHost.
editUrl: false
tableOfContents: false
pageActions: false
diff --git a/src/frontend/src/content/docs/deployment/javascript-apps.mdx b/src/frontend/src/content/docs/deployment/javascript-apps.mdx
index 75685c5bf..a016d9bea 100644
--- a/src/frontend/src/content/docs/deployment/javascript-apps.mdx
+++ b/src/frontend/src/content/docs/deployment/javascript-apps.mdx
@@ -1,6 +1,7 @@
---
title: Deploy JavaScript apps
-description: Learn the main production deployment models for JavaScript app resources in Aspire.
+seoTitle: Deploy Aspire JavaScript apps with the Aspire AppHost
+description: Learn the main production deployment models for JavaScript app resources in Aspire — Next.js, Vite, SSR frameworks, container images, and CDN-fronted hosting targets.
---
import { Aside, TabItem, Tabs } from '@astrojs/starlight/components';
@@ -16,7 +17,7 @@ Use this table as the starting point:
| 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` |
+| SSR or Node.js app started by a package script | JavaScript app resource | `PublishAsPackageScript` |
| Next.js standalone app | Next.js app resource | `AddNextJsApp` |
| Static frontend hosted separately from the backend | Static host + separate API | Custom topology |
@@ -82,8 +83,8 @@ builder.Build().Run();
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -165,8 +166,8 @@ builder.Build().Run();
-```typescript title="apphost.ts" twoslash
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
@@ -222,7 +223,7 @@ if (builder.ExecutionContext.IsRunMode)
-```typescript title="apphost.ts"
+```typescript title="apphost.mts"
const frontend = await builder.addViteApp('frontend', './frontend');
const gateway = await builder
@@ -301,8 +302,8 @@ builder.Build().Run();
-```typescript title="apphost.ts"
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts"
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
await builder.addDockerComposeEnvironment('compose');
@@ -358,13 +359,13 @@ Use this shape when the JavaScript framework output should become the deployed w
There are two common runtime shapes:
- Use `PublishAsNodeServer` when the build produces a self-contained server artifact that can run directly with `node`.
-- Use `PublishAsNpmScript` when the production server starts through a package-manager script and needs production dependencies from `node_modules`.
+- Use `PublishAsPackageScript` when the production server starts through a package-manager script and needs production dependencies from `node_modules`.
### Built Node.js server artifact
Use `PublishAsNodeServer` for frameworks that produce a self-contained Node.js server artifact during build, such as SvelteKit and TanStack Start. Aspire generates a runtime container that runs the built artifact directly with `node`.
-Choose this method instead of `PublishAsNpmScript` when the build output does not need a production `node_modules` install at runtime. The resulting image can be smaller because it copies the server artifact rather than the full application with production dependencies.
+Choose this method instead of `PublishAsPackageScript` when the build output does not need a production `node_modules` install at runtime. The resulting image can be smaller because it copies the server artifact rather than the full application with production dependencies.
@@ -394,8 +395,8 @@ builder.Build().Run();
-```typescript title="apphost.ts"
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts"
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
await builder.addDockerComposeEnvironment('compose');
@@ -424,9 +425,9 @@ The generated container sets `HOST=0.0.0.0` and `HOSTNAME=0.0.0.0` so the Node.j
### Package-script server
-Use `PublishAsNpmScript` for SSR frameworks that start production by running an npm-compatible script, such as Nuxt, Astro SSR, and Remix. Aspire generates a multi-stage Dockerfile that installs production dependencies and uses the package manager script as the container entrypoint.
+Use `PublishAsPackageScript` for SSR frameworks that start production by running a package-manager script, such as Nuxt, Astro SSR, and Remix. Aspire generates a multi-stage Dockerfile that installs production dependencies and uses the package manager script as the container entrypoint.
-Choose this method instead of `PublishAsNodeServer` when the production server imports packages from `node_modules` at runtime or the framework's recommended production command is an npm script.
+Choose this method instead of `PublishAsNodeServer` when the production server imports packages from `node_modules` at runtime or the framework's recommended production command is a package script.
@@ -446,7 +447,7 @@ var apiEndpoint = api.GetEndpoint("http");
var nuxtApp = builder
.AddViteApp("nuxt", "./frameworks/nuxt", runScriptName: "dev")
- .PublishAsNpmScript(startScriptName: "start")
+ .PublishAsPackageScript(scriptName: "start")
.WithEnvironment("API_URL", apiEndpoint)
.WithEnvironment("NUXT_API_URL", apiEndpoint)
.WithExternalHttpEndpoints();
@@ -457,8 +458,8 @@ builder.Build().Run();
-```typescript title="apphost.ts"
-import { createBuilder } from './.modules/aspire.js';
+```typescript title="apphost.mts"
+import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
await builder.addDockerComposeEnvironment('compose');
@@ -472,7 +473,7 @@ const apiEndpoint = await api.getEndpoint('http');
await builder
.addViteApp('nuxt', './frameworks/nuxt', { runScriptName: 'dev' })
- .publishAsNpmScript({ startScriptName: 'start' })
+ .publishAsPackageScript({ scriptName: 'start' })
.withEnvironment('API_URL', apiEndpoint)
.withEnvironment('NUXT_API_URL', apiEndpoint)
.withExternalHttpEndpoints();
@@ -486,11 +487,40 @@ await builder.build().run();
The generated container sets `HOST=0.0.0.0` and `HOSTNAME=0.0.0.0` so the server binds to all interfaces inside the container network.
+### pnpm and Bun with PublishAsPackageScript
+
+`PublishAsPackageScript` works with pnpm and Bun in addition to npm and Yarn. The generated runtime Dockerfile stage is tailored to the package manager:
+
+- **pnpm**: The runtime stage runs `corepack enable pnpm && pnpm --version` before the entrypoint, so pnpm is available when the start script executes. Without this step, the container fails at startup with exit code 127 because pnpm is not included in the base `node:alpine` image.
+- **Bun**: The runtime stage reuses the Bun build image rather than switching to a Node.js image, because `bun run