What the AppHost is
The AppHost is a single project that acts as the control plane for an Aspire application while you develop it. It is not a service your users hit and not a production runtime. It is the one place that knows the full shape of the distributed app - every project, container, and database, and every dependency between them - and it is responsible for starting that whole topology on your machine and keeping it observable.
If you have read What is Aspire? you already know the pitch: describe your services in code, run the lot with one command, watch it all in one dashboard. This article goes underneath that. The AppHost is where the "describe in code" and the "run the lot" actually happen, and the two are more separate than they look - the project you write is a high-level declaration, and a different piece of machinery turns it into running processes.
Concretely, the AppHost is a small .NET console application. What makes it an AppHost rather than an ordinary console app is its project file: it references the Aspire.AppHost.Sdk at the top and pulls in the Aspire.Hosting.AppHost package, which registers the orchestration dependencies. When you run it, its Main builds a model of your application and hands that model off to be executed.
The application model
The output of the AppHost program is not a running app directly - it is an application model: an in-memory graph of resources and the references between them. Resources are added to a builder, and dependencies are declared as explicit calls. The graph on the cover above is exactly what this code produces:
Every node in that graph is an implementation of IResource - a container, an executable, a .NET project, a database, an external service. Three details about this model are worth pulling out, because they are what the rest of the system is built on:
- Resources are typed handles, not strings.
dbandapiare C# values.WithReference(db)is a compiler-checked expression, so a misspelled dependency fails the build, not the run. - Project references are special. A
ProjectReferencein the AppHost is not a normal library reference. The Aspire SDK runs a source generator that emits a strongly-typed class in theProjectsnamespace for each one - that is whereProjects.Apicomes from - so the project becomes a resource the AppHost can launch, not code it links against. - References carry intent, not just order.
WithReferencemeans "wire this dependency's connection details into the dependent."WaitFormeans "do not start the dependent until this one is healthy." They are different statements, and the orchestrator treats them differently.
One more distinction in the model matters more than its size suggests: how the AppHost relates to a given resource. A resource is either managed - Aspire owns its existence, starting it, supervising it, and generating and injecting its connection details - or referenced (an AddConnectionString to something that already exists outside Aspire), where Aspire owns only the wiring. The consuming side is identical either way, so swapping one for the other is a one-line AppHost change. That split has its own article - Managed vs referenced resources - with the hands-on version in What is Hybrid Aspire?.
At this point nothing is running. Build() finalizes the graph; Run() is where it gets handed to something that can actually start things.
The parts
An Aspire AppHost is really five cooperating pieces. The project you edit is only the first of them; the others are pulled in by the SDK and run when you do.
The AppHost project
The .NET console app you write. Builds the model, configures the dashboard and resource service, then calls Run(). A thin top layer over everything below.
The application model
The graph of IResource objects and references built by the builder API. Pure data describing desired state - the blueprint the orchestrator reads.
References & discovery
Resolves WithReference into the connection strings, endpoints, and environment variables injected into each dependent so it finds the others by name, not hardcoded URL.
The control plane
The Developer Control Plane (DCP) - a Kubernetes-compatible local API server that actually launches and supervises containers, executables, and projects.
The dashboard
The observability UI over the running model: resources, logs, traces, and metrics. Reads state from the AppHost, not from DCP directly.
The interesting boundary is between part 02 and part 04. The model is general-purpose and declarative; the control plane is a real orchestrator with opinions about how things run locally. The AppHost's job at run time is to translate one into the other - and that translation is the heart of how it works.
How they fit together
When you run the AppHost, control flows down through those parts in a fixed sequence. The model is built, lowered into a form the control plane understands, executed, and then watched. The diagram below is the who-starts-what:
Define resources
Your Main runs the builder calls. Each AddX appends a resource to the model; each WithReference / WaitFor records an edge.
Build the model
Build() finalizes the graph of IResource objects. This is the general-purpose application model - the same shape whether you will run it or publish it.
Lower to the DCP model
In run mode the AppHost translates the general model into a DCP-specific one tailored for local execution, adding the annotations the engine needs before anything starts.
DCP orchestrates
The AppHost talks to DCP over a Kubernetes client. DCP starts containers, executables, and projects, assigns and deconflicts ports, resolves references into config, and enforces the WaitFor ordering.
The dashboard observes
DCP reports resource state back up. The AppHost aggregates it through its gRPC resource-notification service, and the dashboard renders it - so the UI reads from the AppHost, never from DCP directly.
Two things in that sequence are easy to miss and worth stating plainly. First, the AppHost does not start your services itself - it delegates to DCP, which is a separate process. DCP is, in the Aspire team's own framing, a Kubernetes-compatible API server: a miniature local control plane that the AppHost drives with the same client libraries you would use against a real cluster. Second, the lowering step in stage 3 only happens in run mode. The same model can take a completely different path, which is the next section.
Run mode vs publish mode
The AppHost has two operating modes, and the difference between them is the cleanest way to understand what DCP is and is not. The model you build is identical in both; what the AppHost does with it diverges completely.
Execute it locally
- DCP is registered and started.
- The model is lowered into DCP custom resources.
- Containers, executables, and projects are launched and supervised on your machine.
- The dashboard streams logs, traces, and metrics live.
- This is the inner-loop developer experience.
Serialize it for deployment
- DCP is never involved - nothing runs locally.
- The same model is serialized to deployment artifacts.
- Output feeds tools like
azd, Kubernetes, Docker Compose, or Azure Container Apps. - The references you declared become the wiring your target platform needs.
- This is the path from "described in code" to "running in production."
So DCP is fundamentally a run-mode orchestrator. It exists to make your topology real on a laptop. In publish mode the AppHost behaves more like a compiler: it reads the model and emits a manifest, and what consumes that manifest is somebody else's problem - by design. The same WithReference(db) that injects a local container's connection string in run mode becomes a declared dependency in a deployment manifest in publish mode.
The design decisions
The AppHost is opinionated, and every one of its choices buys something at a cost. These are the decisions worth interrogating - not because they are wrong, but because knowing the cost tells you where the model will and will not fit.
The topology is a program, not a data file. You get type checking, refactoring, source-generated project handles, and arbitrary logic - loops, conditionals, environment-driven branching - in the place you describe your system.
CostThe model is now imperative. You have to build and run a .NET process to know what the graph even is, and a static tool cannot read your topology without executing your code.
DCP runs only in run mode; publish emits manifests for a real platform. Aspire stays in its lane - it never pretends to be the thing running your production traffic, which keeps the local engine simple.
CostThere are two code paths through one model, and what you run locally is not literally what runs in production. You still need - and must trust - a separate deploy target.
Ports are assigned and deconflicted for you, resource names are generated, and WithReference wires service discovery automatically. The happy path is genuinely a few lines.
CostBehavior is implicit. There is a hidden DCP process and generated config you did not write, and the SDK-version-to-DCP-version coupling produces confusing "a newer version is required" failures when they drift.
Reusing Kubernetes conventions and client libraries means the AppHost talks to DCP the same way real tooling talks to a cluster, and the lowering step has a well-understood target shape.
CostIt is heavier conceptual machinery for "run five things locally," and it puts a hidden orchestrator process on your machine that you only notice when it misbehaves.
Read together, the decisions point the same direction: the AppHost optimizes for the distance between local and production being small and code-described, at the cost of some hidden machinery and a real split between running and publishing. That is a good trade when an app has several moving parts and a real deployment target. It is overhead when it does not - which is the boundary the companion article What Aspire does and does not do draws in detail.
References
- What is the AppHost?aspire.dev/get-started/app-host
- Aspire architecture overview - AppHost, app model, DCPlearn.microsoft.com/dotnet/aspire/architecture
- Aspire SDK - project references and the Projects namespacelearn.microsoft.com/dotnet/aspire/fundamentals
- AppHost configuration - DcpCliPath and the orchestration enginelearn.microsoft.com/dotnet/aspire/app-host
- What's new in Aspire 13aspire.dev/whats-new/aspire-13
- TypeScript AppHost in Aspire 13.2 - same app model, different syntaxdevblogs.microsoft.com/aspire
- Exploring the Microsoft Developer Control Plane at the heart of Aspireanthonysimmon.com
- Aspire on GitHubgithub.com/microsoft/aspire
- What is Aspire?stacknova · engineering · aspire
- What is a container?stacknova · cloud · container