con un clic
aspireify
// One-time skill for completing Aspire initialization in an existing app after `aspire init` has dropped the skeleton AppHost. Use this skill when an `aspire.config.json` exists but the AppHost has not yet been wired up.
// One-time skill for completing Aspire initialization in an existing app after `aspire init` has dropped the skeleton AppHost. Use this skill when an `aspire.config.json` exists but the AppHost has not yet been wired up.
**WORKFLOW SKILL** — Deploy Aspire apps from AppHost models to Docker Compose, Kubernetes, Azure, or AWS. WHEN: "deploy Aspire app", "publish Aspire artifacts", "deploy to Azure Container Apps", "generate Kubernetes artifacts", "tear down Aspire deployment". INVOKES: aspire CLI, Aspire docs, target cloud/container CLIs. FOR SINGLE OPERATIONS: use generic Azure, Kubernetes, Docker, or AWS tools only when no Aspire AppHost exists.
Use when working with an Aspire distributed application: operate an AppHost or resources through the Aspire CLI; start, stop, restart, or wait for resources; inspect app state, logs, traces, docs, or health; add integrations; manage secrets/config; publish/deploy or run pipeline steps; initialize an existing app; recover TypeScript `.aspire/modules`; find frontend URLs for Playwright; expose custom dashboard/resource commands; or understand Aspire AppHost APIs in C# or TypeScript. Use even if the user says AppHost, resources, dashboard, bootstrap, Playwright URL, or local distributed app workflow without naming Aspire. Do not use for non-Aspire .NET apps, container-only repos without an AppHost, or ordinary build/test tasks.
Guide for diagnosing GitHub Actions test failures, extracting failed tests from runs, and creating or updating failing-test issues. Use this when asked to investigate GitHub Actions test failures, download failure logs, create failing-test issues, or debug CI issues.
Guide for writing Aspire CLI end-to-end tests using Hex1b terminal automation. Use this when asked to create, modify, or debug CLI E2E tests.
Measures Aspire startup profiling with CLI self-profile capture and dashboard export traces.
Downloads and tests Aspire CLI from a PR build, preferably in the repo-local container runner under eng/scripts, verifies version, and runs test scenarios based on PR changes. Use this when asked to test a pull request.
| name | aspireify |
| description | One-time skill for completing Aspire initialization in an existing app after `aspire init` has dropped the skeleton AppHost. Use this skill when an `aspire.config.json` exists but the AppHost has not yet been wired up. |
This is a one-time setup skill. It completes the Aspire initialization that aspire init started. After this skill finishes successfully, the evergreen aspire skill handles ongoing AppHost work. Do not delete this skill unless the user explicitly asks.
Keep this as one skill with context-specific references. Load the reference files that match the repo you discover instead of trying to keep every edge case in the main document.
The default stance is adapt the AppHost to fit the app, not the other way around. The user's services already work — the goal is to model them in Aspire without breaking anything.
WithEnvironment() to match existing env var names over asking users to rename vars in their codeWithHttpsEndpoint(env: "PORT"), WithHttpEndpoint(env: "PORT"), or no explicit port when supported) over fixed portsdocker-compose.yml config 1:1 before optimizingSometimes a small code change unlocks significantly better Aspire integration. When this happens, present the tradeoff to the user and let them decide. Examples:
Connection strings: A service reads DATABASE_URL but Aspire injects ConnectionStrings__mydb. You can use WithEnvironment("DATABASE_URL", db.Resource.ConnectionStringExpression) (zero code change) or suggest the service reads from config so WithReference(db) just works (enables service discovery, health checks, auto-retry).
→ Ask: "Your API reads DATABASE_URL. I can map that with WithEnvironment (no code change) or you could switch to reading ConnectionStrings:mydb which unlocks WithReference and automatic service discovery. Which do you prefer?"
Port binding: A service hardcodes PORT=3000. You can preserve that with WithHttpsEndpoint(port: 3000) (zero code change) or switch the service to read PORT from env so Aspire can manage ports dynamically and avoid conflicts.
→ Ask: "Your frontend is currently fixed to port 3000. Unless that exact port is important for something external, I recommend switching it to read PORT from env so Aspire can manage the port and avoid conflicts. If you need 3000 to stay stable, I can preserve it. Which do you want?"
OTel setup: Service has its own tracing config pointing to Jaeger. You can leave it (Aspire won't show its traces) or suggest switching the exporter to read OTEL_EXPORTER_OTLP_ENDPOINT (which Aspire injects).
→ Ask: "Your API exports traces to Jaeger directly. I can leave that, or switch it to use the OTEL_EXPORTER_OTLP_ENDPOINT env var so traces show up in the Aspire dashboard. The Jaeger endpoint would still work in non-Aspire environments. Want me to update it?"
Format for presenting tradeoffs:
If you're unsure whether something is a service, whether two services depend on each other, whether a port is truly significant, or whether a Docker Compose service should be modeled — ask. Don't guess at architectural intent.
Do not assume APIs exist. Before writing any AppHost code, look up the correct API using aspire docs search and aspire docs get. Follow the tiered preference: Tier 1 (first-party Aspire.Hosting.*) → Tier 2 (community CommunityToolkit.Aspire.Hosting.*) → Tier 3 (raw AddExecutable/AddDockerfile/AddContainer). See the "Looking up APIs and integrations" section below for full discovery workflow, tier details, and auto-managed values.
Don't invent APIs — if docs search and integration list don't return it, it doesn't exist. Fall back to Tier 3. API shapes differ between C# and TypeScript — always check the correct language docs.
For JavaScript/TypeScript apps, pick the right resource type (AddViteApp, AddNodeApp, or AddJavaScriptApp) and configure dev scripts and port binding. See references/javascript-apps.md for the selection table, dev script patterns, framework-specific port binding, and browser suppression.
Always refer to the product as just Aspire, never ".NET Aspire". This applies to all comments in generated AppHost code, messages to the user, and any documentation you produce.
When printing or displaying the Aspire dashboard URL to the user, always include the full login token query parameter. The dashboard requires authentication — a bare URL like http://localhost:18888 won't work. Use the full URL as printed by aspire start (e.g., http://localhost:18888/login?t=<token>).
Aspire's infrastructure automatically provisions TLS certificates for container resources that register WithHttpsCertificateConfiguration callbacks. AddRedis() registers one by default, which means Redis will get TLS automatically when the Aspire dev cert infrastructure is active. This is usually fine, but some apps expect plain (non-TLS) Redis.
If Redis health checks fail with SslStream / RedisConnectionException errors about SSL/TLS handshake failures, the cause is this auto-TLS behavior. Do not fall back to AddContainer(). Instead, disable the certificate on the Redis resource:
var redis = builder.AddRedis("redis")
.WithoutHttpsCertificate(); // plain Redis, no TLS
WithoutHttpsCertificate() suppresses the auto-TLS cert injection so Redis stays on plain TCP. Use this when the consuming services don't support TLS Redis connections.
Always set up HTTPS endpoints by default. Use WithHttpsEndpoint() instead of WithHttpEndpoint() unless HTTPS doesn't work for a specific integration. For JavaScript and Python apps, call WithHttpsDeveloperCertificate() to configure the dev cert. If HTTPS causes issues for a specific resource, fall back to HTTP and leave a comment explaining why. See the "Endpoints and ports" section in the AppHost wiring reference below for detailed patterns and examples.
When a service needs another service's URL as an environment variable, always pass an endpoint reference — never a hardcoded string. Hardcoded URLs break whenever Aspire assigns different ports. See the "Cross-service environment variable wiring" section in the AppHost wiring reference below for examples.
Similarly, never use withUrlForEndpoint / WithUrlForEndpoint to set dev.localhost URLs. That API is ONLY for setting display labels in the dashboard (e.g., url.DisplayText = "Web UI"). dev.localhost configuration belongs in aspire.config.json profiles — see Step 9.
This skill is about getting a great local development experience. Don't worry about production deployment manifests, cloud provisioning, or publish configuration — that's a separate concern for later.
This means:
ContainerLifetime.Persistent for databases and caches so data survives AppHost restartsWithDataVolume() to persist data across container recreations*.dev.localhost subdomains is encouraged// Instead of the service hardcoding "https://api.stripe.com"
var stripeUrl = builder.AddParameter("stripe-url", secret: false);
var api = builder.AddCSharpApp("api", "../src/Api")
.WithEnvironment("STRIPE_API_URL", stripeUrl);
This makes the external dependency visible in the dashboard and lets developers easily swap endpoints (e.g., to a Stripe test endpoint) without digging through service code. Present this as an option to the user — don't silently refactor their external service calls.
.env files into AppHost parametersMany projects use .env files for configuration. These should be migrated into the AppHost so that all config is centralized and visible in the dashboard. Scan for .env, .env.local, .env.development, etc. and propose migrating their contents:
AddParameter(name, secret: true). Aspire stores these securely via user secrets and prompts the developer to set them.AddParameter(name, secret: false) with a default value, or WithEnvironment() directly.DATABASE_URL=postgres://..., REDIS_URL=redis://...): replace with actual Aspire resources (AddPostgres, AddRedis) and WithReference() — the connection string is then managed by Aspire.// Before: .env file with DATABASE_URL=postgres://user:pass@localhost:5432/mydb
// STRIPE_KEY=sk_test_abc123
// DEBUG=true
// After: modeled in AppHost
var db = builder.AddPostgres("pg").AddDatabase("mydb");
var stripeKey = builder.AddParameter("stripe-key", secret: true);
var api = builder.AddCSharpApp("api", "../src/Api")
.WithReference(db) // replaces DATABASE_URL
.WithEnvironment("STRIPE_KEY", stripeKey) // secret, stored securely
.WithEnvironment("DEBUG", "true"); // plain config
Important: Never delete .env files automatically. After migrating all values into the AppHost, explicitly ask the user:
"I've migrated all the values from your
.envfile into the AppHost. The.envfile is no longer needed for running via Aspire, but it still works for non-Aspire workflows. Would you like me to remove it, or keep it around?"
Some teams still need .env files for CI, Docker Compose, or developers who haven't switched to Aspire yet. Respect that.
Present this as a recommendation. Walk through the .env contents with the user and classify each variable together. Some values may be intentionally local-only and the user may prefer to keep them — that's fine.
.NET projects often use dotnet user-secrets for local development configuration. Look for:
dev/secrets.json.example or secrets.json.example files — these document expected secrets<UserSecretsId> in .csproj files — indicates the project uses User Secretsdotnet user-secrets set or setup_secrets scriptsAspire's AddParameter(name, secret: true) stores values in the same .NET User Secrets store under the hood, so secrets are centralized in the AppHost instead of scattered across individual service projects.
Migration approach:
secrets.json.example files or setup scripts to understand what secrets the repo expects.env migration: connection strings become Aspire resources, API keys become parameters, plain config becomes WithEnvironment()Important: Don't delete or modify existing UserSecretsId entries in service .csproj files — other tooling or non-Aspire workflows may still depend on them.
Before running this skill, aspire init must have already:
apphost.ts or apphost.cs) at the configured locationaspire.config.json at the repository rootVerify both exist before proceeding.
These are hard rules. Do not break them.
Do not run dotnet workload install aspire or any dotnet workload command. The Aspire workload is obsolete. The Aspire CLI (aspire start, aspire run, etc.) handles everything — SDK resolution, package restoration, building, and launching. The workload is not needed and installing it can cause version conflicts.
If a web search, documentation page, or blog post tells you to install the workload, ignore that advice — it is outdated.
Do not modify the root global.json. The repo's SDK pin is intentional. The AppHost may need a newer SDK (e.g., .NET 10) than the repo uses (e.g., .NET 8) — that's fine. aspire init already created the AppHost with the correct TFM. If the AppHost is in full project mode and the repo pins an older SDK, add a nested global.json inside the AppHost directory only — never change the root one.
Do not modify <TargetFramework> in any existing service project. If a service targets net8.0, leave it on net8.0. The Aspire AppHost can orchestrate services on older TFMs without any changes. Only the AppHost itself needs the Aspire-supported TFM.
Before editing AppHost or service project files, check whether the workspace is under git (git rev-parse --is-inside-work-tree) and inspect git status when it is. Do not overwrite unrelated user changes. If there is no git repository, warn the user that this skill will make project mutations and summarize the intended files/areas before proceeding.
If aspire init created a .csproj AppHost, check the Aspire.AppHost.Sdk version in the .csproj. If it references a preview version that isn't available on NuGet (common when using a PR build of the CLI), update it to the latest stable release. Run dotnet nuget list source and check NuGet.org for the current stable version (e.g., 13.2.2). Do not leave the AppHost pinned to an unavailable preview SDK — dotnet build will fail.
aspire start to launch the AppHost (not dotnet run)aspire add <integration> to add hosting integrations (not dotnet add package)aspire restore to restore TypeScript AppHost dependenciesaspire docs search to look up APIsStandard dotnet commands (dotnet build, dotnet add reference, dotnet sln add) are fine for normal .NET operations like building projects, adding project references, and managing solution membership.
Read aspire.config.json at the repository root if it exists. Key fields:
appHost.language: "typescript/nodejs" or "csharp" — determines which syntax and tooling to useappHost.path: path to the AppHost file or project directory — this is where you'll edit codeFor C# AppHosts, there are two sub-modes:
appHost.path points directly to an apphost.cs file using the #:sdk directive. No .csproj needed. Configuration lives in aspire.config.json..csproj, Program.cs, and Properties/launchSettings.json. This was created by aspire init using the aspire-apphost template because a .sln/.slnx was found. The template-generated AppHost is correct and complete — it has proper SDK references, launch profiles with randomized ports, and a skeleton Program.cs. Do not recreate or hand-edit the .csproj or launchSettings.json. Configuration lives in Properties/launchSettings.json inside the AppHost project directory (standard .NET launch settings), not in aspire.config.json.Do not confuse these files. Do not create or modify config files for the user's service projects — only for the AppHost.
| File | Where it lives | What it's for | Who uses it |
|---|---|---|---|
aspire.config.json | Repo root | AppHost path, language, profiles (ports, env vars) | Single-file and polyglot AppHosts only |
Properties/launchSettings.json | Inside the AppHost project dir | Launch profiles (ports, env vars) | Full project mode .csproj AppHosts (standard .NET) |
appsettings.json | Inside service projects | Service-specific configuration | The services themselves — do not create or modify these |
launchSettings.json in service projects | Inside service project dirs | Service launch config | The services themselves — do not create or modify these |
Key rule: When the AppHost is a .csproj project, it's a standard .NET project. It uses Properties/launchSettings.json for launch profiles, just like any other .NET project. aspire init creates this file with the correct ports. Do not create a duplicate aspire.config.json for project-mode AppHosts.
Check which mode you're in by looking at what exists at the appHost.path location. If there's no aspire.config.json, look for a .csproj AppHost directory with Properties/launchSettings.json instead.
If you're in full project mode, also load references/full-solution-apphosts.md. It covers:
Program.cs / Startup.cs / IHostBuilder migration decisions.csproj AppHostsFollow these steps in order. If any step fails, diagnose and fix before continuing. The goal is a working aspire start — keep going until every resource starts cleanly and the dashboard is accessible. Do not stop at partial success.
Analyze the repository to discover all projects and services that could be modeled in the AppHost.
What to look for:
*.csproj files. For each, run:
dotnet msbuild <project> -getProperty:OutputType — Exe/WinExe = runnable service, Library = skipdotnet msbuild <project> -getProperty:TargetFramework — must be net8.0 or newerdotnet msbuild <project> -getProperty:IsAspireHost — skip if true*.sln or *.slnx — if found, the C# AppHost must use full project mode (with .csproj) so it can be opened in Visual Studio alongside the rest of the solution. This is a hard requirement.package.json containing a start, dev, or main/module entry. For each, also check:
vite.config.* file? → use AddViteAppsrc/index.ts, server.js) and a build script that compiles TypeScript? → use AddNodeApp with .WithRunScript() and .WithBuildScript()AddJavaScriptApppackage.json for "workspaces" field (Yarn/npm) or pnpm-workspace.yaml (pnpm). If this is a monorepo:
start, dev) is a potential Aspire resource"start": "yarn --cwd ./subdir start". Model the actual app directory as the resource, not the rootappDirectory is relative to the AppHost location. In monorepos you often need ../, ../../, or similar paths. Double-check these.WithYarn() / .WithPnpm() on each resource handles workspace-aware installs automaticallypyproject.toml, requirements.txt, or main.py/app.pygo.modpom.xml or build.gradleDockerfile entries representing servicesdocker-compose.yml or compose.yml files — these are a goldmine. Parse them to extract:
profiles: key, the compose file uses profiles to organize services into groups (e.g., cloud, storage, mssql, postgres). When profiles exist:
profiles: key, it runs in all profiles — always include itpostgres:16, redis:7) → these become AddContainer() or typed Aspire integrations (e.g., AddPostgres(), AddRedis())WithHttpsEndpoint() or WithEndpoint().env file references → WithEnvironment(). Watch for ${VAR} interpolation syntax — trace these back to .env files and migrate them to AppHost parametersWithVolume() or WithBindMount()depends_on → WithReference() and WaitFor()build: entries → AddDockerfile() pointing to the build context directoryAddContainer() when the image matches a known integration (use aspire docs search to check). For example, postgres:16 → AddPostgres(), redis:7 → AddRedis(), rabbitmq:3 → AddRabbitMQ()..env files: Scan for .env, .env.local, .env.development, .env.example, etc. These contain configuration that should be migrated into AppHost parameters (see Guiding Principles above)secrets.json.example files and <UserSecretsId> in .csproj files. These indicate .NET User Secrets are in use — migrate them into AppHost parameters (see Guiding Principles above)pnpm-lock.yaml → pnpm, yarn.lock → yarn, package-lock.json or none → npm. Use the detected package manager for all install/run commands throughout this skill.Ignore:
node_modules/, .aspire/modules/, dist/, build/, bin/, obj/, .git/test/tests/__tests__, projects referencing xUnit/NUnit/MSTest, or test-only package.json scripts)Before investing time in wiring, run aspire doctor to verify the environment is ready:
aspire doctor
This checks for a working .NET SDK, container runtime (Docker/Podman), trusted dev certificates, and deprecated workloads. Fix any failures before proceeding — discovering that Docker isn't running after you've wired 10 services wastes significant time.
Common issues caught by aspire doctor:
aspire certs trust to fix HTTPS endpoint failures.Once the environment is clean, verify the Aspire skeleton boots:
aspire start
The empty AppHost should start successfully — the dashboard should come up and the process should run without errors. You won't see any resources yet (that's expected), but if aspire start fails here, fix the issue before proceeding.
For full project mode, the AppHost was created from the aspire-apphost template and should work out of the box. If it fails, common causes are:
global.json may pin an older SDK (e.g., 8.0). The AppHost directory needs its own nested global.json pinning the Aspire-supported SDK. Create one if missing (see Critical Rules above).For single-file mode:
aspire.config.json: The file must have a profiles section with applicationUrl. Re-run aspire init to regenerate..aspire/modules/aspire.js SDK is available. Run aspire restore if needed.Once it boots, stop it (Ctrl+C) and continue.
Show the user what you found. For each discovered project/service, show:
Ask the user:
Skip this step for TypeScript AppHosts. OTel is handled in Step 8.
If the AppHost is in full project mode, consult references/full-solution-apphosts.md before making ServiceDefaults changes. Some existing solutions need bootstrap updates before AddServiceDefaults() and MapDefaultEndpoints() can be applied safely.
Before creating ServiceDefaults, check for existing observability setup. Many repos already have their own OpenTelemetry, Polly (HTTP resilience), or health check wiring — often in a shared extension method or SDK package. Search for:
AddOpenTelemetry, UseOpenTelemetry, AddTracing, WithTracing, WithMetrics — existing OTel setupAddStandardHttp, AddPolicyHandler, AddHttpStandardResilienceHandler — existing Polly/resilience configAddHealthChecks, MapHealthChecks — existing health check registrationUseBitwardenSdk(), UseCompanySdk()) — these often bundle OTel, health checks, and auth in one callIf the repo already has OTel/health checks/resilience in a shared extension, strip those from the generated ServiceDefaults to avoid duplication. Only keep the parts that don't overlap. For example, if UseBitwardenSdk() already sets up OTel tracing and metrics, the ServiceDefaults should skip the OTel builder calls and only add service discovery and health endpoint mapping.
Present the overlap to the user: "Your services already set up OpenTelemetry via UseBitwardenSdk(). I'll create ServiceDefaults without the OTel setup to avoid duplication."
Placement is your decision. Where to put ServiceDefaults depends on the repo's structure:
global.json), ServiceDefaults should live next to the AppHost in that same boundary so it can target the same TFM.src/).Microsoft.Extensions.ServiceDiscovery or Aspire.ServiceDefaults), skip creation and use the existing one.To create one:
dotnet new aspire-servicedefaults -n <SolutionName>.ServiceDefaults -o <path>
If a .sln exists and the ServiceDefaults project is compatible with the solution's SDK, add it:
dotnet sln <solution> add <ServiceDefaults.csproj>
Edit the skeleton AppHost file to add resource definitions for each selected project. Use the appropriate syntax based on language.
apphost.ts)import { createBuilder } from './.aspire/modules/aspire.js';
const builder = await createBuilder();
// Express/Node.js API with TypeScript — needs build for publish
const api = await builder
.addNodeApp("api", "./api", "dist/index.js") // production entry point
.withRunScript("start:dev") // dev: runs ts-node-dev or similar
.withBuildScript("build") // publish: compiles TS first
.withYarn() // or .withPnpm() — match the repo
.withHttpsDeveloperCertificate()
.withHttpsEndpoint({ env: "PORT" });
// Vite frontend — HTTPS with dev cert, suppress auto-browser
const frontend = await builder
.addViteApp("frontend", "./frontend")
.withBuildScript("build")
.withYarn()
.withHttpsDeveloperCertificate()
.withEnvironment("BROWSER", "none") // prevent auto-opening browser
.withReference(api)
.waitFor(api);
// .NET project — HTTPS works out of the box
const dotnetSvc = await builder
.addCSharpApp("catalog", "./src/Catalog");
// Dockerfile-based service
const worker = await builder
.addDockerfile("worker", "./worker");
// Python app — HTTPS with dev cert
const pyApi = await builder
.addPythonApp("py-api", "./py-api", "app.py")
.withHttpsDeveloperCertificate();
await builder.build().run();
apphost.cs)#:sdk Aspire.AppHost.Sdk@<version>
#:property IsAspireHost=true
var builder = DistributedApplication.CreateBuilder(args);
var api = builder.AddCSharpApp("api", "../src/Api");
var web = builder.AddCSharpApp("web", "../src/Web")
.WithReference(api)
.WaitFor(api);
builder.Build().Run();
Program.cs + .csproj)In full project mode the AppHost has a .csproj, so prefer typed project references via AddProject<T>() — they give compile-time safety, auto-restart on rebuild, and the typed Projects.* namespace. Reserve AddCSharpApp("name", "../path") for cases where a project reference isn't possible (e.g., the service uses an SDK that can't be referenced from Aspire.AppHost.Sdk, or the AppHost is single-file mode).
Edit the AppHost's Program.cs:
var builder = DistributedApplication.CreateBuilder(args);
var api = builder.AddProject<Projects.Api>("api");
var web = builder.AddProject<Projects.Web>("web")
.WithReference(api)
.WaitFor(api);
builder.Build().Run();
And add project references so the Projects.* namespace is generated:
dotnet add <AppHost.csproj> reference <Api.csproj>
dotnet add <AppHost.csproj> reference <Web.csproj>
// Node.js app (Tier 1: Aspire.Hosting.JavaScript) — HTTPS with dev cert
var frontend = builder.AddViteApp("frontend", "../frontend")
.WithHttpsDeveloperCertificate();
// Python app (Tier 1: Aspire.Hosting.Python) — HTTPS with dev cert
var pyApi = builder.AddPythonApp("py-api", "../py-api", "app.py")
.WithHttpsDeveloperCertificate();
// Go app (Tier 2: CommunityToolkit.Aspire.Hosting.Golang)
var goApi = builder.AddGolangApp("go-api", "../go-api")
.WithHttpsEndpoint(env: "PORT");
// Dockerfile-based service (Tier 3: fallback for unsupported languages)
var worker = builder.AddDockerfile("worker", "../worker");
Add required hosting packages — use aspire add or dotnet add package:
# Tier 1: first-party
aspire add javascript # or: dotnet add <AppHost.csproj> package Aspire.Hosting.JavaScript
aspire add python # or: dotnet add <AppHost.csproj> package Aspire.Hosting.Python
# Tier 2: community toolkit
aspire add communitytoolkit-golang
# or: dotnet add <AppHost.csproj> package CommunityToolkit.Aspire.Hosting.Golang
Always check aspire list integrations and aspire docs search "<language>" to find the best available integration before falling back to AddExecutable/AddDockerfile.
Important rules:
WithReference()/withReference() and WaitFor()/waitFor() for services that depend on each other (ask the user if relationships are unclear).WithExternalHttpEndpoints()/withExternalHttpEndpoints() for user-facing frontends.See references/javascript-apps.md for package.json, tsconfig.json, and ESLint configuration patterns. Key points:
aspire restore to generate .aspire/modules/, then install deps with the repo's package manageraspire restore handles thoseFull project mode: dependencies are managed via the .csproj and dotnet add package/dotnet add reference (already handled in Steps 3-4).
Single-file mode: dependencies are managed via #:sdk and #:project directives in the apphost.cs file.
NuGet feeds: If aspire.config.json specifies a non-stable channel (preview, daily), ensure the appropriate NuGet feed is configured. For single-file mode this is automatic; for project mode, ensure a NuGet.config is in scope.
Skip this step for TypeScript AppHosts.
If any selected .NET service still uses a legacy IHostBuilder / Startup.cs bootstrap, consult references/full-solution-apphosts.md before editing it. Do not assume ServiceDefaults can be dropped into old hosting patterns unchanged.
For each .NET project that the user selected for ServiceDefaults:
dotnet add <Project.csproj> reference <ServiceDefaults.csproj>
Then check each project's Program.cs (or equivalent entry point) and add if not already present:
builder.AddServiceDefaults(); // Add early, after builder creation
And before app.Run():
app.MapDefaultEndpoints();
Be careful with code placement — look at existing structure (top-level statements vs Startup.cs vs Program.Main). Do not duplicate if already present.
For .NET services, ServiceDefaults handles OTel automatically. For everything else, the services need a small setup to export telemetry. Aspire automatically injects OTEL_EXPORTER_OTLP_ENDPOINT into all managed resources — the services just need to read it.
Present this to the user as an option, not a mandatory step. Some users may want to add OTel later, and that's fine — their services will still run, they just won't appear in the dashboard's trace/metrics views.
For each service that doesn't already have OTel, ask:
"Would you like me to add OpenTelemetry instrumentation to
<service>? This lets the Aspire dashboard show its traces, metrics, and logs. I'll need to add a few packages and an instrumentation setup file."
If they say yes, follow the per-language setup guides in references/opentelemetry.md.
Before validating, present the user with optional quality-of-life improvements. These aren't required for aspire start to work, but they make the local dev experience significantly nicer.
Suggest each of these individually — don't apply without asking:
Cookie and session isolation with dev.localhost: When multiple services run on localhost, they share cookies and session storage — which can cause hard-to-debug auth problems. Using *.dev.localhost subdomains isolates each service's cookies and storage. Note: URLs still include ports (e.g., frontend.dev.localhost:5173), but the subdomain isolation prevents cross-service cookie collisions.
"Would you like me to set up
dev.localhostsubdomains for your services? This gives each service its own cookie/session scope so they don't interfere with each other. URLs will look likefrontend.dev.localhost:5173— the*.dev.localhostdomain resolves to 127.0.0.1 automatically on most systems, no/etc/hostschanges needed."
How to do it — pick the right config file based on AppHost mode (see "Configuration files — which is which" earlier in this doc):
apphost.cs with #:sdk directive) and polyglot AppHosts (TypeScript, Python, Go, …): edit the profiles section in aspire.config.json at the repo root..csproj AppHost): edit Properties/launchSettings.json inside the AppHost project directory. Do not edit aspire.config.json for project-mode AppHosts — they read launch profiles from launchSettings.json, so changes to aspire.config.json will be ignored.In both cases, replace localhost with <projectname>.dev.localhost in applicationUrl, and use descriptive subdomains like otlp.dev.localhost and resources.dev.localhost for the infrastructure URLs. This is the same mechanism aspire new uses.
Example — aspire.config.json (single-file / polyglot):
{
"profiles": {
"https": {
"applicationUrl": "https://myproject.dev.localhost:17042;http://myproject.dev.localhost:15042",
"environmentVariables": {
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://otlp.dev.localhost:21042",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://resources.dev.localhost:22042"
}
}
}
}
Equivalent — Properties/launchSettings.json (full project mode):
{
"profiles": {
"https": {
"commandName": "Project",
"applicationUrl": "https://myproject.dev.localhost:17042;http://myproject.dev.localhost:15042",
"environmentVariables": {
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://otlp.dev.localhost:21042",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://resources.dev.localhost:22042"
}
}
}
}
Use the project/repo name (lowercased) as the subdomain prefix for applicationUrl. Use otlp and resources for the infrastructure URLs. Keep the existing port numbers — just swap localhost for the appropriate *.dev.localhost subdomain.
Custom URL labels in the dashboard (display text only): Rename endpoint URLs in the Aspire dashboard for clarity:
.WithUrlForEndpoint("https", url => url.DisplayText = "Web UI")
OpenTelemetry (if not done in Step 8): "Would you like me to add observability to your services so they appear in the Aspire dashboard's traces and metrics views?"
Present these as a batch: "I have a few optional dev experience improvements I can make. Want to hear about them?"
aspire start
Once the app is running, use the Aspire CLI to verify everything is wired up correctly:
aspire describe — confirm all expected resources appear with correct types, endpoints, and states.aspire describe — check that environment variables (connection strings, ports, secrets from parameters) are injected into each resource as expected. Verify .env values that were migrated to parameters are present.aspire otel — verify that services instrumented with OpenTelemetry are exporting traces and metrics to the Aspire dashboard collector.aspire logs <resource> — check logs for each resource to ensure clean startup with no crashes, missing config, or connection failures.selfHosted flag, a config key like OpenTelemetry:Enabled, or an environment check). Many SDKs default OTel OFF for self-hosted/local modes — set the enabling flag explicitly via WithEnvironment().OTEL_EXPORTER_OTLP_ENDPOINT is being injected by Aspire (it should be automatic for services modeled with AddCSharpApp/AddProject).curl https://localhost:<port>/healthz or any known endpoint). Some services don't emit traces until they receive traffic.This skill is not done until aspire start runs without errors and every resource is in an expected terminal/runtime state. Acceptable end states are:
Treat these as failure states unless you intentionally designed for them:
If anything lands in an unexpected state, diagnose it, fix it, and run aspire start again. Keep iterating until the app behaves as expected — do not move on to Step 11 with crash-shaped "success".
Once everything is healthy, print a summary for the user:
✅ Aspire init complete!
Dashboard: <full dashboard URL including login token>
Resources:
<name> <type> <status>
<name> <type> <status>
...
<any notes about optional steps skipped, e.g., "OTel not configured — run the aspire skill to add it later">
Get the dashboard URL (with login token) from aspire start output. Get resource status from aspire describe. If any resource shows Finished, confirm from logs that it was an intentional one-shot resource that exited successfully before including it as success. This summary is the user's confirmation that init worked — make it complete and accurate.
Common issues:
#:project paths wrong, missing SDK directiveaspire certs trust and retryIf a .sln/.slnx exists, verify all new projects are included:
dotnet sln <solution> list
Ensure both the AppHost and ServiceDefaults projects appear.
After successful validation:
aspire stop.aspire skill is present for ongoing AppHost work.aspireify/ skill directory unless the user explicitly asks.aspire doctor to diagnose environment issueswithEnvironment — when a service needs another service's URL (e.g., VITE_APP_WS_SERVER_URL), pass an endpoint reference, NOT a string literal. Use room.getEndpoint("http") (TS) or room.GetEndpoint("http") (C#) and pass that to withEnvironment. Hardcoded URLs break when ports change.withUrlForEndpoint to set dev.localhost URLs — dev.localhost configuration belongs in aspire.config.json profiles, not in AppHost code. withUrlForEndpoint is ONLY for setting display labels (e.g., url.DisplayText = "Web UI")..sln/.slnx + .csproj AppHost), see references/full-solution-apphosts.md.docker-compose.yml or compose.yml, see references/docker-compose.md.