The two relationships
An Aspire app model is a graph of resources - databases, caches, projects, containers. What is easy to miss is that the AppHost relates to each one in exactly one of two ways, and that relationship - not the resource type - decides how Aspire treats it: what actually runs, what shows up in the dashboard, and what gets a connection string generated for it.
This is the distinction the AppHost teardown flags but never expands. A resource is either managed - Aspire owns its existence - or referenced - it lives outside Aspire, which owns only the wiring to it. Every resource is one or the other, and the whole rest of this article is the consequences of that one fork.
It runs because Aspire runs it
- Declared with
AddRedis,AddPostgres,AddContainer,AddProject, and the like. - Aspire starts it, supervises its health, and tears it down when you stop.
- Connection details are generated by Aspire and injected automatically.
It exists without Aspire
- Declared with
AddConnectionString, orAsExistingfor a cloud resource. - Aspire owns only the wiring - it reads a value from configuration and passes it through.
- A SQL instance you run in SSMS, a managed cloud database, anything Aspire does not start.
Managed resources
A managed resource is one whose existence Aspire owns. You declare it with an Add* call, and from then on Aspire is responsible for it: it starts the container or process, watches its health, reports or restarts it, and stops it when you shut the AppHost down.
Three things follow from Aspire owning the resource:
- Connection details are generated, not written. Aspire assigns the port, generates the password, and injects a
ConnectionStrings:<name>value into every dependent - you never type a host or a secret. - It shows up as a running resource in the dashboard. Logs, health, and telemetry are all there, because Aspire is the thing supervising it.
- WaitFor works. Because Aspire knows the resource's health, it can hold a dependent until the resource is actually ready.
Adding Redis caching is a textbook managed resource: one line in the AppHost, and Aspire runs the container, generates the connection, and injects it.
Referenced resources
A referenced resource already exists outside Aspire. You don't ask Aspire to run it - you hand it a connection string, and Aspire wires that into the dependents untouched. Aspire owns the reference, not the resource.
The value comes from configuration - User Secrets in development, environment variables or Key Vault in production:
What you give up is exactly what Aspire was doing in the managed case: no generated credentials, no container, no lifecycle, and no health gate - WaitFor has nothing to wait on. What you gain is a real, persistent database you control. For a cloud resource that already exists, AsExisting is the strongly-typed version of the same idea.
The consumer can't tell
Here is the payoff, and the reason the distinction is cheap to live with: the consuming side is identical for both. Whether db is a managed Postgres container or a referenced connection string, the project receives the same ConnectionStrings:db value and resolves its client the same way.
So switching a resource from managed to referenced - or back - is a one-line change in the AppHost and no change at all in the consumer:
var db = builder.AddPostgres("db") .AddDatabase("appdata");
var db = builder.AddConnectionString("db");
This is why you can develop against a disposable local container and point at a real database later without touching application code. The relationship is an AppHost concern; the consumer only ever sees a connection string.
Choosing between them
Neither relationship is the "right" one - they trade different things. The real question is who should own the database's existence for the job in front of you.
A fresh, isolated instance every run, with nothing to install, no credentials to manage, and full dashboard observability. Ideal for the inner loop and for CI, where you want a clean slate each time.
CostIt is rebuilt each run unless you make the container persistent, it costs container memory and a first-pull, and it is not the database your real data lives in.
A real, persistent instance with your schema and data - a shared team database, a local SQL Server you manage in SSMS, or a cloud database. It survives restarts and matches what production actually talks to.
CostYou own setup, migrations, and drift between machines; Aspire can't health-gate it with WaitFor, and it won't appear as a managed resource in the dashboard.
Rule of thumb: managed for disposable inner-loop dependencies (a cache, a scratch database), referenced for anything shared, pre-existing, or that owns real data. Adding Azure SQL walks both side by side - a managed local container, and a referenced connection string to your own SSMS instance.
The blur: cloud resources
Azure resources make the line move depending on the environment, which is where the distinction gets genuinely interesting. AddAzureSqlServer("sql").RunAsContainer() is managed locally - a SQL Server container Aspire runs - but on aspire publish the same model is provisioned in Azure. And AsExisting references an Azure database that already exists.
So one resource can be managed in development and provisioned-or-referenced in the cloud, all from a single declaration. That is the same run-mode vs publish-mode split the AppHost teardown describes, viewed through the ownership lens. The hybrid setup wires both kinds through one AppHost at once.
References
- What is the AppHost? - the application modelaspire.dev/get-started/app-host
- Local Azure provisioning - existing resources & connection stringsaspire.dev/integrations/cloud/azure/local-provisioning
- Aspire AppHost overview - resources and referenceslearn.microsoft.com/dotnet/aspire/fundamentals
- Inside Aspire's AppHoststacknova · engineering · apphost
- How to add Azure SQL Database to an Aspire appstacknova · engineering · azure sql