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 796d61f47..dc034faa6 100644
--- a/src/frontend/src/content/docs/app-host/certificate-configuration.mdx
+++ b/src/frontend/src/content/docs/app-host/certificate-configuration.mdx
@@ -41,10 +41,10 @@ Many of the certificate features in Aspire rely on a development certificate. Be
The preferred way to manage the development certificate is to use the [Aspire CLI](/get-started/install-cli/). When you run `aspire run` in an interactive session, the CLI automatically ensures the development certificate is created and trusted. No additional manual steps are required.
-For non-C# AppHosts (such as [TypeScript](/app-host/typescript-apphost/) or Python AppHosts), the `dotnet` first-run experience that normally creates the HTTPS development certificate never runs, because these AppHosts launch a prebuilt native binary instead of invoking `dotnet`. The Aspire CLI fills this gap when `aspire run` starts and no development certificate exists:
+For generated TypeScript, Python, Go, Java, and Rust AppHosts, the `dotnet` first-run experience that normally creates the HTTPS development certificate never runs, because these AppHosts launch a prebuilt native binary instead of invoking `dotnet`. The Aspire CLI fills this gap when `aspire run` starts and no development certificate exists:
-- In an interactive session—and on Linux, where establishing trust doesn't require a prompt—the CLI creates *and* trusts the certificate, just as it does for C# AppHosts.
-- In a non-interactive session on macOS or Windows (for example, in CI), the CLI can't show the macOS Keychain password prompt or the Windows trust dialog, so it *generates* the certificate without trusting it. This lets servers such as Kestrel load the certificate from the personal store, even though it isn't trusted. If the certificate can't be generated, a warning is displayed and the run continues.
+- In an interactive session—and on Linux, where establishing trust doesn't require a prompt—the CLI creates _and_ trusts the certificate, just as it does for C# AppHosts.
+- In a non-interactive session on macOS or Windows (for example, in CI), the CLI can't show the macOS Keychain password prompt or the Windows trust dialog, so it _generates_ the certificate without trusting it. This lets servers such as Kestrel load the certificate from the personal store, even though it isn't trusted. If the certificate can't be generated, a warning is displayed and the run continues.
To opt out of automatic certificate generation, set the `ASPIRE_CLI_GENERATE_HTTPS_CERTIFICATE` environment variable to `false`. This mirrors the .NET SDK's `DOTNET_GENERATE_ASPNET_CERTIFICATE` opt-out:
@@ -85,19 +85,20 @@ aspire certs trust
### Developer certificate for DCP communication
@@ -141,8 +142,16 @@ You can control this behavior using the HTTPS endpoint APIs described below.
To explicitly configure a resource to use the development certificate for its HTTPS endpoints:
-
+
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -157,24 +166,31 @@ var pythonApp = builder.AddUvicornApp("api", "../api", "app:main")
builder.Build().Run();
```
+
+
```typescript title="apphost.mts" twoslash
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
// Explicitly use the developer certificate
-const nodeApp = await builder.addViteApp("frontend", "../frontend")
- .withHttpsDeveloperCertificate();
+const nodeApp = await builder
+ .addViteApp('frontend', '../frontend')
+ .withHttpsDeveloperCertificate();
// Use developer certificate with an encrypted private key
-const certPassword = await builder.addParameter("cert-password", { secret: true });
-const pythonApp = await builder.addUvicornApp("api", "../api", "app:main")
- .withHttpsDeveloperCertificate({ password: certPassword });
+const certPassword = await builder.addParameter('cert-password', {
+ secret: true,
+});
+const pythonApp = await builder
+ .addUvicornApp('api', '../api', 'app:main')
+ .withHttpsDeveloperCertificate({ password: certPassword });
await builder.build().run();
```
+
@@ -189,8 +205,16 @@ The `WithHttpsDeveloperCertificate` method:
To configure a resource to use a specific X.509 certificate for HTTPS endpoints:
-
+
+
```csharp title="AppHost.cs"
using System.Security.Cryptography.X509Certificates;
@@ -210,19 +234,22 @@ builder.AddNpmApp("frontend", "../frontend")
builder.Build().Run();
```
+
+
```typescript title="apphost.mts" twoslash
import { createBuilder, refExpr } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
-const api = await builder.addContainer("api", {
- image: "my-api",
- tag: "latest",
+const api = await builder.addContainer('api', {
+ image: 'my-api',
+ tag: 'latest',
});
-api.createExecutionConfiguration()
+api
+ .createExecutionConfiguration()
.withArgumentsConfig()
.withEnvironmentVariablesConfig()
.withHttpsCertificateConfig(async () => ({
@@ -233,6 +260,7 @@ api.createExecutionConfiguration()
await builder.build().run();
```
+
@@ -246,8 +274,16 @@ The certificate must:
To prevent Aspire from configuring any HTTPS certificate for a resource:
-
+
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -257,19 +293,21 @@ var redis = builder.AddRedis("cache")
builder.Build().Run();
```
+
+
```typescript title="apphost.mts" twoslash
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
// Disable automatic HTTPS certificate configuration
-const redis = await builder.addRedis("cache")
- .withoutHttpsCertificate();
+const redis = await builder.addRedis('cache').withoutHttpsCertificate();
await builder.build().run();
```
+
@@ -283,8 +321,14 @@ Use `WithoutHttpsCertificate` when:
For resources that need custom certificate configuration logic, use `WithHttpsCertificateConfiguration` to specify how certificate files should be passed to the resource:
-
+) -> Value, so its typed certificate context cannot be used safely.',
+ }}
+>
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -315,30 +359,76 @@ builder.AddContainer("api", "my-api:latest")
builder.Build().Run();
```
+
+
```typescript title="apphost.mts" twoslash
import { createBuilder, refExpr } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
-const api = await builder.addContainer("api", {
- image: "myimage",
- tag: "latest",
+const api = await builder.addContainer('api', {
+ image: 'myimage',
+ tag: 'latest',
});
-api.createExecutionConfiguration()
+api
+ .createExecutionConfiguration()
.withArgumentsConfig()
.withEnvironmentVariablesConfig()
.withCertificateTrustConfig(async () => ({
certificateBundlePath: refExpr`/certs/ca-bundle.crt`,
certificateDirectoriesPath: refExpr`/certs`,
- rootCertificatesPath: "/etc/ssl/certs",
+ rootCertificatesPath: '/etc/ssl/certs',
isContainer: true,
}));
await builder.build().run();
```
+
+
+
+
+```go title="apphost.go"
+api := builder.
+ AddContainer("api", "my-api:latest").
+ WithHttpsCertificateConfiguration(
+ func(ctx aspire.HttpsCertificateConfigurationCallbackAnnotationContext) {
+ arguments := ctx.Arguments()
+ _ = arguments.Add("--tls-cert")
+ _ = arguments.Add(ctx.CertificatePath())
+ _ = arguments.Add("--tls-key")
+ _ = arguments.Add(ctx.KeyPath())
+
+ environment := ctx.Environment()
+ _ = environment.Set("TLS_CERT_FILE", ctx.CertificatePath())
+ _ = environment.Set("TLS_KEY_FILE", ctx.KeyPath())
+ _ = environment.Set("TLS_PFX_FILE", ctx.PfxPath())
+ },
+ )
+if err := api.Err(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+}
+```
+
+
+
+
+```java title="AppHost.java"
+var api = builder.addContainer("api", "my-api:latest");
+api.withHttpsCertificateConfiguration(ctx -> {
+ ctx.arguments().add("--tls-cert");
+ ctx.arguments().add(ctx.certificatePath());
+ ctx.arguments().add("--tls-key");
+ ctx.arguments().add(ctx.keyPath());
+
+ ctx.environment().set("TLS_CERT_FILE", ctx.certificatePath());
+ ctx.environment().set("TLS_KEY_FILE", ctx.keyPath());
+ ctx.environment().set("TLS_PFX_FILE", ctx.pfxPath());
+});
+```
+
@@ -377,8 +467,16 @@ You can control this behavior per resource using the `WithDeveloperCertificateTr
To explicitly enable or disable development certificate trust for a specific resource:
-
+
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -392,23 +490,28 @@ var pythonApp = builder.AddPythonApp("api", "../api", "main.py")
builder.Build().Run();
```
+
+
```typescript title="apphost.mts" twoslash
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
// Explicitly enable development certificate trust
-const nodeApp = await builder.addNodeApp("frontend", "../frontend", "index.js")
- .withDeveloperCertificateTrust(true);
+const nodeApp = await builder
+ .addNodeApp('frontend', '../frontend', 'index.js')
+ .withDeveloperCertificateTrust(true);
// Disable development certificate trust
-const pythonApp = await builder.addPythonApp("api", "../api", "main.py")
- .withDeveloperCertificateTrust(false);
+const pythonApp = await builder
+ .addPythonApp('api', '../api', 'main.py')
+ .withDeveloperCertificateTrust(false);
await builder.build().run();
```
+
@@ -418,6 +521,17 @@ Certificate authority collections allow you to bundle custom certificates and ma
#### Create and use a certificate authority collection
+
+
+
```csharp title="AppHost.cs"
using System.Security.Cryptography.X509Certificates;
@@ -438,9 +552,8 @@ builder.AddNpmApp("my-project", "../myapp")
builder.Build().Run();
```
-
+
+
In the preceding example, the certificate bundle is created with custom certificates and then applied to a Node.js application, enabling it to trust those certificates.
@@ -465,8 +578,16 @@ Attempts to append the configured certificates to the default trusted certificat
This is the default scope for most resources. For Python resources, only OTEL trust configuration will be applied in this mode.
-
+
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -475,18 +596,25 @@ builder.AddNodeApp("api", "../api")
builder.Build().Run();
```
+
+
```typescript title="apphost.mts" twoslash
-import { createBuilder, CertificateTrustScope } from './.aspire/modules/aspire.mjs';
+import {
+ createBuilder,
+ CertificateTrustScope,
+} from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
-await builder.addNodeApp("api", "../api", "index.js")
- .withCertificateTrustScope(CertificateTrustScope.Append);
+await builder
+ .addNodeApp('api', '../api', 'index.js')
+ .withCertificateTrustScope(CertificateTrustScope.Append);
await builder.build().run();
```
+
@@ -496,25 +624,36 @@ await builder.build().run();
#### Override mode
Attempts to override a resource to only trust the configured certificates, replacing the default trusted certificates entirely. This mode is useful when you need strict control over which certificates are trusted.
+
+
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -528,9 +667,8 @@ builder.AddPythonModule("api", "./api", "uvicorn")
builder.Build().Run();
```
-
+
+
#### System mode
@@ -538,8 +676,16 @@ Attempts to combine the configured certificates with the default system root cer
This is the default scope for Python projects because Python only has mechanisms to fully override certificate trust.
-
+
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -548,18 +694,25 @@ builder.AddPythonApp("worker", "../worker", "main.py")
builder.Build().Run();
```
+
+
```typescript title="apphost.mts" twoslash
-import { createBuilder, CertificateTrustScope } from './.aspire/modules/aspire.mjs';
+import {
+ createBuilder,
+ CertificateTrustScope,
+} from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
-await builder.addPythonApp("worker", "../worker", "main.py")
- .withCertificateTrustScope(CertificateTrustScope.System);
+await builder
+ .addPythonApp('worker', '../worker', 'main.py')
+ .withCertificateTrustScope(CertificateTrustScope.System);
await builder.build().run();
```
+
@@ -571,6 +724,7 @@ This is the default scope for .NET projects on Windows, as there's no way to aut
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -579,18 +733,61 @@ builder.AddContainer("service", "myimage")
builder.Build().Run();
```
+
+
```typescript title="apphost.mts" twoslash
-import { createBuilder, CertificateTrustScope } from './.aspire/modules/aspire.mjs';
+import {
+ createBuilder,
+ CertificateTrustScope,
+} from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
-await builder.addContainer("service", { image: "myimage", tag: "latest" })
- .withCertificateTrustScope(CertificateTrustScope.None);
+await builder
+ .addContainer('service', { image: 'myimage', tag: 'latest' })
+ .withCertificateTrustScope(CertificateTrustScope.None);
await builder.build().run();
```
+
+
+
+
+```python title="apphost.py"
+service = builder.add_container("service", "myimage")
+service.with_certificate_trust_scope("None")
+```
+
+
+
+
+```go title="apphost.go"
+service := builder.
+ AddContainer("service", "myimage").
+ WithCertificateTrustScope(aspire.CertificateTrustScopeNone)
+```
+
+
+
+
+```java title="AppHost.java"
+var service = builder.addContainer("service", "myimage");
+service.withCertificateTrustScope(CertificateTrustScope.NONE);
+```
+
+
+
+
+```rust title="apphost.rs"
+let service = builder.add_container(
+ "service",
+ serde_json::json!("myimage"),
+)?;
+service.with_certificate_trust_scope(CertificateTrustScope::None)?;
+```
+
@@ -602,8 +799,16 @@ For advanced scenarios, you can specify custom certificate trust behavior using
Use `WithCertificateTrustConfiguration` to customize how certificate trust is configured for a resource:
-
+
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -625,6 +830,7 @@ builder.AddContainer("api", "myimage")
builder.Build().Run();
```
+
+
+
+For a Python AppHost, `defaultWatchEnabled` watches `apphost.py`. When AppHost code changes, Aspire restarts the AppHost-managed application so the updated model is applied.
+
+Resource processes keep their runtime-specific development behavior. Use this workflow for AppHost model, endpoint, parameter, and integration changes; use each resource's own reload or rebuild workflow for application source changes.
+
+```bash title="Aspire CLI"
+aspire config set features.defaultWatchEnabled true
+aspire run
+```
+
+
+
+
+For a Go AppHost, `defaultWatchEnabled` watches `apphost.go`. When AppHost code changes, Aspire rebuilds and restarts the AppHost-managed application so the updated model is applied.
+
+Resource processes keep their runtime-specific development behavior. Use this workflow for AppHost model, endpoint, parameter, and integration changes; use each resource's own reload or rebuild workflow for application source changes.
+
+```bash title="Aspire CLI"
+aspire config set features.defaultWatchEnabled true
+aspire run
+```
+
+
+
+
+For a Java AppHost, `defaultWatchEnabled` watches `AppHost.java`. When AppHost code changes, Aspire rebuilds and restarts the AppHost-managed application so the updated model is applied.
+
+Resource processes keep their runtime-specific development behavior. Use this workflow for AppHost model, endpoint, parameter, and integration changes; use each resource's own reload or rebuild workflow for application source changes.
+
+```bash title="Aspire CLI"
+aspire config set features.defaultWatchEnabled true
+aspire run
+```
+
+
+
+
+For a Rust AppHost, `defaultWatchEnabled` watches `apphost.rs`. When AppHost code changes, Aspire rebuilds and restarts the AppHost-managed application so the updated model is applied.
+
+Resource processes keep their runtime-specific development behavior. Use this workflow for AppHost model, endpoint, parameter, and integration changes; use each resource's own reload or rebuild workflow for application source changes.
+
+```bash title="Aspire CLI"
+aspire config set features.defaultWatchEnabled true
+aspire run
+```
+
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 883296b20..70705f0c1 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
@@ -97,7 +97,14 @@ volumes:
**Aspire equivalent:**
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -213,7 +220,14 @@ volumes:
**Aspire equivalent:**
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -308,7 +322,14 @@ services:
**Aspire approach:**
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -364,7 +385,14 @@ await builder.build().run();
If your application expects URL-format variables like `DATABASE_URL` or `REDIS_URL`, construct them manually using the `WithEnvironment` callback:
-
+
```csharp title="AppHost.cs"
var dbPassword = builder.AddParameter("dbPassword", secret: true);
@@ -456,6 +484,67 @@ const worker = await builder.addContainer("worker", { image: "myworker", tag: "l
await builder.build().run();
```
+
+
+
+```python title="apphost.py"
+app = builder.add_container("app", "myapp:latest")
+app.with_volume("/data", name="app-data", is_read_only=True)
+app.with_bind_mount("./config", "/app/config", is_read_only=True)
+worker = builder.add_container("worker", "myworker:latest")
+worker.with_volume("/shared", name="app-data")
+```
+
+
+
+
+```go title="apphost.go"
+app := builder.
+ AddContainer("app", &aspire.AddContainerOptions{
+ Image: "myapp",
+ Tag: aspire.StringPtr("latest"),
+ }).
+ WithVolume("/data", &aspire.WithVolumeOptions{
+ Name: aspire.StringPtr("app-data"),
+ IsReadOnly: aspire.BoolPtr(true),
+ }).
+ WithBindMount("./config", "/app/config", &aspire.WithBindMountOptions{
+ IsReadOnly: aspire.BoolPtr(true),
+ })
+worker := builder.
+ AddContainer("worker", "myworker:latest").
+ WithVolume("/shared", &aspire.WithVolumeOptions{
+ Name: aspire.StringPtr("app-data"),
+ })
+```
+
+
+
+
+```java title="AppHost.java"
+var app = builder.addContainer("app", "myapp:latest");
+app.withVolume(
+ "/data",
+ new WithVolumeOptions().name("app-data").isReadOnly(true));
+app.withBindMount("./config", "/app/config", true);
+var worker = builder.addContainer("worker", "myworker:latest");
+worker.withVolume("/shared", new WithVolumeOptions().name("app-data"));
+```
+
+
+
+
+```rust title="apphost.rs"
+let app = builder.add_container("app", serde_json::json!("myapp:latest"))?;
+app.with_volume("/data", Some("app-data"), Some(true))?;
+app.with_bind_mount("./config", "/app/config", Some(true))?;
+let worker = builder.add_container(
+ "worker",
+ serde_json::json!("myworker:latest"),
+)?;
+worker.with_volume("/shared", Some("app-data"), None)?;
+```
+
@@ -561,7 +650,14 @@ Aspire generates .NET-style connection strings (`ConnectionStrings__*`) rather t
**Solution**: If your application expects specific URL formats, construct them manually using `WithEnvironment()`:
-
+
```csharp title="AppHost.cs"
var dbPassword = builder.AddParameter("dbPassword", secret: true);
@@ -615,6 +711,40 @@ const api = await builder.addProject("api", "./Api/Api.csproj", "https")
.waitFor(database); // Startup ordering
```
+
+```python title="apphost.py"
+api = builder.add_project("api", "./Api/Api.csproj")
+api.with_reference(database)
+api.wait_for(database)
+```
+
+
+```go title="apphost.go"
+api := builder.
+ AddProject("api", "./Api/Api.csproj").
+ WithReference(database).
+ WaitFor(database)
+```
+
+
+```java title="AppHost.java"
+var api = builder.addProject("api", "./Api/Api.csproj");
+api.withReference(database);
+api.waitFor(database);
+```
+
+
+```rust title="apphost.rs"
+let api = builder.add_project("api", "./Api/Api.csproj", None)?;
+api.with_reference(serialize_handle(&database), None, None, None)?;
+
+let database_resource = IResource::new(
+ database.handle().clone(),
+ database.client().clone(),
+);
+api.wait_for(&database_resource, None)?;
+```
+
#### Volume mounting issues
@@ -629,7 +759,14 @@ Aspire automatically assigns random ports by default.
**Solution**: Use `WithHostPort()` or `WithHttpEndpoint(port:)` for static port mapping:
-
+
```csharp title="AppHost.cs"
var redis = builder.AddRedis("cache")
@@ -663,11 +800,46 @@ const api = await builder.addProject("api", "./Api/Api.csproj", "https")
.withHttpHealthCheck("/health");
```
+
+```python title="apphost.py"
+api = builder.add_project("api", "./Api/Api.csproj")
+api.with_http_health_check(path="/health")
+```
+
+
+```go title="apphost.go"
+api := builder.
+ AddProject("api", "./Api/Api.csproj").
+ WithHttpHealthCheck(&aspire.WithHttpHealthCheckOptions{
+ Path: aspire.StringPtr("/health"),
+ })
+```
+
+
+```java title="AppHost.java"
+var api = builder.addProject("api", "./Api/Api.csproj");
+api.withHttpHealthCheck(
+ new WithHttpHealthCheckOptions().path("/health"));
+```
+
+
+```rust title="apphost.rs"
+let api = builder.add_project("api", "./Api/Api.csproj", None)?;
+api.with_http_health_check(Some("/health"), None, None)?;
+```
+
For custom container health checks that need shell commands (like RabbitMQ), register a custom health check and associate it with the resource:
-
+
```csharp title="AppHost.cs"
builder.Services.AddHealthChecks()
diff --git a/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx b/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx
index 31067eb24..365680b87 100644
--- a/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx
+++ b/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx
@@ -5,7 +5,6 @@ description: Learn how session, persistent, resource-scoped, and parent-process
import AppHostTabs from '@components/AppHostTabs.astro';
-
import { Image } from 'astro:assets';
import LearnMore from '@components/LearnMore.astro';
import persistentContainer from '@assets/whats-new/aspire-9/persistent-container.png';
@@ -88,6 +87,16 @@ Starting in Aspire 13.5, `WithExplicitStart()` also affects when execution confi
For session-scoped resources (the default lifetime), Aspire defers Developer Control Plane (DCP) registration until you manually start the resource from the dashboard. This means execution configuration callbacks — such as `WithEnvironment(context => ...)` — are **not** evaluated during AppHost startup. They run only when the resource is manually started.
+) -> Value instead of a typed EnvironmentCallbackContext callback.',
+ }}
+>
+
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -104,6 +113,33 @@ var job = builder.AddExecutable("batch-job", "dotnet", ".", "run", "--project",
builder.Build().Run();
```
+
+
+
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+declare function getApiKeyAsync(): Promise;
+
+// The callback below is NOT evaluated during AppHost startup.
+// It runs only when the resource is manually started from the dashboard.
+await builder
+ .addExecutable('batch-job', 'dotnet', '.', ['run', '--project', 'BatchJob'])
+ .withExplicitStart()
+ .withEnvironmentCallback(async (context) => {
+ // Prompt or compute dynamic configuration at start time
+ const environment = await context.environment();
+ await environment.set('API_KEY', await getApiKeyAsync());
+ });
+
+await builder.build().run();
+```
+
+
+
+
:::note[Placeholder API used for demonstration]
`GetApiKeyAsync` is a placeholder used to demonstrate deferred, callback-based configuration. Its implementation isn't shown because the relevant behavior is retrieving a value when the callback runs and assigning that value to an environment variable on the target resource.
:::
@@ -112,6 +148,13 @@ builder.Build().Run();
For persistent resources, Aspire must register the resource with the Developer Control Plane (DCP) immediately at startup so it can discover any existing running instance. However, when you manually start a persistent explicit-start resource, Aspire patches the existing DCP resource to start it rather than deleting and recreating it. This means the execution configuration callbacks run once during startup registration and are **not** re-evaluated when you manually start the resource.
+) -> Value instead of a typed EnvironmentCallbackContext callback.',
+ }}
+>
+
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -131,11 +174,129 @@ var cache = builder.AddContainer("long-lived-cache", "my-cache-image")
builder.Build().Run();
```
+
+
+
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+// Persistent explicit-start resources are registered at startup to detect existing instances.
+// The callback runs during startup registration — not again when manually started.
+await builder
+ .addContainer('long-lived-cache', {
+ image: 'my-cache-image',
+ tag: 'latest',
+ })
+ .withPersistentLifetime()
+ .withExplicitStart()
+ .withEnvironmentCallback(async (context) => {
+ const environment = await context.environment();
+ await environment.set('CACHE_SIZE', '512mb');
+ });
+
+await builder.build().run();
+```
+
+
+
+
+```python title="apphost.py"
+from aspire_app import create_builder
+
+with create_builder() as builder:
+ # Persistent explicit-start resources are registered at startup to detect
+ # existing instances. The callback runs during startup registration.
+ cache = builder.add_container("long-lived-cache", "my-cache-image")
+ cache.with_persistent_lifetime()
+ cache.with_explicit_start()
+ cache.with_env_callback(
+ lambda context: context.env.set("CACHE_SIZE", "512mb")
+ )
+
+ builder.run()
+```
+
+
+
+
+```go title="apphost.go"
+package main
+
+import (
+ "log"
+
+ "apphost/modules/aspire"
+)
+
+func main() {
+ builder, err := aspire.CreateBuilder()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ // Persistent explicit-start resources are registered at startup to detect
+ // existing instances. The callback runs during startup registration.
+ cache := builder.
+ AddContainer("long-lived-cache", "my-cache-image").
+ WithPersistentLifetime().
+ WithExplicitStart().
+ WithEnvironmentCallback(func(context aspire.EnvironmentCallbackContext) {
+ if err := context.Environment().Set("CACHE_SIZE", "512mb"); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+ })
+ if err := cache.Err(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ app, err := builder.Build()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+ if err := app.Run(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+}
+```
+
+
+
+
+```java title="AppHost.java"
+import aspire.*;
+
+void main(String[] args) throws Exception {
+ var builder = DistributedApplication.CreateBuilder(args);
+
+ // Persistent explicit-start resources are registered at startup to detect
+ // existing instances. The callback runs during startup registration.
+ var cache = builder.addContainer("long-lived-cache", "my-cache-image");
+ cache.withPersistentLifetime();
+ cache.withExplicitStart();
+ cache.withEnvironmentCallback(
+ context -> context.environment().set("CACHE_SIZE", "512mb"));
+
+ builder.build().run();
+}
+```
+
+
+
+
## Configure a persistent container
For new code, configure a persistent container with `WithPersistentLifetime()`:
-
+
```csharp title="AppHost.cs"
@@ -215,6 +376,61 @@ await worker.withPersistentLifetime();
await builder.build().run();
```
+
+
+
+```python title="apphost.py"
+from aspire_app import create_builder
+
+with create_builder() as builder:
+ worker = builder.add_executable("worker", "node", "../worker", ["server.js"])
+ worker.with_http_endpoint(port=5050, target_port=5050)
+ worker.with_persistent_lifetime()
+
+ builder.run()
+```
+
+
+
+
+```go title="apphost.go"
+worker := builder.
+ AddExecutable("worker", "node", "../worker", []string{"server.js"}).
+ WithHttpEndpoint(&aspire.WithHttpEndpointOptions{
+ Port: aspire.Float64Ptr(5050),
+ TargetPort: aspire.Float64Ptr(5050),
+ }).
+ WithPersistentLifetime()
+if err := worker.Err(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+}
+```
+
+
+
+
+```java title="AppHost.java"
+var worker = builder.addExecutable(
+ "worker", "node", "../worker", new String[] { "server.js" });
+worker.withHttpEndpoint(
+ new WithHttpEndpointOptions().port(5050.0).targetPort(5050.0));
+worker.withPersistentLifetime();
+```
+
+
+
+
+```rust title="apphost.rs"
+let worker = builder.add_executable(
+ "worker",
+ "node",
+ "../worker",
+ vec!["server.js".to_string()],
+)?;
+worker.with_http_endpoint(Some(5050.0), Some(5050.0), None, None, None)?;
+worker.with_persistent_lifetime()?;
+```
+
@@ -250,6 +466,47 @@ await api.withPersistentLifetime();
await builder.build().run();
```
+
+
+
+```python title="apphost.py"
+from aspire_app import create_builder
+
+with create_builder() as builder:
+ api = builder.add_project("api", "../ApiService/ApiService.csproj")
+ api.with_persistent_lifetime()
+
+ builder.run()
+```
+
+
+
+
+```go title="apphost.go"
+api := builder.
+ AddProject("api", "../ApiService/ApiService.csproj").
+ WithPersistentLifetime()
+if err := api.Err(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+}
+```
+
+
+
+
+```java title="AppHost.java"
+var api = builder.addProject("api", "../ApiService/ApiService.csproj");
+api.withPersistentLifetime();
+```
+
+
+
+
+```rust title="apphost.rs"
+let api = builder.add_project("api", "../ApiService/ApiService.csproj", None)?;
+api.with_persistent_lifetime()?;
+```
+
@@ -259,7 +516,14 @@ Persistent project and executable resources are run by Aspire's orchestrator so
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"
@@ -342,6 +606,62 @@ await worker.withParentProcessLifetime(parentProcessId);
await builder.build().run();
```
+
+
+
+```python title="apphost.py"
+import os
+
+parent_process_id = int(os.environ["RESOURCE_PARENT_PROCESS_ID"])
+worker = builder.add_executable(
+"scoped-worker", "node", "../worker", ["server.js"]
+)
+worker.with_parent_process_lifetime(parent_process_id)
+
+```
+
+
+
+
+```go title="apphost.go"
+parentProcessID, err := strconv.ParseFloat(
+ os.Getenv("RESOURCE_PARENT_PROCESS_ID"),
+ 64,
+)
+if err != nil {
+ log.Fatal(err)
+}
+worker := builder.
+ AddExecutable("scoped-worker", "node", "../worker", []string{"server.js"}).
+ WithParentProcessLifetime(parentProcessID)
+```
+
+
+
+
+```java title="AppHost.java"
+var parentProcessId = Double.parseDouble(
+ System.getenv("RESOURCE_PARENT_PROCESS_ID"));
+var worker = builder.addExecutable(
+ "scoped-worker", "node", "../worker", new String[] { "server.js" });
+worker.withParentProcessLifetime(parentProcessId);
+```
+
+
+
+
+```rust title="apphost.rs"
+let parent_process_id = std::env::var("RESOURCE_PARENT_PROCESS_ID")?
+ .parse::()?;
+let worker = builder.add_executable(
+ "scoped-worker",
+ "node",
+ "../worker",
+ vec!["server.js".to_string()],
+)?;
+worker.with_parent_process_lifetime(parent_process_id)?;
+```
+
@@ -353,7 +673,14 @@ The older container-specific lifetime API is still supported. Use `WithLifetime(
For new code, prefer the shared `WithPersistentLifetime()` and `WithSessionLifetime()` APIs because they work consistently across containers, executables, and projects.
-
+
```csharp title="AppHost.cs"
@@ -415,7 +742,14 @@ For example, if you have a service named `"postgres"` in an AppHost project loca
For advanced scenarios, you can set a custom container name using the `WithContainerName` method:
-
+
```csharp title="AppHost.cs"
@@ -483,7 +817,14 @@ For persistent executable and project resources, stop the running process with y
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"
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 7c83b91dc..4da5707d2 100644
--- a/src/frontend/src/content/docs/app-host/typescript-apphost.mdx
+++ b/src/frontend/src/content/docs/app-host/typescript-apphost.mdx
@@ -491,7 +491,7 @@ aspire update
## See also
-- [Build your first app](/get-started/first-app/?lang=typescript)
+- [Build your first app](/get-started/first-app/?aspire-lang=typescript)
- [AppHost overview](/get-started/app-host/)
- [Multi-language architecture](/architecture/multi-language-architecture/)
- [aspire doctor command](/reference/cli/commands/aspire-doctor/)
diff --git a/src/frontend/src/content/docs/app-host/with-terminal.mdx b/src/frontend/src/content/docs/app-host/with-terminal.mdx
index dbd6fd3e4..c81821796 100644
--- a/src/frontend/src/content/docs/app-host/with-terminal.mdx
+++ b/src/frontend/src/content/docs/app-host/with-terminal.mdx
@@ -37,6 +37,92 @@ const agent = await builder.addExecutable("agent", "my-agent", ".")
await builder.build().run();
```
+
+
+
+```python title="apphost.py"
+from aspire_app import create_builder
+
+with create_builder() as builder:
+ agent = builder.add_executable("agent", "my-agent", ".", [])
+ agent.with_terminal()
+
+ builder.run()
+```
+
+
+
+
+```go title="apphost.go"
+package main
+
+import (
+ "log"
+
+ "apphost/modules/aspire"
+)
+
+func main() {
+ builder, err := aspire.CreateBuilder()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ agent := builder.
+ AddExecutable("agent", "my-agent", ".", []string{}).
+ WithTerminal()
+ if err := agent.Err(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ app, err := builder.Build()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+ if err := app.Run(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+}
+```
+
+
+
+
+```java title="AppHost.java"
+import aspire.*;
+
+void main(String[] args) throws Exception {
+ var builder = DistributedApplication.CreateBuilder(args);
+
+ var agent = builder.addExecutable(
+ "agent", "my-agent", ".", new String[] {});
+ agent.withTerminal();
+
+ builder.build().run();
+}
+```
+
+
+
+
+```rust title="apphost.rs"
+#[path = ".aspire/modules/mod.rs"]
+mod aspire;
+
+use aspire::*;
+
+fn main() -> Result<(), Box> {
+ let builder = create_builder(None)?;
+
+ let agent = builder.add_executable("agent", "my-agent", ".", vec![])?;
+ agent.with_terminal()?;
+
+ let app = builder.build()?;
+ app.run(None)?;
+ Ok(())
+}
+```
+
@@ -120,9 +206,59 @@ await builder.build().run();
```
-In TypeScript AppHosts, `withTerminal()` currently applies the default options shown above. Configurable options are coming to the non-C# API as `WithTerminal` is finalized (tracked by [microsoft/aspire#18105](https://github.com/microsoft/aspire/issues/18105)).
+In TypeScript AppHosts, `withTerminal()` currently applies the default options shown above. Configurable options are coming to the generated AppHost SDKs as `WithTerminal` is finalized (tracked by [microsoft/aspire#18105](https://github.com/microsoft/aspire/issues/18105)).
+
+
+
+```python title="apphost.py"
+from aspire_app import create_builder
+
+with create_builder() as builder:
+ agent = builder.add_executable("agent", "my-agent", ".", [])
+ agent.with_terminal()
+
+ builder.run()
+```
+
+Python AppHosts currently apply the default terminal options. The generated SDK doesn't expose configurable columns, rows, or terminal-host visibility.
+
+
+
+
+```go title="apphost.go"
+agent := builder.
+ AddExecutable("agent", "my-agent", ".", []string{}).
+ WithTerminal()
+if err := agent.Err(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+}
+```
+
+Go AppHosts currently apply the default terminal options. The generated SDK doesn't expose configurable columns, rows, or terminal-host visibility.
+
+
+
+
+```java title="AppHost.java"
+var agent = builder.addExecutable(
+ "agent", "my-agent", ".", new String[] {});
+agent.withTerminal();
+```
+
+Java AppHosts currently apply the default terminal options. The generated SDK doesn't expose configurable columns, rows, or terminal-host visibility.
+
+
+
+
+```rust title="apphost.rs"
+let agent = builder.add_executable("agent", "my-agent", ".", vec![])?;
+agent.with_terminal()?;
+```
+
+Rust AppHosts currently apply the default terminal options. The generated SDK doesn't expose configurable columns, rows, or terminal-host visibility.
+
@@ -130,7 +266,15 @@ In TypeScript AppHosts, `withTerminal()` currently applies the default options s
Each replica of a resource gets its own independent terminal session. Aspire creates one terminal host per parent replica, so requesting three replicas yields three separate terminals. The order of `WithReplicas` and `WithTerminal` does not matter—the final replica count is always honored:
-
+
```csharp title="AppHost.cs"
@@ -145,21 +289,6 @@ var agent = builder.AddExecutable("agent", "my-agent", ".")
builder.Build().Run();
```
-
-
-
-```typescript title="apphost.ts"
-import { createBuilder } from './.aspire/modules/aspire.mjs';
-
-const builder = await createBuilder();
-
-const agent = await builder.addExecutable("agent", "my-agent", ".")
- .withReplicas(3)
- .withTerminal();
-
-await builder.build().run();
-```
-
diff --git a/src/frontend/src/content/docs/app-host/withdockerfile.mdx b/src/frontend/src/content/docs/app-host/withdockerfile.mdx
index 192b2324b..1bdb77ff8 100644
--- a/src/frontend/src/content/docs/app-host/withdockerfile.mdx
+++ b/src/frontend/src/content/docs/app-host/withdockerfile.mdx
@@ -57,6 +57,37 @@ const container = await builder.addDockerfile(
"mycontainer", "relative/context/path");
```
+
+```python title="apphost.py"
+container = builder.add_dockerfile(
+ "mycontainer", "relative/context/path"
+)
+```
+
+
+```go title="apphost.go"
+container := builder.AddDockerfile(
+ "mycontainer",
+ "relative/context/path",
+)
+```
+
+
+```java title="AppHost.java"
+var container = builder.addDockerfile(
+ "mycontainer", "relative/context/path");
+```
+
+
+```rust title="apphost.rs"
+let container = builder.add_dockerfile(
+ "mycontainer",
+ "relative/context/path",
+ None,
+ None,
+)?;
+```
+
Unless the context path argument is a rooted path the context path is interpreted as being relative to the AppHost project directory.
@@ -90,6 +121,65 @@ const container = (await builder.executionContext.isRunMode())
"mycontainer", "relative/context/path", "Dockerfile.release");
```
+
+```python title="apphost.py"
+dockerfile_path = (
+ "Dockerfile.debug"
+ if builder.execution_context.is_run_mode
+ else "Dockerfile.release"
+)
+container = builder.add_dockerfile(
+ "mycontainer",
+ "relative/context/path",
+ dockerfile_path=dockerfile_path,
+)
+```
+
+
+```go title="apphost.go"
+isRunMode, err := builder.ExecutionContext().IsRunMode()
+if err != nil {
+ log.Fatal(aspire.FormatError(err))
+}
+dockerfilePath := "Dockerfile.release"
+if isRunMode {
+ dockerfilePath = "Dockerfile.debug"
+}
+container := builder.AddDockerfile(
+ "mycontainer",
+ "relative/context/path",
+ &aspire.AddDockerfileOptions{
+ DockerfilePath: aspire.StringPtr(dockerfilePath),
+ },
+)
+```
+
+
+```java title="AppHost.java"
+var dockerfilePath = builder.executionContext().isRunMode()
+ ? "Dockerfile.debug"
+ : "Dockerfile.release";
+var options = new AddDockerfileOptions()
+ .dockerfilePath(dockerfilePath);
+var container = builder.addDockerfile(
+ "mycontainer", "relative/context/path", options);
+```
+
+
+```rust title="apphost.rs"
+let dockerfile_path = if builder.execution_context()?.is_run_mode()? {
+ "Dockerfile.debug"
+} else {
+ "Dockerfile.release"
+};
+let container = builder.add_dockerfile(
+ "mycontainer",
+ "relative/context/path",
+ Some(dockerfile_path),
+ None,
+)?;
+```
+
## Customize existing container resources
@@ -98,7 +188,14 @@ When using `AddDockerfile` the return value is an `IResourceBuilder
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -196,6 +293,26 @@ await builder.addDockerfileBuilder("frontend", "../frontend", configureDockerfil
await builder.build().run();
```
+
+
+
+The generated Python SDK exposes this operation as `builder.add_dockerfile_builder(name, context_path, callback, stage="runtime")`, with a typed `DockerfileBuilderCallbackContext`.
+
+
+
+
+The generated Go SDK exposes this operation as `builder.AddDockerfileBuilder(name, contextPath, callback, &aspire.AddDockerfileBuilderOptions{Stage: aspire.StringPtr("runtime")})`.
+
+
+
+
+The generated Java SDK exposes this operation as `builder.addDockerfileBuilder(name, contextPath, callback, "runtime")`.
+
+
+
+
+The generated Rust SDK includes `add_dockerfile_builder`, but its callback is emitted as `Fn(Vec) -> Value` rather than a typed `DockerfileBuilderCallbackContext`, so the fluent builder example isn't type-safe in Rust.
+
@@ -246,6 +363,26 @@ await builder
await builder.build().run();
```
+
+
+
+Use `container.with_dockerfile_builder("../frontend", callback, stage="runtime")`. The callback receives a typed `DockerfileBuilderCallbackContext`.
+
+
+
+
+Use `container.WithDockerfileBuilder("../frontend", callback, &aspire.WithDockerfileBuilderOptions{Stage: aspire.StringPtr("runtime")})`.
+
+
+
+
+Use `container.withDockerfileBuilder("../frontend", callback, "runtime")`.
+
+
+
+
+The generated Rust SDK includes `with_dockerfile_builder`, but its callback is emitted as `Fn(Vec) -> Value` rather than a typed `DockerfileBuilderCallbackContext`.
+
@@ -299,6 +436,26 @@ CMD ["node", "server.js"]
await builder.build().run();
```
+
+
+
+Use `builder.add_dockerfile_factory("myapp", "../myapp", callback)`. The Python callback receives `DockerfileFactoryContext` and returns the Dockerfile text as `str`.
+
+
+
+
+Use `builder.AddDockerfileFactory("myapp", "../myapp", callback)`. The Go callback has the signature `func(aspire.DockerfileFactoryContext) string`.
+
+
+
+
+Use `builder.addDockerfileFactory("myapp", "../myapp", callback)`. The Java callback receives `DockerfileFactoryContext` and returns `String`.
+
+
+
+
+The generated Rust SDK includes `add_dockerfile_factory`, but its callback is emitted as `Fn(Vec) -> Value` rather than a typed context returning `String`.
+
@@ -341,6 +498,18 @@ EXPOSE 80
await builder.build().run();
```
+
+
+Use `container.with_dockerfile_factory("../myapp", callback)`. The callback receives `DockerfileFactoryContext` and returns the Dockerfile text as `str`.
+
+
+Use `container.WithDockerfileFactory("../myapp", callback)`. The callback has the signature `func(aspire.DockerfileFactoryContext) string`.
+
+
+Use `container.withDockerfileFactory("../myapp", callback)`. The callback receives `DockerfileFactoryContext` and returns `String`.
+
+
+The generated Rust SDK includes `with_dockerfile_factory`, but its callback is emitted as `Fn(Vec) -> Value` rather than a typed context returning `String`.
@@ -367,6 +536,42 @@ const container = await builder.addDockerfile("mygoapp", "relative/context/path"
await container.withBuildArg("GO_VERSION", "1.22");
```
+
+```python title="apphost.py"
+container = builder.add_dockerfile(
+ "mygoapp", "relative/context/path"
+)
+container.with_build_arg("GO_VERSION", "1.22")
+```
+
+
+```go title="apphost.go"
+container := builder.
+ AddDockerfile("mygoapp", "relative/context/path").
+ WithBuildArg("GO_VERSION", "1.22")
+```
+
+
+```java title="AppHost.java"
+var container = builder.addDockerfile(
+ "mygoapp", "relative/context/path");
+container.withBuildArg("GO_VERSION", "1.22");
+```
+
+
+```rust title="apphost.rs"
+let container = builder.add_dockerfile(
+ "mygoapp",
+ "relative/context/path",
+ None,
+ None,
+)?;
+container.with_build_arg(
+ "GO_VERSION",
+ serde_json::json!("1.22"),
+)?;
+```
+
The value parameter on the `WithBuildArg` method can be a literal value (`boolean`, `string`, `int`) or it can be a resource builder for a [parameter resource](/fundamentals/external-parameters/). The following code replaces the `GO_VERSION` with a parameter value that can be specified at deployment time.
@@ -394,6 +599,51 @@ const container = await builder.addDockerfile("mygoapp", "relative/context/path"
await container.withBuildArg("GO_VERSION", goVersion);
```
+
+```python title="apphost.py"
+go_version = builder.add_parameter("goversion")
+container = builder.add_dockerfile(
+ "mygoapp", "relative/context/path"
+)
+container.with_build_arg("GO_VERSION", go_version)
+```
+
+
+```go title="apphost.go"
+goVersion := builder.AddParameter("goversion")
+container := builder.
+ AddDockerfile("mygoapp", "relative/context/path").
+ WithBuildArg("GO_VERSION", goVersion)
+```
+
+
+```java title="AppHost.java"
+var goVersion = builder.addParameter("goversion");
+var container = builder.addDockerfile(
+ "mygoapp", "relative/context/path");
+container.withBuildArg("GO_VERSION", goVersion);
+```
+
+
+```rust title="apphost.rs"
+let go_version = builder.add_parameter(
+ "goversion",
+ None,
+ None,
+ None,
+)?;
+let container = builder.add_dockerfile(
+ "mygoapp",
+ "relative/context/path",
+ None,
+ None,
+)?;
+container.with_build_arg(
+ "GO_VERSION",
+ serialize_handle(&go_version),
+)?;
+```
+
Build arguments correspond to the [`ARG` command](https://docs.docker.com/build/guide/build-args/) in _Dockerfiles_. Expanding the preceding example, this is a multi-stage _Dockerfile_ which specifies specific container image version to use as a parameter.
@@ -446,6 +696,55 @@ const container = await builder.addDockerfile("myapp", "relative/context/path");
await container.withBuildSecret("ACCESS_TOKEN", accessToken);
```
+
+```python title="apphost.py"
+access_token = builder.add_parameter(
+ "accesstoken", secret=True
+)
+container = builder.add_dockerfile(
+ "myapp", "relative/context/path"
+)
+container.with_build_secret("ACCESS_TOKEN", access_token)
+```
+
+
+```go title="apphost.go"
+accessToken := builder.AddParameter(
+ "accesstoken",
+ &aspire.AddParameterOptions{Secret: aspire.BoolPtr(true)},
+)
+container := builder.
+ AddDockerfile("myapp", "relative/context/path").
+ WithBuildSecret("ACCESS_TOKEN", accessToken)
+```
+
+
+```java title="AppHost.java"
+var accessToken = builder.addParameter(
+ "accesstoken",
+ new AddParameterOptions().secret(true));
+var container = builder.addDockerfile(
+ "myapp", "relative/context/path");
+container.withBuildSecret("ACCESS_TOKEN", accessToken);
+```
+
+
+```rust title="apphost.rs"
+let access_token = builder.add_parameter(
+ "accesstoken",
+ None,
+ None,
+ Some(true),
+)?;
+let container = builder.add_dockerfile(
+ "myapp",
+ "relative/context/path",
+ None,
+ None,
+)?;
+container.with_build_secret("ACCESS_TOKEN", &access_token)?;
+```
+
For example, consider the `RUN` command in a _Dockerfile_ which exposes the specified secret to the specific command:
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 15fea19d3..bcbae5ddc 100644
--- a/src/frontend/src/content/docs/architecture/resource-api-patterns.mdx
+++ b/src/frontend/src/content/docs/architecture/resource-api-patterns.mdx
@@ -33,7 +33,14 @@ An `AddX(...)` method executes:
### Signature pattern
-
+
```csharp
@@ -90,7 +97,55 @@ const resource = await builder.addRedis("name" /*, optional params */);
```typescript
-resource.withEndpoint({ port: hostPort, targetPort: containerPort, name: endpointName });
+await resource.withEndpoint({ port: hostPort, targetPort: containerPort, name: endpointName });
+```
+
+
+
+
+```python
+resource.with_endpoint(
+ port=host_port,
+ target_port=container_port,
+ name=endpoint_name,
+)
+```
+
+
+
+
+```go
+resource.WithEndpoint(&aspire.WithEndpointOptions{
+ Port: aspire.Float64Ptr(float64(hostPort)),
+ TargetPort: aspire.Float64Ptr(float64(containerPort)),
+ Name: aspire.StringPtr(endpointName),
+})
+```
+
+
+
+
+```java
+resource.withEndpoint(new WithEndpointOptions()
+ .port(hostPort)
+ .targetPort(containerPort)
+ .name(endpointName));
+```
+
+
+
+
+```rust
+resource.with_endpoint(
+ Some(host_port),
+ Some(container_port),
+ None,
+ Some(endpoint_name),
+ None,
+ None,
+ None,
+ None,
+)?;
```
@@ -109,9 +164,37 @@ resource.withEndpoint({ port: hostPort, targetPort: containerPort, name: endpoin
```typescript
+await resource.withHealthCheck(healthCheckKey);
+```
+
+
+
+
+```python
+resource.with_health_check(health_check_key)
+```
+
+
+
+
+```go
+resource.WithHealthCheck(healthCheckKey)
+```
+
+
+
+
+```java
resource.withHealthCheck(healthCheckKey);
```
+
+
+
+```rust
+resource.with_health_check(health_check_key)?;
+```
+
@@ -129,10 +212,45 @@ resource.withHealthCheck(healthCheckKey);
```typescript
+await resource.withImage(imageName, { tag: imageTag });
+await resource.withImageRegistry(registryUrl);
+```
+
+
+
+
+```python
+resource.with_image(image_name, tag=image_tag)
+resource.with_image_registry(registry_url)
+```
+
+
+
+
+```go
+resource.WithImage(
+ imageName,
+ &aspire.WithImageOptions{Tag: aspire.StringPtr(imageTag)},
+)
+resource.WithImageRegistry(registryURL)
+```
+
+
+
+
+```java
resource.withImage(imageName, imageTag);
resource.withImageRegistry(registryUrl);
```
+
+
+
+```rust
+resource.with_image(image_name, Some(image_tag))?;
+resource.with_image_registry(registry_url)?;
+```
+
@@ -150,8 +268,40 @@ resource.withImageRegistry(registryUrl);
```typescript
+await resource.withEntrypoint("/bin/sh");
+await resource.withArgs(["--flag", "value"]);
+```
+
+
+
+
+```python
+resource.with_entrypoint("/bin/sh")
+resource.with_args(["--flag", "value"])
+```
+
+
+
+
+```go
+resource.WithEntrypoint("/bin/sh")
+resource.WithArgs([]string{"--flag", "value"})
+```
+
+
+
+
+```java
resource.withEntrypoint("/bin/sh");
-resource.withArgs(["--flag", "value"]);
+resource.withArgs(new String[] { "--flag", "value" });
+```
+
+
+
+
+```rust
+resource.with_entrypoint("/bin/sh")?;
+resource.with_args(vec!["--flag".to_string(), "value".to_string()])?;
```
@@ -159,7 +309,11 @@ resource.withArgs(["--flag", "value"]);
**Environment variables**:
-
+
```csharp
@@ -170,6 +324,27 @@ resource.withArgs(["--flag", "value"]);
```typescript
+await resource.withEnvironment("ENV_VAR", value);
+```
+
+
+
+
+```python
+resource.with_env("ENV_VAR", value)
+```
+
+
+
+
+```go
+resource.WithEnvironment("ENV_VAR", value)
+```
+
+
+
+
+```java
resource.withEnvironment("ENV_VAR", value);
```
@@ -178,7 +353,11 @@ resource.withEnvironment("ENV_VAR", value);
**Event subscriptions**:
-
+ -> Value rather than typed event contexts, so this typed event-handling pattern cannot be expressed safely.',
+ }}
+>
```csharp
@@ -189,13 +368,47 @@ builder.Eventing.Subscribe(resource, handler);
```typescript
-builder.addEventingSubscriber(async (context) => {
+await builder.addEventingSubscriber(async (context) => {
context.onBeforeStart(async (event) => {
// Handle event
});
});
```
+
+
+
+```python
+def subscribe(context):
+ context.on_before_start(lambda event: None)
+
+builder.add_eventing_subscriber(subscribe)
+```
+
+
+
+
+```go
+if err := builder.AddEventingSubscriber(func(context aspire.EventingSubscriberRegistrationContext) {
+ context.OnBeforeStart(func(event aspire.BeforeStartEvent) {
+ // Handle event
+ })
+}); err != nil {
+ log.Fatal(aspire.FormatError(err))
+}
+```
+
+
+
+
+```java
+builder.addEventingSubscriber(context -> {
+ context.onBeforeStart(event -> {
+ // Handle event
+ });
+});
+```
+
@@ -214,7 +427,14 @@ builder.addEventingSubscriber(async (context) => {
### Signature pattern
-
+
```csharp
@@ -251,7 +471,14 @@ Annotations are **public** metadata types implementing `IResourceAnnotation`. Th
### Definition and attachment
-
+
```csharp
@@ -301,7 +528,11 @@ Custom value objects defer evaluation and allow the framework to discover depend
### Attaching to resources
-
+
```csharp
@@ -313,13 +544,41 @@ builder.WithEnvironment(context =>
```typescript
+await resource.withEnvironment("REDIS_CONNECTION_STRING", redis);
+```
+
+
+
+
+```python
+resource.with_env("REDIS_CONNECTION_STRING", redis)
+```
+
+
+
+
+```go
+resource.WithEnvironment("REDIS_CONNECTION_STRING", redis)
+```
+
+
+
+
+```java
resource.withEnvironment("REDIS_CONNECTION_STRING", redis);
```
-
+
```csharp title="Example: BicepOutputReference"
@@ -344,7 +603,14 @@ In the TypeScript SDK, `BicepOutputReference` and the value provider interfaces
-
+
```csharp
diff --git a/src/frontend/src/content/docs/architecture/resource-hierarchies.mdx b/src/frontend/src/content/docs/architecture/resource-hierarchies.mdx
index ea12676f2..ce3071daf 100644
--- a/src/frontend/src/content/docs/architecture/resource-hierarchies.mdx
+++ b/src/frontend/src/content/docs/architecture/resource-hierarchies.mdx
@@ -70,6 +70,17 @@ In Aspire, configuration, connectivity details, and dependencies between distrib
Aspire represents these relationships through a **heterogeneous Directed Acyclic Graph (DAG)**. This graph tracks not only dependency ordering but also how **structured values** are passed between resources at multiple abstraction levels: configuration, connection, and runtime behavior.
+
+
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -80,6 +91,9 @@ var web = builder.AddNpmApp("web").WithReference(api);
builder.Build().Run();
```
+
+
+
```mermaid
architecture-beta
@@ -193,6 +207,15 @@ Aspire evaluates the model in **two distinct modes**:
**Example — Using `ReferenceExpression`:**
+
+
+
```csharp title="AppHost.cs"
var ep = api.GetEndpoint("http");
@@ -203,6 +226,9 @@ builder.WithEnvironment("HEALTH_URL",
);
```
+
+
+
_Publish manifest excerpt:_
```ini
@@ -222,6 +248,15 @@ HEALTH_URL=https://localhost:5000/health
### Alternate pattern using `ExecutionContext`
+
+
+
```csharp title="AppHost.cs"
var ep = api.GetEndpoint("http");
@@ -237,6 +272,9 @@ else
}
```
+
+
+
### Pattern used by `IResourceWithConnectionString`
A common implementation builds the connection string with `ReferenceExpression`, mixing any value objects (endpoint properties, parameters, other references):
@@ -279,6 +317,9 @@ These properties are dynamically resolved during the application's startup seque
Resources supporting endpoints should implement `IResourceWithEndpoints`, enabling the use of `GetEndpoint(name)` to retrieve an `EndpointReference`. This is implemented on the built-in `ProjectResource`, `ContainerResource` and `ExecutableResource`. It allows endpoints to be programmatically accessed and passed between resources.
+
+
+
```csharp title="Example — Endpoint Access and Resolution"
var builder = DistributedApplication.CreateBuilder(args);
@@ -291,6 +332,135 @@ var endpoint = redis.GetEndpoint("tcp");
builder.Build().Run();
```
+
+
+
+```typescript title="apphost.mts"
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+const redis = await builder.addContainer('redis', 'redis');
+await redis.withEndpoint({ name: 'tcp', targetPort: 6379 });
+
+const endpoint = await redis.getEndpoint('tcp');
+
+await builder.build().run();
+```
+
+
+
+
+```python title="apphost.py"
+from aspire_app import create_builder
+
+with create_builder() as builder:
+ redis = builder.add_container("redis", "redis")
+ redis.with_endpoint(name="tcp", target_port=6379)
+
+ endpoint = redis.get_endpoint("tcp")
+
+ builder.run()
+```
+
+
+
+
+```go title="apphost.go"
+package main
+
+import (
+ "log"
+
+ "apphost/modules/aspire"
+)
+
+func main() {
+ builder, err := aspire.CreateBuilder()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ redis := builder.
+ AddContainer("redis", "redis").
+ WithEndpoint(&aspire.WithEndpointOptions{
+ Name: aspire.StringPtr("tcp"),
+ TargetPort: aspire.Float64Ptr(6379),
+ })
+ if err := redis.Err(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ endpoint := redis.GetEndpoint("tcp")
+ if err := endpoint.Err(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ app, err := builder.Build()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+ if err := app.Run(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+}
+```
+
+
+
+
+```java title="AppHost.java"
+import aspire.*;
+
+void main(String[] args) throws Exception {
+ var builder = DistributedApplication.CreateBuilder(args);
+
+ var redis = builder.addContainer("redis", "redis");
+ redis.withEndpoint(new WithEndpointOptions()
+ .name("tcp")
+ .targetPort(6379));
+
+ var endpoint = redis.getEndpoint("tcp");
+
+ builder.build().run();
+}
+```
+
+
+
+
+```rust title="apphost.rs"
+#[path = ".aspire/modules/mod.rs"]
+mod aspire;
+
+use aspire::*;
+
+fn main() -> Result<(), Box> {
+ let builder = create_builder(None)?;
+
+ let redis = builder.add_container("redis", serde_json::json!("redis"))?;
+ redis.with_endpoint(
+ None,
+ Some(6379.0),
+ None,
+ Some("tcp"),
+ None,
+ None,
+ None,
+ None,
+ )?;
+
+ let endpoint = redis.get_endpoint("tcp")?;
+
+ let app = builder.build()?;
+ app.run(None)?;
+ Ok(())
+}
+```
+
+
+
+
### What does "allocated" mean?
An endpoint is **allocated** when Aspire resolves its runtime values (e.g., `Host`, `Port`, `Url`) during **run mode**. Allocation happens as part of the **startup sequence**, ensuring endpoints are ready for use in local development.
@@ -311,6 +481,17 @@ Use the `IsAllocated` property on an `EndpointReference` to check whether an end
Endpoint resolution happens during the startup sequence of the `DistributedApplication`. To safely access endpoint values (e.g., `Url`, `Host`, `Port`), you must wait until endpoints are allocated. Aspire provides eventing APIs, such as `AfterEndpointsAllocatedEvent`, to access endpoints after allocation. These APIs ensure code executes only when endpoints are ready.
+
+
+
```csharp title="Example — Checking Allocation and Using Eventing"
var builder = DistributedApplication.CreateBuilder(args);
@@ -346,6 +527,9 @@ builder.Eventing.Subscribe(
builder.Build().Run();
```
+
+
+
The preceding code will output different results depending on whether the application is running in **run mode** or **publish mode**:
**Run Mode**:
@@ -375,6 +559,13 @@ This section covers how to reference endpoints from other resources in Aspire, a
The `WithReference` API allows you to pass an endpoint reference directly to a target resource.
+
+
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -387,12 +578,120 @@ builder.AddProject("worker")
builder.Build().Run();
```
+
+
+
+```typescript title="apphost.mts"
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+const redis = await builder.addContainer('redis', 'redis');
+await redis.withEndpoint({ name: 'tcp', targetPort: 6379 });
+
+await builder
+ .addProject('worker', '../Worker/Worker.csproj')
+ .withReference(await redis.getEndpoint('tcp'));
+
+await builder.build().run();
+```
+
+
+
+
+```python title="apphost.py"
+from aspire_app import create_builder
+
+with create_builder() as builder:
+ redis = builder.add_container("redis", "redis")
+ redis.with_endpoint(name="tcp", target_port=6379)
+
+ worker = builder.add_project("worker", "../Worker/Worker.csproj")
+ worker.with_reference(redis.get_endpoint("tcp"))
+
+ builder.run()
+```
+
+
+
+
+```go title="apphost.go"
+package main
+
+import (
+ "log"
+
+ "apphost/modules/aspire"
+)
+
+func main() {
+ builder, err := aspire.CreateBuilder()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ redis := builder.
+ AddContainer("redis", "redis").
+ WithEndpoint(&aspire.WithEndpointOptions{
+ Name: aspire.StringPtr("tcp"),
+ TargetPort: aspire.Float64Ptr(6379),
+ })
+
+ worker := builder.
+ AddProject("worker", "../Worker/Worker.csproj").
+ WithReference(redis.GetEndpoint("tcp"))
+
+ if err := worker.Err(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ app, err := builder.Build()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+ if err := app.Run(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+}
+```
+
+
+
+
+```java title="AppHost.java"
+import aspire.*;
+
+void main(String[] args) throws Exception {
+ var builder = DistributedApplication.CreateBuilder(args);
+
+ var redis = builder.addContainer("redis", "redis");
+ redis.withEndpoint(new WithEndpointOptions()
+ .name("tcp")
+ .targetPort(6379));
+
+ builder.addProject("worker", "../Worker/Worker.csproj")
+ .withReference(redis.getEndpoint("tcp"));
+
+ builder.build().run();
+}
+```
+
+
+
+
`WithReference` is optimized for applications that use service discovery.
### Using `WithEnvironment`
The `WithEnvironment` API exposes endpoint details as environment variables, enabling runtime configuration.
+
+
+
```csharp title="Example — Passing Redis Endpoint as Environment Variable"
var builder = DistributedApplication.CreateBuilder(args);
@@ -405,6 +704,107 @@ builder.AddProject("worker")
builder.Build().Run();
```
+
+
+
+```typescript title="apphost.mts"
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+const redis = await builder.addContainer('redis', 'redis');
+await redis.withEndpoint({ name: 'tcp', targetPort: 6379 });
+
+await builder
+ .addProject('worker', '../Worker/Worker.csproj')
+ .withEnvironment('RedisUrl', await redis.getEndpoint('tcp'));
+
+await builder.build().run();
+```
+
+
+
+
+```python title="apphost.py"
+from aspire_app import create_builder
+
+with create_builder() as builder:
+ redis = builder.add_container("redis", "redis")
+ redis.with_endpoint(name="tcp", target_port=6379)
+
+ worker = builder.add_project("worker", "../Worker/Worker.csproj")
+ worker.with_env("RedisUrl", redis.get_endpoint("tcp"))
+
+ builder.run()
+```
+
+
+
+
+```go title="apphost.go"
+package main
+
+import (
+ "log"
+
+ "apphost/modules/aspire"
+)
+
+func main() {
+ builder, err := aspire.CreateBuilder()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ redis := builder.
+ AddContainer("redis", "redis").
+ WithEndpoint(&aspire.WithEndpointOptions{
+ Name: aspire.StringPtr("tcp"),
+ TargetPort: aspire.Float64Ptr(6379),
+ })
+
+ worker := builder.
+ AddProject("worker", "../Worker/Worker.csproj").
+ WithEnvironment("RedisUrl", redis.GetEndpoint("tcp"))
+
+ if err := worker.Err(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ app, err := builder.Build()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+ if err := app.Run(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+}
+```
+
+
+
+
+```java title="AppHost.java"
+import aspire.*;
+
+void main(String[] args) throws Exception {
+ var builder = DistributedApplication.CreateBuilder(args);
+
+ var redis = builder.addContainer("redis", "redis");
+ redis.withEndpoint(new WithEndpointOptions()
+ .name("tcp")
+ .targetPort(6379));
+
+ builder.addProject("worker", "../Worker/Worker.csproj")
+ .withEnvironment("RedisUrl", redis.getEndpoint("tcp"));
+
+ builder.build().run();
+}
+```
+
+
+
+
`WithEnvironment` gives full control over the configuration names injected into the target resource.
## `EndpointReferenceExpression` — Accessing Endpoint Parts
@@ -417,7 +817,11 @@ In C#, call `endpoint.Property(...)` to get that field. In TypeScript AppHosts,
| Only one part (e.g., host) | C#: `endpoint.Property(EndpointProperty.Host)`
TypeScript: `await endpoint.property(EndpointProperty.Host)` |
| Compose multiple parts into one setting | Build a `ReferenceExpression` (see dedicated section). |
-
+
```csharp title="AppHost.cs"
@@ -456,6 +860,66 @@ await builder
await builder.build().run();
```
+
+
+
+```python title="apphost.py"
+from aspire_app import create_builder
+
+with create_builder() as builder:
+ redis = builder.add_container("redis", "redis")
+ redis.with_endpoint(name="tcp", target_port=6379)
+
+ endpoint = redis.get_endpoint("tcp")
+ redis_host = endpoint.property("Host")
+ redis_port = endpoint.property("Port")
+
+ worker = builder.add_project("worker", "../Worker/Worker.csproj")
+ worker.with_env("REDIS_HOST", redis_host)
+ worker.with_env("REDIS_PORT", redis_port)
+
+ builder.run()
+```
+
+
+
+
+```go title="apphost.go"
+redis := builder.
+ AddContainer("redis", "redis").
+ WithEndpoint(&aspire.WithEndpointOptions{
+ Name: aspire.StringPtr("tcp"),
+ TargetPort: aspire.Float64Ptr(6379),
+ })
+
+endpoint := redis.GetEndpoint("tcp")
+redisHost := endpoint.Property(aspire.EndpointPropertyHost)
+redisPort := endpoint.Property(aspire.EndpointPropertyPort)
+
+worker := builder.
+ AddProject("worker", "../Worker/Worker.csproj").
+ WithEnvironment("REDIS_HOST", redisHost).
+ WithEnvironment("REDIS_PORT", redisPort)
+```
+
+
+
+
+```java title="AppHost.java"
+var redis = builder.addContainer("redis", "redis");
+redis.withEndpoint(new WithEndpointOptions()
+ .name("tcp")
+ .targetPort(6379));
+
+var endpoint = redis.getEndpoint("tcp");
+var redisHost = endpoint.property(EndpointProperty.HOST);
+var redisPort = endpoint.property(EndpointProperty.PORT);
+
+builder.addProject("worker", "../Worker/Worker.csproj")
+ .withEnvironment("REDIS_HOST", redisHost)
+ .withEnvironment("REDIS_PORT", redisPort);
+```
+
@@ -521,6 +985,17 @@ Starting with Aspire 13.2, you can explicitly control endpoint resolution contex
Use the `Caller` property to resolve an endpoint from the perspective of a specific calling resource:
+
+
+
```csharp title="Resolve endpoint from a resource's perspective"
var builder = DistributedApplication.CreateBuilder(args);
@@ -539,12 +1014,26 @@ var url = await endpoint.GetValueAsync(new ValueProviderContext {
// e.g., "cache:6379" using the resource name as the hostname
```
+
+
+
This is particularly useful when you need to pass connection information between resources that may be running in different contexts (containers vs. host processes).
#### Resolve from a specific network
Use the `Network` property to resolve an endpoint from the perspective of a specific network:
+
+
+
```csharp title="Resolve endpoint from a network's perspective"
var builder = DistributedApplication.CreateBuilder(args);
@@ -562,6 +1051,9 @@ var url = await endpoint.GetValueAsync(new ValueProviderContext {
// e.g., "cache:6379" using the resource name as the hostname
```
+
+
+
The `KnownNetworkIdentifiers` class provides predefined network identifiers:
- `LocalhostNetwork`: Resolves to localhost-based URLs
@@ -581,6 +1073,17 @@ The following code demonstrates how to set environment variables for a project t
With Aspire 13.2, you can simplify this using `ValueProviderContext`:
+
+
+
```csharp title="Simplified with ValueProviderContext (Aspire 13.2+)"
var builder = DistributedApplication.CreateBuilder(args);
@@ -615,8 +1118,22 @@ var api = builder.AddProject("api")
builder.Build().Run();
```
+
+
+
**Before Aspire 13.2**, you would need to manually construct the URL:
+
+
+
```csharp title="Manual URL construction (pre-13.2)"
var builder = DistributedApplication.CreateBuilder(args);
@@ -651,3 +1168,6 @@ var api = builder.AddProject("api")
builder.Build().Run();
```
+
+
+
diff --git a/src/frontend/src/content/docs/architecture/resource-model.mdx b/src/frontend/src/content/docs/architecture/resource-model.mdx
index de54cdd4a..ac82aaa11 100644
--- a/src/frontend/src/content/docs/architecture/resource-model.mdx
+++ b/src/frontend/src/content/docs/architecture/resource-model.mdx
@@ -5,6 +5,7 @@ description: "Learn how Aspire's resource model represents distributed apps as a
---
import { Aside, Steps } from '@astrojs/starlight/components';
+import AppHostTabs from '@components/AppHostTabs.astro';
import LearnMore from '@components/LearnMore.astro';
Aspire's AppHost represents a collection of resources, known as the "resource model". This model allows developers to define and manage the various components and services that make up their applications, providing a unified way to interact with these resources throughout the development lifecycle.
@@ -15,6 +16,17 @@ The resource model is a **directed acyclic graph (
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -25,6 +37,9 @@ var web = builder.AddNpmApp("web", "../web").WithReference(api);
builder.Build().Run();
```
+
+
+
The preceding `AppHost` code defines an architecture with three resources:
```mermaid
@@ -51,7 +66,7 @@ architecture-beta
-Prefer writing your AppHost in TypeScript? Start with [Build your first Aspire app](/get-started/first-app/?lang=typescript).
+Prefer writing your AppHost in TypeScript? Start with [Build your first Aspire app](/get-started/first-app/?aspire-lang=typescript).
## Resource basics
@@ -103,6 +118,17 @@ This pattern improves the developer experience by:
To continue from the previous example, here's how you can add resources and wire them together in the `AppHost`:
+
+
+
```csharp '.WithReference' title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -113,6 +139,9 @@ var web = builder.AddNpmApp("web", "../web").WithReference(api);
builder.Build().Run();
```
+
+
+
The preceding example:
- A PostgreSQL server (`pg`) is created and configured with a database named `appdata`.
diff --git a/src/frontend/src/content/docs/dashboard/enable-browser-telemetry/blazor-webassembly.mdx b/src/frontend/src/content/docs/dashboard/enable-browser-telemetry/blazor-webassembly.mdx
index d19444be1..d49f449d0 100644
--- a/src/frontend/src/content/docs/dashboard/enable-browser-telemetry/blazor-webassembly.mdx
+++ b/src/frontend/src/content/docs/dashboard/enable-browser-telemetry/blazor-webassembly.mdx
@@ -5,8 +5,6 @@ description: Enable browser telemetry for Blazor WebAssembly apps with Aspire's
import AppHostTabs from '@components/AppHostTabs.astro';
-
-
Blazor WebAssembly apps run .NET in the browser, so they need a startup path that makes telemetry configuration available before the WebAssembly runtime starts. Starting with Aspire 13.4, you can use the Aspire Blazor hosting integration instead of configuring a Blazor WebAssembly app like a generic JavaScript browser app that sends OTLP directly to the dashboard.
The Blazor hosting integration configures same-origin gateway or host routes for service calls and OTLP telemetry. The browser sends telemetry to a relative `/_otlp` path, or the app-prefixed equivalent such as `/app/_otlp`, and the gateway or host forwards it to the Aspire dashboard. This avoids CORS setup for the dashboard and keeps dashboard OTLP headers on the server-side proxy instead of putting them in browser-visible configuration.
@@ -15,7 +13,12 @@ The Blazor hosting integration configures same-origin gateway or host routes for
For a standalone Blazor WebAssembly project, add the WASM project resource, reference any backend services from it, and bind it to a Blazor Gateway. Configure the gateway with HTTP/protobuf OTLP export because browser-based clients can't use OTLP/gRPC.
-
+
```csharp title="AppHost.cs"
@@ -72,6 +75,17 @@ The gateway exposes browser-safe configuration under the app path, such as `/app
For a Blazor Web App that hosts an Interactive WebAssembly client, configure the host project to proxy API calls and telemetry for the WebAssembly client:
+
+
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -84,9 +98,8 @@ builder.AddProject("blazorapp")
builder.Build().Run();
```
-:::note
-`ProxyBlazorService` and `ProxyBlazorTelemetry` are C# AppHost APIs in Aspire 13.4. They aren't exported to TypeScript AppHosts yet.
-:::
+
+
The AppHost emits YARP reverse proxy configuration and a Blazor client configuration response for the host project. In the host app, load the reverse proxy configuration and map the proxy routes:
diff --git a/src/frontend/src/content/docs/dashboard/telemetry-after-deployment.mdx b/src/frontend/src/content/docs/dashboard/telemetry-after-deployment.mdx
index feb03e090..ba11e4b7d 100644
--- a/src/frontend/src/content/docs/dashboard/telemetry-after-deployment.mdx
+++ b/src/frontend/src/content/docs/dashboard/telemetry-after-deployment.mdx
@@ -4,6 +4,7 @@ description: Understand how telemetry and the Aspire dashboard work after you de
---
import { Aside, Steps } from '@astrojs/starlight/components';
+import AppHostTabs from '@components/AppHostTabs.astro';
import LearnMore from '@components/LearnMore.astro';
The Aspire dashboard is designed for local development and short-term diagnostics. It stores telemetry in memory, which means telemetry is lost when the dashboard restarts and there are built-in limits on how much data it retains. After deploying your app to a production environment, you need to configure a persistent telemetry backend.
@@ -52,6 +53,40 @@ For more information about how Aspire configures OpenTelemetry, see [Aspire tele
Add the Application Insights resource to your AppHost project:
+
+
+
+```typescript title="apphost.mts" twoslash
+import { createBuilder } from './.aspire/modules/aspire.mjs';
+
+const builder = await createBuilder();
+
+const insights = await builder.addAzureApplicationInsights('app-insights');
+
+const apiService = await builder.addProject(
+ 'apiservice',
+ '../MyApp.ApiService/MyApp.ApiService.csproj'
+);
+await apiService.withReference(insights);
+
+const webFrontend = await builder.addProject(
+ 'webfrontend',
+ '../MyApp.Web/MyApp.Web.csproj'
+);
+await webFrontend.withReference(insights);
+await webFrontend.withExternalHttpEndpoints();
+
+await builder.build().run();
+```
+
+
+
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -67,6 +102,9 @@ builder.AddProject("webfrontend")
builder.Build().Run();
```
+
+
+
When you reference Application Insights, Aspire automatically configures the `APPLICATIONINSIGHTS_CONNECTION_STRING` environment variable for each service. The `Azure.Monitor.OpenTelemetry.AspNetCore` package uses the `UseAzureMonitor()` method to read this variable and send telemetry to Application Insights.
diff --git a/src/frontend/src/content/docs/deployment/app-lifecycle.mdx b/src/frontend/src/content/docs/deployment/app-lifecycle.mdx
index c51186b4b..7a2ad4dbe 100644
--- a/src/frontend/src/content/docs/deployment/app-lifecycle.mdx
+++ b/src/frontend/src/content/docs/deployment/app-lifecycle.mdx
@@ -37,12 +37,17 @@ Each phase uses the same `AppHost` configuration, but each phase answers a diffe
### Example application
-The following example uses a C# AppHost, but the same workflow shape applies to TypeScript AppHosts.
+The workflow shape applies to every AppHost language. The complete Docker Compose example is currently available for C# and TypeScript because the daily Python, Go, Java, and Rust generated SDKs don't expose the required Docker Compose and SQL Server integration APIs.
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="AppHost.cs"
@@ -436,9 +441,9 @@ After the workflow completes, you have everything needed for production deployme
| **Release** | CI/CD workflow (i.e. GitHub Actions) | Publish to staging/ production | Cloud/Server | Container | Container |
- This example uses a C# AppHost, but the same workflow shape applies to
- TypeScript AppHosts. Replace `.csproj` references with your `apphost.mts` path
- as needed.
+ The lifecycle commands and CI/CD phases apply to every AppHost language. Use
+ the AppHost file and runtime setup for your selected language; the deployment
+ target must also be available in that language's generated SDK.
The AppHost is the **single source of truth** for your application architecture. Each phase above uses the exact same AppHost configuration. This eliminates configuration drift between development and deployment. It defines things your distributed application needs like:
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 25d777ac9..f5f26e73b 100644
--- a/src/frontend/src/content/docs/deployment/azure/app-service.mdx
+++ b/src/frontend/src/content/docs/deployment/azure/app-service.mdx
@@ -43,7 +43,12 @@ The Aspire CLI adds the [📦 Aspire.Hosting.Azure.AppService](https://www.nuget
Then add the App Service environment in your AppHost and a supported web app or API resource. App Service environments automatically target supported project resources and Dockerfile-backed web containers:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -108,7 +113,12 @@ Azure App Service redirects HTTP traffic to HTTPS at the platform level. To matc
This behavior is enabled by default. If you intentionally need generated endpoint URLs and connection strings to preserve `http://`, disable the upgrade on the App Service environment:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -175,7 +185,12 @@ App Service exposes one health check path per website. If you configure multiple
Use `PublishAsAzureAppServiceWebsite` when you want to customize the generated website or deployment slot. It isn't required for a standard App Service deployment.
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -224,7 +239,12 @@ await builder.build().run();
When you use deployment slots, production website customizations and slot customizations are configured separately.
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -274,7 +294,12 @@ await builder.build().run();
Azure App Service application settings accept only letters, numbers, and underscores. If a connection name contains dashes and you intentionally want to bypass App Service name validation for that website, call `SkipEnvironmentVariableNameChecks()` after `PublishAsAzureAppServiceWebsite`.
-
+
```csharp title="AppHost.cs"
builder.AddProject("api")
@@ -320,7 +345,12 @@ For Azure authentication, shared Azure settings, and `Parameters__*` inputs, see
The Azure App Service environment includes the Aspire Dashboard by default, so you can inspect logs, traces, metrics, and topology for the deployed compute resources after deployment. Managed Azure backing services aren't shown as dashboard resources there. If you don't want to deploy it, disable it on the environment:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -347,7 +377,12 @@ await appServiceEnv.withDashboard(false);
Enable Azure Application Insights on the App Service environment when you want Aspire to provision monitoring resources and flow the connection string into your deployed websites:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -380,7 +415,12 @@ await appServiceEnv.withAzureApplicationInsights();
Use deployment slots when you want to deploy the websites in your environment into a staging slot before swapping into production:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
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 0bf4d6107..c115a77f7 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
@@ -41,7 +41,12 @@ This page does not duplicate `azd`'s full operational workflow. For install, ini
The `aspire deploy` path and `azd` use different resource naming schemes by default. If you're upgrading from an existing `azd` deployment to `aspire deploy`, use `WithAzdResourceNaming()` to preserve the original naming convention. This avoids creating duplicate Azure resources:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
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 0bedb1082..b5b0731ab 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
@@ -45,7 +45,12 @@ If you're staying on the default public model, focus on identities, secrets, RBA
For Azure Container Apps, a common production pattern is to place the environment in a delegated subnet and put private endpoints for backing Azure services in a separate subnet:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -111,7 +116,12 @@ For service-specific requirements such as Service Bus Premium tier support and A
Store sensitive configuration data and secrets in Azure Key Vault instead of source control or container images:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -145,7 +155,12 @@ For more information, see [Aspire Azure Key Vault integration](/integrations/clo
For more granular control over permissions and role assignments, attach a user-assigned managed identity to the compute resource that needs it:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -179,7 +194,12 @@ For detailed guidance, see [Aspire Azure user-assigned managed identity integrat
Use the deployed Aspire Dashboard to inspect the compute resources that Aspire places in Azure Container Apps or Azure App Service, then use Application Insights and Azure Monitor alerts to detect suspicious behavior, repeated failures, or unusual traffic patterns after deployment:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
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 820f4a959..23377ac39 100644
--- a/src/frontend/src/content/docs/deployment/azure/container-apps.mdx
+++ b/src/frontend/src/content/docs/deployment/azure/container-apps.mdx
@@ -44,7 +44,12 @@ Learn more about the `aspire add` command in the [reference docs](/reference/cli
Then add the Azure Container Apps environment in your AppHost and a web app you want to place in that environment:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -120,7 +125,12 @@ Azure Container Apps uses an Envoy-based HTTP edge proxy for HTTP ingress:
To match the deployed serving scheme, Aspire upgrades external HTTP endpoints in an Azure Container Apps environment to HTTPS when it generates endpoint URLs and service discovery connection strings for dependent resources. If you intentionally need generated endpoint URLs and connection strings to preserve `http://`, disable the upgrade on the environment:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -197,7 +207,12 @@ Probe traffic stays on the internal container network. If a probe is associated
Use `PublishAsAzureContainerApp` when you want to customize the generated Container App resource for a project, container, or executable. It isn't required for a standard Container Apps deployment.
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -236,7 +251,12 @@ await builder.build().run();
Use `ConfigureCustomDomain` when you want Aspire to configure a custom domain and managed certificate on the generated Container App.
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -295,6 +315,52 @@ const api = await builder.addProject("api", "../Api/Api.csproj", "http");
await api.withExternalHttpEndpoints();
```
+
+```python title="apphost.py"
+from aspire_app import create_builder
+
+with create_builder() as builder:
+ api = builder.add_project("api", "../Api/Api.csproj", launch_profile_or_options="http")
+ api.with_external_http_endpoints()
+ builder.run()
+```
+
+
+```go title="apphost.go"
+api := builder.AddProject("api", "../Api/Api.csproj", &aspire.AddProjectOptions{
+ LaunchProfileOrOptions: "http",
+})
+api.WithExternalHttpEndpoints()
+if err := api.Err(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+}
+```
+
+
+```java title="AppHost.java"
+import aspire.*;
+
+void main() throws Exception {
+ var builder = DistributedApplication.CreateBuilder();
+ var api = builder.addProject("api", "../Api/Api.csproj", "http");
+ api.withExternalHttpEndpoints();
+ builder.build().run();
+}
+```
+
+
+```rust title="apphost.rs"
+let builder = create_builder(None)?;
+let api = builder.add_project(
+ "api",
+ "../Api/Api.csproj",
+ Some(serde_json::json!("http")),
+)?;
+api.with_external_http_endpoints()?;
+let app = builder.build()?;
+app.run(None)?;
+```
+
## Optional environment settings
@@ -303,7 +369,12 @@ await api.withExternalHttpEndpoints();
By default, Aspire provisions an Azure Container Registry for the environment. If you want to use a shared or explicitly named registry instead, add an Azure Container Registry resource and attach it to the environment:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -335,7 +406,12 @@ For more information, see [Azure Container Registry integration](/integrations/c
The Azure Container Apps environment includes the Aspire Dashboard by default. It helps you inspect the compute resources deployed into that environment. Managed Azure backing services aren't shown as dashboard resources there. If you don't want to deploy it, disable it on the environment:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
diff --git a/src/frontend/src/content/docs/deployment/custom-deployments.mdx b/src/frontend/src/content/docs/deployment/custom-deployments.mdx
index 992c321ff..00cd5ca1d 100644
--- a/src/frontend/src/content/docs/deployment/custom-deployments.mdx
+++ b/src/frontend/src/content/docs/deployment/custom-deployments.mdx
@@ -138,7 +138,12 @@ The preceding code:
In your AppHost, you can add the `ComputeEnvironmentResource` to the application model like this:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
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 3ba656c08..df9f9c44a 100644
--- a/src/frontend/src/content/docs/deployment/deploy-with-aspire.mdx
+++ b/src/frontend/src/content/docs/deployment/deploy-with-aspire.mdx
@@ -16,7 +16,12 @@ Deployment behavior doesn't live outside the AppHost. It comes from resources in
### Resources add pipeline steps
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -92,7 +97,12 @@ Parameters are the external values the pipeline needs from outside the AppHost,
Parameters connect AppHost code to pipeline behavior. In the publish path, the target preserves that requirement in emitted artifacts so another tool or manual step can provide the value later. In the deploy path, Aspire resolves the value internally while it generates and applies the deployment.
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
diff --git a/src/frontend/src/content/docs/deployment/docker-compose.mdx b/src/frontend/src/content/docs/deployment/docker-compose.mdx
index 400028b55..515c126d7 100644
--- a/src/frontend/src/content/docs/deployment/docker-compose.mdx
+++ b/src/frontend/src/content/docs/deployment/docker-compose.mdx
@@ -61,7 +61,12 @@ When Podman is the active runtime, Aspire uses `podman-compose` (or the Docker C
To deploy with Docker Compose, add a Docker Compose environment resource to your AppHost using `AddDockerComposeEnvironment`:
-
+
```csharp title="AppHost.cs" {3}
var builder = DistributedApplication.CreateBuilder(args);
@@ -227,7 +232,12 @@ Docker Compose deployments can build images from Dockerfiles that are generated
[`ASPIREDOCKERFILEBUILDER001`](/diagnostics/aspiredockerfilebuilder001/) when you choose to use them.
-
+
```csharp title="AppHost.cs"
using Aspire.Hosting.ApplicationModel.Docker;
@@ -305,7 +315,12 @@ The Docker hosting integration captures environment variables from your app mode
For advanced scenarios, use `ConfigureEnvFile` to customize the generated `.env` file:
-
+
```csharp title="AppHost.cs"
using Aspire.Hosting.Docker;
@@ -350,7 +365,12 @@ This is useful when you need to add custom environment variables to the generate
Use `ConfigureComposeFile` to customize the generated `docker-compose.yml` model before Aspire writes it to disk:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -394,7 +414,12 @@ await builder.build().run();
To customize the generated Docker Compose service for a specific resource, use the `PublishAsDockerComposeService` method. This is optional — all resources are automatically included in the Docker Compose output. Use this method only when you need to modify the generated service definition:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -445,7 +470,12 @@ The `configure` callback receives the `DockerComposeServiceResource` and the gen
Use `GetHostAddressExpression` when you need the host name that another Docker Compose service should use for an endpoint. In Docker Compose deployments, this expression resolves to the generated service name on the Compose network.
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -498,6 +528,90 @@ const builder = await createBuilder();
const container = await builder.addContainer('mycontainer', { image: 'myimage', tag: 'latest' });
await container.withImagePullPolicy(ImagePullPolicy.Always);
```
+
+
+
+```python title="apphost.py"
+from aspire_app import create_builder
+
+with create_builder() as builder:
+ container = builder.add_container("mycontainer", "myimage:latest")
+ container.with_image_pull_policy("Always")
+ builder.run()
+```
+
+
+
+
+```go title="apphost.go"
+package main
+
+import (
+ "log"
+
+ "apphost/modules/aspire"
+)
+
+func main() {
+ builder, err := aspire.CreateBuilder()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ container := builder.AddContainer("mycontainer", "myimage:latest").
+ WithImagePullPolicy(aspire.ImagePullPolicyAlways)
+ if err := container.Err(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ app, err := builder.Build()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+ if err := app.Run(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+}
+```
+
+
+
+
+```java title="AppHost.java"
+import aspire.*;
+
+void main(String[] args) throws Exception {
+ var builder = DistributedApplication.CreateBuilder(args);
+
+ builder.addContainer("mycontainer", "myimage:latest")
+ .withImagePullPolicy(ImagePullPolicy.ALWAYS);
+
+ builder.build().run();
+}
+```
+
+
+
+
+```rust title="apphost.rs"
+#[path = ".aspire/modules/mod.rs"]
+mod aspire;
+
+use aspire::*;
+
+fn main() -> Result<(), Box> {
+ let builder = create_builder(None)?;
+
+ let container =
+ builder.add_container("mycontainer", serde_json::json!("myimage:latest"))?;
+ container.with_image_pull_policy(ImagePullPolicy::Always)?;
+
+ let app = builder.build()?;
+ app.run(None)?;
+ Ok(())
+}
+```
+
@@ -524,7 +638,12 @@ When deploying containers, you can customize how container images are named, tag
Use `WithRemoteImageName` and `WithRemoteImageTag` to customize the image reference:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -560,7 +679,12 @@ await api.withRemoteImageTag('v1.0.0');
For more complex scenarios, use `WithImagePushOptions` to register a callback that dynamically configures push options:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -610,7 +734,12 @@ Multiple callbacks can be registered on the same resource, and they are invoked
Use the `AddContainerRegistry` method to define a container registry and `WithContainerRegistry` to associate resources with it:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
@@ -662,7 +791,12 @@ await api.withContainerRegistry(registry);
For more flexible configuration in CI/CD pipelines, use parameters with environment variables:
-
+
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
diff --git a/src/frontend/src/content/docs/deployment/environments.mdx b/src/frontend/src/content/docs/deployment/environments.mdx
index 0d8c4764e..60e369b8f 100644
--- a/src/frontend/src/content/docs/deployment/environments.mdx
+++ b/src/frontend/src/content/docs/deployment/environments.mdx
@@ -107,6 +107,120 @@ if (await env.isEnvironment('Testing')) {
}
```
+
+
+
+```python title="apphost.py"
+from aspire_app import create_builder
+
+with create_builder() as builder:
+ env = builder.env
+
+ if env.is_development():
+ # Development-specific configuration
+ pass
+
+ # The daily Python SDK exposes Development, Staging, and Production checks,
+ # but not the custom is_environment(name) helper.
+ builder.run()
+```
+
+The daily Python generated SDK exposes `is_development()`, `is_staging()`, and `is_production()`, but it doesn't currently expose the custom `is_environment(name)` check.
+
+
+
+
+```go title="apphost.go"
+package main
+
+import (
+ "log"
+
+ "apphost/modules/aspire"
+)
+
+func main() {
+ builder, err := aspire.CreateBuilder()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ env := builder.Environment()
+ isDevelopment, err := env.IsDevelopment()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+ if isDevelopment {
+ // Development-specific configuration
+ }
+
+ isTesting, err := env.IsEnvironment("Testing")
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+ if isTesting {
+ // Testing-specific configuration
+ }
+
+ app, err := builder.Build()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+ if err := app.Run(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+}
+```
+
+
+
+
+```java title="AppHost.java"
+import aspire.*;
+
+void main(String[] args) throws Exception {
+ var builder = DistributedApplication.CreateBuilder(args);
+ var env = builder.environment();
+
+ if (env.isDevelopment()) {
+ // Development-specific configuration
+ }
+
+ if (env.isEnvironment("Testing")) {
+ // Testing-specific configuration
+ }
+
+ builder.build().run();
+}
+```
+
+
+
+
+```rust title="apphost.rs"
+#[path = ".aspire/modules/mod.rs"]
+mod aspire;
+
+use aspire::*;
+
+fn main() -> Result<(), Box> {
+ let builder = create_builder(None)?;
+ let env = builder.environment()?;
+
+ if env.is_development()? {
+ // Development-specific configuration
+ }
+
+ if env.is_environment("Testing")? {
+ // Testing-specific configuration
+ }
+
+ let app = builder.build()?;
+ app.run(None)?;
+ Ok(())
+}
+```
+
@@ -119,11 +233,13 @@ The following convenience methods are available:
| `IsProduction()` / `isProduction()` | `Production` |
| `IsEnvironment(name)` / `isEnvironment(name)` | Any custom name |
+Go, Java, and Rust expose all four checks using their generated naming conventions. Python currently exposes only the three named-environment checks.
+
## Common patterns
### Set environment variables on child resources
-Your services often need to know which environment they're running in. Different frameworks use different environment variables — use `WithEnvironment` to set the appropriate one for each service:
+Your services often need to know which environment they're running in. Different frameworks use different environment variables — use the generated environment-variable method (`WithEnvironment`, `withEnvironment`, `with_env`, or `with_environment`) to set the appropriate one for each service:
@@ -181,6 +297,154 @@ await worker.withEnvironment('APP_ENV', appEnvironment);
await builder.build().run();
```
+
+
+
+```python title="apphost.py"
+from aspire_app import create_builder
+
+with create_builder() as builder:
+ is_development = builder.env.is_development()
+ dotnet_environment = "Development" if is_development else "Production"
+ app_environment = "development" if is_development else "production"
+
+ api = builder.add_project("api", "../Api/Api.csproj")
+ api.with_env("DOTNET_ENVIRONMENT", dotnet_environment)
+
+ frontend = builder.add_container("frontend", "node:22-alpine")
+ frontend.with_env("NODE_ENV", app_environment)
+
+ worker = builder.add_container("worker", "myorg/worker:latest")
+ worker.with_env("APP_ENV", app_environment)
+
+ builder.run()
+```
+
+
+
+
+```go title="apphost.go"
+package main
+
+import (
+ "log"
+
+ "apphost/modules/aspire"
+)
+
+func main() {
+ builder, err := aspire.CreateBuilder()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ isDevelopment, err := builder.Environment().IsDevelopment()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+ dotnetEnvironment := "Production"
+ appEnvironment := "production"
+ if isDevelopment {
+ dotnetEnvironment = "Development"
+ appEnvironment = "development"
+ }
+
+ api := builder.AddProject("api", "../Api/Api.csproj").
+ WithEnvironment("DOTNET_ENVIRONMENT", dotnetEnvironment)
+ if err := api.Err(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ frontend := builder.AddContainer("frontend", "node:22-alpine").
+ WithEnvironment("NODE_ENV", appEnvironment)
+ if err := frontend.Err(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ worker := builder.AddContainer("worker", "myorg/worker:latest").
+ WithEnvironment("APP_ENV", appEnvironment)
+ if err := worker.Err(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ app, err := builder.Build()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+ if err := app.Run(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+}
+```
+
+
+
+
+```java title="AppHost.java"
+import aspire.*;
+
+void main(String[] args) throws Exception {
+ var builder = DistributedApplication.CreateBuilder(args);
+ var isDevelopment = builder.environment().isDevelopment();
+ var dotnetEnvironment = isDevelopment ? "Development" : "Production";
+ var appEnvironment = isDevelopment ? "development" : "production";
+
+ builder.addProject("api", "../Api/Api.csproj")
+ .withEnvironment("DOTNET_ENVIRONMENT", dotnetEnvironment);
+
+ builder.addContainer("frontend", "node:22-alpine")
+ .withEnvironment("NODE_ENV", appEnvironment);
+
+ builder.addContainer("worker", "myorg/worker:latest")
+ .withEnvironment("APP_ENV", appEnvironment);
+
+ builder.build().run();
+}
+```
+
+
+
+
+```rust title="apphost.rs"
+#[path = ".aspire/modules/mod.rs"]
+mod aspire;
+
+use aspire::*;
+
+fn main() -> Result<(), Box> {
+ let builder = create_builder(None)?;
+ let is_development = builder.environment()?.is_development()?;
+ let dotnet_environment = if is_development {
+ "Development"
+ } else {
+ "Production"
+ };
+ let app_environment = if is_development {
+ "development"
+ } else {
+ "production"
+ };
+
+ let api = builder.add_project("api", "../Api/Api.csproj", None)?;
+ api.with_environment(
+ "DOTNET_ENVIRONMENT",
+ serde_json::json!(dotnet_environment),
+ )?;
+
+ let frontend =
+ builder.add_container("frontend", serde_json::json!("node:22-alpine"))?;
+ frontend.with_environment("NODE_ENV", serde_json::json!(app_environment))?;
+
+ let worker =
+ builder.add_container("worker", serde_json::json!("myorg/worker:latest"))?;
+ worker.with_environment("APP_ENV", serde_json::json!(app_environment))?;
+
+ let app = builder.build()?;
+ app.run(None)?;
+ Ok(())
+}
+```
+
@@ -199,10 +463,11 @@ Common environment variable conventions by framework and ecosystem:
| Java (Spring) | `SPRING_PROFILES_ACTIVE` | `dev`, `test`, `prod` |
- `AddNodeApp` automatically sets `NODE_ENV` to `development` or `production`
- based on whether the AppHost environment is `Development`. If you need a
- different value (such as `staging`), override it with an explicit
- `WithEnvironment("NODE_ENV", ...)` call as shown above.
+ In C# and TypeScript AppHosts, the Node.js resource automatically sets
+ `NODE_ENV` to `development` or `production` based on whether the AppHost
+ environment is `Development`. Override it explicitly when you need another
+ value. The daily Python, Go, Java, and Rust SDKs don't expose the Node.js
+ resource, so their examples set `NODE_ENV` on a container.
### Use parameters for per-environment values
@@ -239,6 +504,110 @@ await api.withEnvironment('API_KEY', apiKey);
await builder.build().run();
```
+
+
+
+```python title="apphost.py"
+from aspire_app import create_builder
+
+with create_builder() as builder:
+ api_key = builder.add_parameter("apiKey", secret=True)
+
+ api = builder.add_project("api", "../Api/Api.csproj")
+ api.with_env("API_KEY", api_key)
+
+ builder.run()
+```
+
+
+
+
+```go title="apphost.go"
+package main
+
+import (
+ "log"
+
+ "apphost/modules/aspire"
+)
+
+func main() {
+ builder, err := aspire.CreateBuilder()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ secret := true
+ apiKey := builder.AddParameter("apiKey", &aspire.AddParameterOptions{
+ Secret: &secret,
+ })
+ if err := apiKey.Err(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ api := builder.AddProject("api", "../Api/Api.csproj").
+ WithEnvironment("API_KEY", apiKey)
+ if err := api.Err(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+
+ app, err := builder.Build()
+ if err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+ if err := app.Run(); err != nil {
+ log.Fatal(aspire.FormatError(err))
+ }
+}
+```
+
+
+
+
+```java title="AppHost.java"
+import aspire.*;
+
+void main(String[] args) throws Exception {
+ var builder = DistributedApplication.CreateBuilder(args);
+ var apiKey = builder.addParameter(
+ "apiKey",
+ new AddParameterOptions().secret(true)
+ );
+
+ builder.addProject("api", "../Api/Api.csproj")
+ .withEnvironment("API_KEY", apiKey);
+
+ builder.build().run();
+}
+```
+
+
+
+
+```rust title="apphost.rs"
+#[path = ".aspire/modules/mod.rs"]
+mod aspire;
+
+use aspire::*;
+
+fn main() -> Result<(), Box> {
+ let builder = create_builder(None)?;
+ let api_key = builder.add_parameter(
+ "apiKey",
+ None,
+ None,
+ Some(true),
+ )?;
+
+ let api = builder.add_project("api", "../Api/Api.csproj", None)?;
+ api.with_environment("API_KEY", serialize_handle(&api_key))?;
+
+ let app = builder.build()?;
+ app.run(None)?;
+ Ok(())
+}
+```
+
@@ -305,7 +674,12 @@ Environment variables take the highest priority, so they override any values fro
Use the [execution context](#environment-vs-execution-context) for choices that differ between local orchestration and publish/deploy workflows. For example, use a local Redis container in run mode and an Azure Managed Redis resource when publishing:
-
+
```csharp title="AppHost.cs"
diff --git a/src/frontend/src/content/docs/deployment/javascript-apps.mdx b/src/frontend/src/content/docs/deployment/javascript-apps.mdx
index 28e3ff879..33bb498a3 100644
--- a/src/frontend/src/content/docs/deployment/javascript-apps.mdx
+++ b/src/frontend/src/content/docs/deployment/javascript-apps.mdx
@@ -61,7 +61,12 @@ flowchart LR
App --> Frontend["Vite build output"]
```
-
+
```csharp title="AppHost.cs"
@@ -139,7 +144,12 @@ flowchart LR
YARP --> Frontend["Frontend routes"]
```
-
+
```csharp title="AppHost.cs"
@@ -201,7 +211,12 @@ await builder.build().run();
If your gateway or BFF needs to know about the frontend dev server during local development, gate that wiring to run mode only:
-
+
```csharp title="AppHost.cs"
@@ -274,7 +289,12 @@ Use `PublishAsStaticWebsite` when the framework produces static files and you wa
Choose this shape instead of `PublishWithStaticFiles(...)` when you do not already have a gateway or BFF resource that should own the public route table. If you already have an explicit YARP resource for gateway or BFF behavior, keep using `PublishWithStaticFiles(...)` on that resource.
-
+
```csharp title="AppHost.cs"
@@ -365,7 +385,12 @@ Use `PublishAsNodeServer` for frameworks that produce a self-contained Node.js s
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.
-
+
```csharp title="AppHost.cs"
@@ -426,7 +451,12 @@ Use `PublishAsPackageScript` for SSR frameworks that start production by running
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.
-
+
```csharp title="AppHost.cs"
@@ -490,6 +520,14 @@ The generated container sets `HOST=0.0.0.0` and `HOSTNAME=0.0.0.0` so the server
- **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