Since the advent of microservices, development teams have gained the flexibility to deploy services independently, without coordinating with the entire engineering organization. Bug fixes can be released in isolation without full regression testing, and multiple teams can ship updates simultaneously, sometimes ten or more deploys a day per team.
But we rarely talk about the downsides of microservices. In medium to large-scale systems, the number of services can grow quickly. Netflix reportedly runs over seven hundred microservices, and Uber manages more than two thousand. That kind of scale introduces a lot of moving parts, testing complexity, and debugging challenges across service boundaries. And all of this can severely impact developer experience (DX).
Recently, I came across a new framework called .NET Aspire, which dramatically simplifies local microservices development. Aspire handles service discovery, configuration management, and observability for distributed applications, giving you a complete view of your system through a built-in dashboard. This results in a much simpler, smoother local development experience compared to manually wiring up multiple services. In this guide, we’ll explore how Aspire works and how it can help improve developer experience in microservices-based systems.
Prerequisites
Before we begin, ensure you have the following installed:
-
.NET 8 SDK or later
-
-
Aspire uses Docker to run dependencies like Redis, PostgreSQL, and so on.
-
Ensure Docker is running before starting
-
-
Visual Studio 2022 (v17.9+) or Visual Studio Code with C# Dev Kit
-
Basic understanding of:
-
C# and .NET development
-
Microservices architecture concepts
-
REST APIs and service communication
-
Optional but Recommended:
-
Familiarity with Docker and containerization
-
Experience with distributed application development
-
Knowledge of observability concepts (logging, tracing, metrics)
Table of Contents
Understanding Developer Experience in Microservices
When people talk about DX, they often think of it as tooling or ergonomics, things like good documentation, fast build times, and clean APIs. But in distributed systems, DX becomes much broader. It’s about how easily developers can set up, run, and reason about the systems they’re building.
In a monolithic application, starting your development environment might mean running a single command like dotnet run. But in a microservices-based system, you might need to start multiple APIs, databases, background workers, and queues, all with specific configuration dependencies. This extra overhead doesn’t just slow you down, it breaks your focus and adds friction to daily development.
Over time, that friction compounds.
-
Onboarding new developers becomes slower.
-
Debugging across service boundaries gets harder.
-
Teams spend more time managing environments than writing features.
That’s why DX is so important in microservices architectures. It is not just about developer happiness, it’s about velocity, consistency, and confidence. If your local environment isn’t easy to run or reason about, every other process in your development lifecycle suffers.
This is where orchestration frameworks like .NET Aspire start to make a real difference. They handle the complexity of coordinating services, so developers can focus on building and iterating faster, the way modern software development is meant to work.
Introducing .NET Aspire
As microservice systems grow, local development environments often become a patchwork of scripts, Docker Compose files, and manual setup steps. Each developer ends up managing their own version of “how to get things running,” and small differences in configuration can lead to big inconsistencies across teams.
.NET Aspire is an orchestration framework designed to simplify this process. It provides a way to define, configure, and run your distributed applications as a single unit, directly within your .NET solution.
In practical terms, Aspire helps developers by handling three key areas automatically:
-
Service Orchestration
Aspire can start multiple projects (APIs, workers, databases, and so on) in the correct order. It takes care of service dependencies so that, for example, your API doesn’t try to start before the database it depends on is ready. -
Configuration Management
Instead of juggling dozens ofappsettings.jsonfiles or environment variables, Aspire provides a centralized configuration model. It shares connection strings, ports, and environment settings across services in a consistent way. -
Observability and Insights
Aspire includes built-in OpenTelemetry support and a dashboard that gives you real-time visibility into your running services, including their health, logs, and endpoints. This makes debugging and local monitoring much easier.
In many ways, Aspire does for services what Kubernetes does for containers, but with a sharper focus on local development and developer experience. It’s not meant to replace your production orchestration tools, it’s designed to make your everyday development smoother, faster, and less error-prone.
How to Set Up .NET Aspire in Your Project
We’ll create a microservices setup and watch Aspire orchestrate it with minimal code. Make sure you’re running .NET 8 or later. Aspire requires it.
Create a New Aspire Project
Start by creating a new Aspire app host using the .NET CLI:
dotnet new aspire-app -n MyCompany.AppHost
This command creates a new Aspire “host” project, the entry point that orchestrates your other microservices, APIs, and dependencies.
You’ll notice that the generated project contains a Program.cs file with an AppHostBuilder. This builder acts as the control center for your distributed system.
Add Your Microservices
You can now reference your existing projects or create new ones directly in the same solution. For example:
dotnet new webapi -n CatalogService
dotnet new webapi -n OrderService
dotnet new worker -n NotificationWorker
Then, add them to your Aspire host by editing Program.cs:
var builder = DistributedApplication.CreateBuilder(args);
var catalog = builder.AddProject<Projects.CatalogService>("catalog");
var order = builder.AddProject<Projects.OrderService>("order")
.WaitFor(catalog); // ensure this starts after CatalogService
var notifications = builder.AddProject<Projects.NotificationWorker>("notifications");
builder.Build().Run();
In this example:
-
AddProjectregisters each service with Aspire. -
.WaitFor()enforces startup dependencies (for example,OrderServicedepends onCatalogService). -
Aspire takes care of starting these services in the right order, sharing environment variables, and managing ports automatically.
Run All Services with One Command
Now, from your app host directory, run:
dotnet run
Aspire will:
-
Start all the registered services.
-
Allocate available ports.
-
Inject shared configurations.
-
Launch a local dashboard showing service health, endpoints, and logs.
You should see output like this:
Starting CatalogService...
Starting OrderService...
Starting NotificationWorker...
AppHost running on http://localhost:18888
And when you open the dashboard in your browser, you’ll see all your services, their statuses, and links to their APIs.
Add a Local Database (Optional)
To show how Aspire handles dependencies, let’s add a PostgreSQL container:
var db = builder.AddPostgres("postgres");
builder.AddProject<Projects.CatalogService>("catalog")
.WithReference(db); // injects connection string automatically
Now when you run the app, Aspire will start PostgreSQL first, generate a connection string, and pass it to CatalogService. No manual setup or .env files required.
Why This Matters for Developer Experience
Before Aspire, getting your local environment running meant opening multiple terminals, waiting around for databases to start, and copying connection strings between projects. With Aspire, it’s just one command. Everything starts automatically, configuration is shared across services, and you get observability built in. That’s the developer experience win. Less time fighting your setup, more time actually coding.
Framework: How to Adopt .NET Aspire Incrementally
If you’re considering trying Aspire in your own team, you don’t have to migrate everything at once. In fact, the best approach is incremental adoption. Start small and expand gradually.
Here’s a simple framework you can follow:
Step 1: Start Small
Create an Aspire host and connect one or two key services.
This helps your team understand the orchestration flow before scaling up.
dotnet new aspire-app -n MyCompany.AppHost
Step 2: Add Dependencies Incrementally
As you grow, include more services and use .WaitFor() to define dependencies and startup order.
var builder = DistributedApplication.CreateBuilder(args);
var db = builder.AddPostgres("postgres");
builder.AddProject<Projects.CatalogService>("catalog")
.WithReference(db);
builder.AddProject<Projects.ApiGateway>("gateway")
.WaitFor("catalog");
builder.Build().Run();
Step 3: Integrate Observability
Leverage Aspire’s built-in OpenTelemetry integration for metrics and traces. You’ll instantly gain better insight into service interactions even without external tools.
Step 4: Share Your Setup
Commit your Aspire host to source control so every developer uses the same setup.
This ensures consistency across environments, reducing the classic “works on my machine” problem.
Note: Aspire doesn’t require a full rewrite. It works great as a starting layer while your team continues evolving your existing orchestration setup.
How to Use the .NET Aspire Dashboard
One of the standout features of .NET Aspire is its built-in dashboard, which gives you real-time visibility into your microservices while they run locally.
When you start your Aspire app host with dotnet run, it automatically spins up a local dashboard (by default at http://localhost:18888). This dashboard provides a centralized view of all your services — APIs, databases, background workers, and any connected dependencies.
Here’s what you’ll find inside:

Service Overview
The dashboard home page lists every service in your distributed application. For each one, you can see:
-
Name and type (for example, cache, apiservice, webfrontend)
-
Current state (Running, Starting, Stopped)
-
Source
-
Port and endpoint information
-
Startup time and uptime
-
Logs and metrics shortcuts
This immediately replaces the need to track multiple terminal windows or scroll through dozens of logs just to confirm everything started correctly.
The dashboard automatically detects unhealthy or failed services and highlights them, so you can identify startup issues early.
Navigating to Endpoints
Each service card includes quick links to its exposed endpoint, providing easy access to relevant tools and interfaces. For example, APIs may include links to Swagger UI or Scalar, databases may link to pgAdmin or similar management tools, and internal services may offer links to custom dashboards.
This setup allows users to test APIs or verify database connections directly from the dashboard without needing to remember specific ports or manually construct URLs.
Real-Time Logs
Clicking into a specific service opens a detailed view showing real-time logs streamed directly from that service.
This is especially helpful when debugging startup issues or service interactions. Instead of running dotnet run in separate terminals, you can view logs for all your services in one place, color-coded and timestamped for clarity.
Observability Built-In (OpenTelemetry)
Aspire includes OpenTelemetry by default, which means that even without additional configuration, you automatically gain access to several powerful observability features. These include distributed traces across service boundaries, metrics for performance monitoring, and correlated logs that help track requests spanning multiple services.
For teams already using tools like Grafana, Jaeger, or SigNoz, Aspire can export this telemetry data to your preferred observability platform with minimal setup.
With tracing enabled, you can follow a request as it travels from your API to your database, through background workers, and back, all from within the dashboard.
Why the Dashboard Improves Developer Experience
Without Aspire, running a local microservices environment typically requires managing multiple terminal windows, tracking ports manually, and searching through log files to diagnose failures.
Aspire consolidates these tasks into a single visual interface where developers can view all services, check dependencies, inspect logs, and monitor system health directly from the browser.
This integrated environment enables faster debugging, maintains developer focus, and simplifies work with complex systems by reducing the overhead of manual coordination.
Practical Scenarios: Solving Real-World DX Challenges with .NET Aspire
So far, we have looked at how Aspire works and what it provides out of the box. But to really understand its impact on developer experience, let’s go through a few real-world pain points that almost every team building with microservices has faced, and how Aspire helps solve them.
Starting Multiple Services in the Right Order
The Problem: In most microservices setups, service startup order matters. For instance, your API Gateway might depend on the User Service and Catalog Service, which both depend on a Database.
If you start these in the wrong order, the gateway fails to connect, and you end up restarting services manually until everything stabilizes.
How Aspire Solves It: Aspire provides a simple way to express dependencies using .WaitFor():
var builder = DistributedApplication.CreateBuilder(args);
var db = builder.AddPostgres("postgres");
var user = builder.AddProject<Projects.UserService>("user")
.WithReference(db);
var catalog = builder.AddProject<Projects.CatalogService>("catalog")
.WithReference(db);
var gateway = builder.AddProject<Projects.ApiGateway>("gateway")
.WaitFor(user)
.WaitFor(catalog);
builder.Build().Run();
Aspire automatically ensures that each service only starts after the services it depends on are fully ready.
No more manual sequencing or “start this one first” instructions in your README.
Port Conflicts and Configuration Drift
The Problem: Developers often encounter the dreaded “Port 5000 is already in use” or spend time editing configuration files to avoid conflicts. Over time, local setups diverge across the team, making onboarding and debugging harder.
How Aspire Solves It: Aspire dynamically manages ports and configuration at runtime. Each service gets a unique port assignment, and Aspire automatically shares connection information across services.
You can still set explicit ports when needed:
builder.AddProject<Projects.Frontend>("frontend")
.WithHttpEndpoint(port: 5173);
This removes guesswork, keeps environments consistent, and ensures new developers can clone the repo and start everything without editing config files.
Simplifying New Developer Onboarding
The Problem: For many teams, onboarding means following a long README with dozens of setup steps, manual database migrations, and environment variable configurations. It can take hours, or even days before a new developer can run the system locally.
How Aspire Solves It: Aspire defines your entire environment in code. That means the setup process becomes as simple as cloning the repository and running one command:
dotnet run
Aspire will start all necessary services, configure dependencies, and bring up the dashboard for visibility. This transforms onboarding from a multi-hour process into something that can be completed in minutes, with far fewer setup issues.
Improving Debugging and Cross-Service Visibility
The Problem: Debugging in microservices often means jumping between logs, tracing requests across multiple services, or reproducing issues that only appear when several services run together.
How Aspire Solves It: With built-in observability and the Aspire dashboard, you can view logs across all services in one place, inspect health checks and metrics, and trace requests using OpenTelemetry. This makes it much easier to identify issues across service boundaries and speeds up debugging, especially during integration testing or local development.
Running Optional or External Services
The Problem: Sometimes you don’t need to run every service locally. For example, you might connect to a shared staging API or external dependency instead of running a local instance.
How Aspire Solves It: Aspire lets you make services optional using conditional checks:
if (Directory.Exists("../Frontend"))
{
builder.AddProject<Projects.Frontend>("frontend");
}
This makes your setup flexible: you can run a minimal environment for development or a full environment for integration testing, all using the same configuration.
Why These Scenarios Matter
Each of these examples solves a specific friction point in the developer experience. Startup complexity, environment drift, onboarding time, and debugging difficulty.
By automating orchestration and configuration, Aspire frees developers from repetitive setup work and lets them focus on building features instead of managing infrastructure.
Going Further
Once you’re comfortable with Aspire’s basics, you can extend it beyond local orchestration to streamline other parts of your workflow.
-
Integrate front-end applications
Orchestrate React, Angular, or Node.js apps alongside your .NET services for a unified full-stack setup. -
Export telemetry data
Send Aspire’s OpenTelemetry output to platforms like Grafana, Jaeger, or Azure Application Insights for deeper analysis. -
Use Aspire in CI/CD pipelines
Bring up full environments for integration or smoke testing during continuous integration runs, all using your existing Aspire configuration. -
Explore community examples
Check out the official Aspire samples and templates for advanced orchestration patterns, cloud integration, and observability setups.
Key Takeaways and When to Use .NET Aspire
As we’ve seen throughout this guide, .NET Aspire isn’t just another developer tool, it’s a framework built specifically to improve developer experience in microservices-based applications.
By orchestrating all your services in a consistent, declarative way, Aspire helps teams reduce friction, speed up setup, and make local environments more reliable and observable.
Key Takeaways
-
Developer Experience (DX) matters as your system grows.
Microservices introduce flexibility and scalability, but they also add complexity; multiple services, ports, dependencies, and startup sequences. Without good orchestration, DX quickly degrades. -
Aspire simplifies orchestration for local development.
It automatically handles service startup, dependencies, configuration sharing, and observability all defined in code, right within your .NET solution. -
The Aspire dashboard improves visibility.
You get a centralized, real-time view of your entire system; services, logs, health, and endpoints eliminating the need for multiple terminals or manual tracking. -
Onboarding new developers becomes faster and smoother.
A singledotnet runcommand can spin up your entire development environment, reducing setup time from hours or days to minutes. -
Built-in observability means better debugging and confidence.
With OpenTelemetry integrated out of the box, developers can trace requests, monitor performance, and diagnose issues across services with minimal setup.
When (and When Not) to Use .NET Aspire
Use Aspire when:
Aspire makes sense if you’re building .NET microservices and tired of complex local setup. It’s especially valuable when your team is dealing with environment drift, slow onboarding, or startup sequences that feel like juggling. If you want one command to spin up your entire system, with observability built in from day one, Aspire is worth trying.
You might not need Aspire when:
Aspire might not be worth it if your current setup already works well. Maybe you’re using Kubernetes or Docker Compose locally and everything runs smoothly. Or you’re building a monolith or single service that doesn’t need orchestration. Or your stack has a lot of non-.NET components that would need custom wiring. If your local development is already simple and stable, don’t fix what isn’t broken.
In other words:
Aspire shines in the local development and onboarding phase. Helping developers build, test, and iterate on distributed systems with minimal friction.
It’s not meant to replace production orchestrators like Kubernetes but to complement them by improving the developer’s day-to-day workflow.
Conclusion
Developer Experience is often overlooked when teams move to microservices, but it directly impacts productivity, quality, and morale. By using .NET Aspire, you can bring order, visibility, and simplicity back to your local development environment.
If you’re looking to streamline your microservices workflow, give Aspire a try. You’ll spend less time fighting your setup and more time building what actually matters; great software.
Ready to get started? Check out the official .NET Aspire documentation or clone one of the sample projects to see it in action.
If you made it to the end of this tutorial, thanks for reading! You can also connect with me on LinkedIn if you’d like to stay in touch.
Powered by WPeMatico
