Standalone Aspire Dashboard Setup for Distributed .NET Applications
Learn to run the standalone Aspire dashboard in Docker and view traces, logs, and metrics from your distributed .NET apps over OTLP, step by step.
A CCTV screen for your delivery hub
Imagine a busy courier hub in your city. Parcels arrive, get sorted, move to vans, and leave for delivery. The hub manager cannot stand next to every belt at once. So the manager sits in a small room with a wall of CCTV screens. Each screen shows one part of the hub. When a parcel goes missing or a belt jams, the manager looks at the screens and quickly sees where the problem is.
The standalone Aspire dashboard is that wall of screens for your software. Your distributed .NET app may have many parts: an API, a background worker, a payment service, a database. Each part sends little reports about what it is doing. The dashboard collects all those reports and shows them on one neat screen. You see request timelines, log messages, and live numbers, all in one place.
The best part is that you do not need to change your build, your deployment, or your project structure. You just run one small container, point your apps at it, and watch. In this guide we will set it up step by step using .NET 10 (the current LTS), but the same steps work on .NET 8 and 9.
What the dashboard actually does
The dashboard does not generate any data by itself. It is a receiver and a viewer. Your apps push telemetry to it using the OpenTelemetry Protocol, usually shortened to OTLP. OTLP is a shared, open wire format for sending three kinds of signals: traces, logs, and metrics.
Here is the whole idea in one picture.
Because OTLP is an open standard, the dashboard does not care what language your service is written in. A C# API, a Python script, and a Java service can all send to the same dashboard. For this guide we focus on .NET, but keep that flexibility in mind.
Let us be clear about the three signals, since they show up on different tabs in the dashboard.
| Signal | What it is | Example you will see |
|---|---|---|
| Traces | The journey of one request across services | A waterfall showing API to database, 240 ms total |
| Logs | Text messages your code writes | "Order 1042 saved", with level and timestamp |
| Metrics | Numbers measured over time | Requests per second, memory used, error count |
| Resources | The list of apps sending data | api, worker, payment, each with a status |
The ports you need to know
The container uses a few ports. Mixing them up is the most common mistake, so it helps to learn them once. The numbers inside the container are fixed by Microsoft. You map them to ports on your own machine (the host) when you run the container.
| Inside container | Common host port | Purpose |
|---|---|---|
| 18888 | 18888 | The web dashboard you open in the browser |
| 18889 | 4317 | OTLP over gRPC, where apps send telemetry |
| 18890 | 4318 | OTLP over HTTP, an alternative for apps |
So when you start the container, you forward the browser port and at least one OTLP port. The dashboard UI lives on 18888. Your apps talk to 4317 or 4318.
Ports at a glance
Steps
18888 UI
Open this in your browser
4317 gRPC
App sends OTLP here (default)
4318 HTTP
App sends OTLP here (fallback)
Step 1: Run the dashboard in Docker
You need Docker installed and running. That is the only requirement. There is nothing to install into your .NET projects for the dashboard itself.
Open a terminal and run this command.
// This is a shell command, shown here for copy-paste convenience.
// docker run --rm -it -d \
// -p 18888:18888 \
// -p 4317:18889 \
// -p 4318:18890 \
// --name aspire-dashboard \
// mcr.microsoft.com/dotnet/aspire-dashboard:latestLet us read that command slowly, because each part has a job.
--rmremoves the container when it stops, so you do not pile up old containers.-itkeeps it interactive, and-druns it in the background (detached).-p 18888:18888maps the dashboard UI to your machine.-p 4317:18889maps the host's 4317 to the dashboard's gRPC OTLP port.-p 4318:18890maps the host's 4318 to the dashboard's HTTP OTLP port.--name aspire-dashboardgives the container a friendly name so you can find it later.
Once it is running, open http://localhost:18888 in your browser. You will see a login page asking for a token. Do not panic. That is on purpose.
Step 2: Find your login token
By default the dashboard is locked so that no one nearby can peek at your telemetry. When the container starts, it writes a login token to its own logs. You read it and paste it in.
// Another shell command. Read the container logs:
// docker logs aspire-dashboard
//
// Look for a line like:
// Login to the dashboard at http://localhost:18888/login?t=ab12cd34ef...
// Copy the value after t= and paste it into the login page,
// or just click the whole link.If you are using Docker Desktop, you can also click the container and open the Logs tab to see the same line. The token changes every time the container restarts, so if you restart it, fetch the token again.
Skipping the login for local work
If you are only on your own machine and the constant token copying annoys you, you can turn authentication off. Add one environment variable when you start the container.
// Shell command with anonymous access turned on.
// Use this ONLY on your own machine, never on a shared server.
//
// docker run --rm -it -d \
// -p 18888:18888 \
// -p 4317:18889 \
// -p 4318:18890 \
// -e ASPIRE_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true \
// --name aspire-dashboard \
// mcr.microsoft.com/dotnet/aspire-dashboard:latestWith ASPIRE_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS set to true, the dashboard opens straight to the data with no login. This is handy for quick local demos. It is risky anywhere others can reach the dashboard, because telemetry can contain private details like user IDs or URLs. Keep the login on whenever the dashboard is not strictly private.
Step 3: Send telemetry from your .NET app
Now the fun part. We make a plain ASP.NET Core app export OpenTelemetry data to the dashboard. You do not need any Aspire packages. You only need the OpenTelemetry packages.
Add these NuGet packages to your project.
| Package | Why you need it |
|---|---|
OpenTelemetry.Extensions.Hosting | Wires OpenTelemetry into the .NET host |
OpenTelemetry.Exporter.OpenTelemetryProtocol | Sends data using OTLP |
OpenTelemetry.Instrumentation.AspNetCore | Traces incoming HTTP requests automatically |
OpenTelemetry.Instrumentation.Http | Traces outgoing HttpClient calls automatically |
Now configure them in Program.cs. The code below turns on traces, metrics, and logs, and points the OTLP exporter at the dashboard.
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
var builder = WebApplication.CreateBuilder(args);
// Give this service a name so it shows up nicely in the dashboard.
var serviceName = "orders-api";
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource.AddService(serviceName))
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter())
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddRuntimeInstrumentation()
.AddOtlpExporter());
// Logs are added on the logging builder.
builder.Logging.AddOpenTelemetry(logging =>
{
logging.IncludeFormattedMessage = true;
logging.IncludeScopes = true;
logging.AddOtlpExporter();
});
var app = builder.Build();
app.MapGet("/", (ILogger<Program> logger) =>
{
logger.LogInformation("Home endpoint was called");
return "Hello from orders-api";
});
app.Run();Notice that we call AddOtlpExporter() three times: once for tracing, once for metrics, and once for logs. Each one sends its own signal type. We do not pass an endpoint in the code. Instead we set the endpoint with environment variables, which keeps the code clean and easy to change per environment.
Step 4: Point the exporter at the dashboard
The OpenTelemetry SDK reads two standard environment variables to know where to send data and how to talk. Set them before you run the app.
// Shell, for the terminal where you run the app.
//
// Use gRPC on port 4317 (the .NET default):
// setx OTEL_EXPORTER_OTLP_PROTOCOL grpc
// setx OTEL_EXPORTER_OTLP_ENDPOINT http://localhost:4317
//
// Or use HTTP on port 4318 instead:
// setx OTEL_EXPORTER_OTLP_PROTOCOL http/protobuf
// setx OTEL_EXPORTER_OTLP_ENDPOINT http://localhost:4318
//
// On Linux or macOS, use: export OTEL_EXPORTER_OTLP_ENDPOINT=...The variable OTEL_EXPORTER_OTLP_ENDPOINT tells the SDK where the dashboard is. The variable OTEL_EXPORTER_OTLP_PROTOCOL tells it whether to use gRPC or HTTP. Because we mapped the container ports to 4317 and 4318 earlier, the app and the dashboard now agree on the address.
Run the app, then open the home endpoint a few times. Switch to the dashboard in your browser and click around. Within a second or two you should see your service appear under Resources, your requests under Traces, your messages under Structured logs, and live numbers under Metrics.
End to end data flow
Steps
Request
User hits your API
Instrumentation
SDK records a span and logs
OTLP exporter
Sends to localhost:4317
Dashboard
Stores it in memory
You read it
View traces and logs in UI
How a trace travels across services
The real value shows up when you have more than one service. Say your orders-api calls a payments-api over HTTP. OpenTelemetry adds a tiny header to that call so both services share the same trace id. The dashboard then stitches the two halves into one timeline. This is called distributed tracing, and it is exactly the CCTV-wall idea from the start.
When you open that trace in the dashboard, you see a waterfall. The top bar is the orders-api request. Nested under it is the payments-api call. If the payment step is slow, the bar is wide and you can see it at a glance. This makes it easy to answer the question every team asks: where did the time go?
Keeping the dashboard running with Docker Compose
Typing the long docker run command every day gets boring. A small docker-compose.yml file lets you start the dashboard with one short command. It also keeps your settings in version control so your whole team uses the same setup.
// docker-compose.yml (YAML, shown in a code block for copy-paste)
//
// services:
// aspire-dashboard:
// image: mcr.microsoft.com/dotnet/aspire-dashboard:latest
// container_name: aspire-dashboard
// ports:
// - "18888:18888"
// - "4317:18889"
// - "4318:18890"
// environment:
// - ASPIRE_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=trueNow you start it with docker compose up -d and stop it with docker compose down. This is the friendliest way to share the dashboard with teammates, because everyone gets the same ports and the same options.
A few things that trip people up
A short list of common problems will save you time. Most setup pain comes from one of these.
- No data appears. Check the OTLP endpoint. The app must point at
http://localhost:4317(gRPC) orhttp://localhost:4318(HTTP), and the matching host port must be mapped in yourdocker runcommand. - Connection refused on gRPC. Some networks block gRPC. Switch
OTEL_EXPORTER_OTLP_PROTOCOLtohttp/protobufand send to 4318 instead. - Login token keeps changing. That is normal. Each container restart prints a fresh token. For local-only work, set anonymous access on.
- Data disappears after a restart. The dashboard keeps telemetry in memory only. Restarting the container clears everything. This is fine for development. Use a durable backend for anything you must keep.
- Logs look empty. Make sure you called
AddOtlpExporter()on the logging builder, not only on tracing and metrics.
When to move beyond the standalone dashboard
The standalone dashboard is wonderful for local development and demos. It is light, fast, and needs no setup in your code beyond OpenTelemetry. But it is not a long-term store. It holds data in memory, and it caps how many traces and logs it keeps so it does not eat all your RAM.
For staging and production you usually send the same OTLP data to a durable system, such as Jaeger for traces, Prometheus and Grafana for metrics, or a hosted observability service. The lovely part is that your app code does not change at all. You only change the value of OTEL_EXPORTER_OTLP_ENDPOINT. Because everything speaks OTLP, you can keep the dashboard for local work and a bigger backend for production, using the exact same instrumentation in your code. That is the whole point of building on an open standard.
Quick recap
- The standalone Aspire dashboard is a single Docker container that receives and shows telemetry. You do not need Aspire packages in your code.
- It accepts OTLP from any app. Your .NET app uses the normal OpenTelemetry packages and an OTLP exporter.
- Three ports matter: 18888 for the browser UI, 4317 for OTLP over gRPC, and 4318 for OTLP over HTTP.
- The dashboard prints a login token to its logs by default. For local-only work you can set
ASPIRE_DASHBOARD_UNSECURED_ALLOW_ANONYMOUStotrue. - Point your app with
OTEL_EXPORTER_OTLP_ENDPOINTandOTEL_EXPORTER_OTLP_PROTOCOL. AddAddOtlpExporter()for traces, metrics, and logs. - Distributed tracing joins requests across services into one waterfall using a shared trace id.
- The dashboard stores data in memory, so it is great for development. Point OTLP at a durable backend for production without changing your code.
References and further reading
- Standalone .NET Aspire dashboard — Microsoft Learn
- Aspire dashboard configuration reference — Microsoft Learn
- Aspire dashboard security considerations — Microsoft Learn
- Use OpenTelemetry with OTLP and the standalone Aspire Dashboard — Microsoft Learn
- microsoft/dotnet-aspire-dashboard — Docker Hub
Related Posts
Getting Started With OpenTelemetry in .NET With Jaeger and Seq
A beginner guide to OpenTelemetry in .NET. Add traces, metrics, and logs, then view them in Jaeger and Seq using the OTLP exporter step by step.
Introduction to Distributed Tracing With OpenTelemetry in .NET
A beginner-friendly guide to distributed tracing in .NET with OpenTelemetry. Learn traces, spans, context propagation, and how to add them step by step.
.NET Aspire: A Game Changer for Cloud-Native Development
A beginner-friendly guide to .NET Aspire, the cloud-native stack that orchestrates your services, databases, and dashboards with one simple command.
Monitoring .NET Applications With OpenTelemetry and Grafana
A beginner-friendly guide to monitoring .NET apps with OpenTelemetry and Grafana. Send metrics, traces, and logs to Prometheus, Tempo, and Loki step by step.
Logging Best Practices in ASP.NET Core: A Beginner's Guide
Learn logging best practices in ASP.NET Core: log levels, structured logging, source-generated LoggerMessage, scopes, correlation IDs, and keeping secrets out.
Structured Logging in ASP.NET Core with Serilog: A Beginner's Guide
A friendly, step-by-step guide to structured logging in ASP.NET Core with Serilog: setup, sinks, request logging, and viewing logs on .NET 10.