Introduction

Containerized applications make development and deployment more consistent, but debugging a distributed application inside containers can still be difficult.

A developer may need to inspect several services at the same time:

Frontend
   |
   v
API
   |
   +---- PostgreSQL
   |
   +---- Redis
   |
   +---- Message Broker

When each service runs inside a separate container, finding the source of a problem can involve logs, ports, environment variables, health checks, and process state.

.NET Aspire is designed to make local distributed application development easier by providing an application model and developer dashboard. Aspire 13.6 expands the container-development experience, including improved ways to work with and debug containerized resources directly from the dashboard.

This article explains how the container debugging workflow works, what developers should inspect when a service fails, and how to use the Aspire dashboard as a central place for troubleshooting distributed .NET applications.

What Is .NET Aspire?

.NET Aspire is a development stack for building and running distributed applications.

Instead of configuring every service independently, developers can describe application resources through an Aspire AppHost.

A simplified application might look like:

AppHost
   |
   +---- API
   |
   +---- Web App
   |
   +---- PostgreSQL
   |
   +---- Redis

The AppHost describes how these resources relate to each other.

Aspire then provides tooling for running and observing the application during development.

What Is the Aspire Dashboard?

The Aspire Dashboard provides a centralized view of the resources participating in an Aspire application.

Depending on the application configuration, developers can inspect information such as:

  • Resources

  • Logs

  • Traces

  • Metrics

  • Resource status

  • Endpoints

  • Environment information

  • Health information

Instead of opening separate terminals for every service, developers can start with one view of the distributed system.

A simplified dashboard workflow looks like:

Aspire Dashboard
       |
       +---- API
       |      +---- Logs
       |      +---- Traces
       |      +---- Metrics
       |
       +---- Worker
       |
       +---- Database
       |
       +---- Cache

Why Container Debugging Matters

Containers introduce another execution boundary.

A developer might see:

Application works locally

but:

Application fails in container

The difference can come from:

  • Environment variables

  • File paths

  • Network configuration

  • Port mappings

  • Container images

  • Missing dependencies

  • Runtime versions

  • Health checks

  • Container startup order

Debugging therefore requires visibility into both the application and the container environment.

Aspire and Containerized Resources

Aspire applications can include containerized dependencies and application resources.

For example:

var builder = DistributedApplication.CreateBuilder(args);

var postgres = builder.AddPostgres("postgres")
    .AddDatabase("appdb");

var cache = builder.AddRedis("cache");

builder.AddProject<Projects.Api>("api")
    .WithReference(postgres)
    .WithReference(cache);

builder.Build().Run();

The AppHost describes the resources rather than requiring every dependency to be started manually.

When the application starts, the Aspire dashboard provides a centralized view of these resources.

What Changes in the Container Debugging Workflow?

Traditional container debugging often starts from the command line.

For example:

docker ps

followed by:

docker logs <container>

and then:

docker exec -it <container> /bin/sh

These commands remain useful, but a distributed application can require many commands across many containers.

The Aspire dashboard provides a higher-level view where developers can identify the failing resource first and then investigate its runtime information.

The workflow becomes:

Run Application
      |
      v
Open Aspire Dashboard
      |
      v
Find Failed Resource
      |
      v
Inspect Logs / Traces / Metrics
      |
      v
Debug Application or Container

Starting a Containerized Aspire Application

A typical development workflow begins with the AppHost project.

For example:

dotnet run --project AppHost

Aspire starts the configured resources and makes the dashboard available.

The exact startup behavior depends on the AppHost configuration and the resources included in the solution.

Once the dashboard opens, begin by looking at the resource list.

Understanding Resource Status

A healthy application might show:

api        Running
worker     Running
postgres   Running
redis      Running

A problem may appear as:

api        Failed
worker     Running
postgres   Running
redis      Running

This immediately narrows the investigation.

Instead of debugging the entire application, start with the resource whose state differs from the expected state.

Inspecting Logs

Logs are usually the first place to look when a containerized service fails.

Suppose the API container starts and then exits.

The logs may reveal:

Connection refused
Configuration value missing
Unable to load assembly
Database unavailable
Port already in use

The important advantage of centralized logs is correlation.

You can compare:

API logs
Database logs
Worker logs

instead of opening multiple terminal sessions.

Debugging Application Code

Container debugging becomes more useful when you can move from a runtime failure to the application code responsible for it.

For example:

HTTP Request
    |
    v
API Container
    |
    v
Controller
    |
    v
Service
    |
    v
Database

Suppose the API returns an unexpected result.

A developer can inspect the relevant application code using the normal .NET debugging workflow while using the Aspire dashboard to understand the surrounding distributed system.

This distinction matters.

The dashboard gives you system visibility.

The debugger gives you code-level inspection.

The two complement each other.

Debugging a Database Connection

One common container problem is database connectivity.

Consider:

API Container
     |
     | connection string
     v
PostgreSQL Container

If the API cannot connect, inspect the following:

  1. Is PostgreSQL running?

  2. Is the database resource healthy?

  3. Is the API referencing the correct resource?

  4. Is the connection string being injected correctly?

  5. Is the application using the correct database name?

  6. Is the expected endpoint available?

With Aspire resource references, connection information can be managed as part of the application model rather than manually copied into multiple configuration files.

Debugging Redis and Other Dependencies

The same approach applies to Redis and other containerized services.

For example:

var redis = builder.AddRedis("cache");

builder.AddProject<Projects.Api>("api")
    .WithReference(redis);

If the API reports cache connection failures, first inspect the Redis resource.

This is faster than assuming the problem is inside application code.

The dependency itself may have failed to start.

Environment Variables

Container problems are frequently configuration problems.

An application may expect:

DATABASE_HOST
DATABASE_NAME
CACHE_ENDPOINT
API_KEY

but the container may not receive the expected values.

When debugging, compare:

Developer Environment
        |
        v
AppHost Configuration
        |
        v
Container Environment
        |
        v
Application Configuration

A missing environment variable can cause an application to fail before the first HTTP request is processed.

Port Problems

Port configuration is another common source of confusion.

A service may listen on one port inside the container while a different port is exposed to the host.

Conceptually:

Host
Port 5000
   |
   v
Container
Port 8080

Do not assume the host port and container port are identical.

When an endpoint does not respond, inspect the resource's endpoint configuration rather than changing ports randomly.

Health Checks

Health checks help distinguish between a running container and a usable application.

For example:

Container: Running
Application: Unhealthy

These are different states.

A container can be alive while the application inside it cannot connect to a database or complete its startup process.

Health information can therefore shorten troubleshooting by identifying dependencies that are not ready.

Distributed Tracing

Logs tell you what individual services reported.

Traces help show how a request moved through multiple services.

For example:

HTTP Request
     |
     v
API
     |
     v
Order Service
     |
     v
Database

If the request becomes slow, tracing can help identify which part of the request path consumed the time.

This becomes increasingly useful as the number of services grows.

Metrics and Container Behavior

Metrics provide another view of the application.

A service may technically be healthy but show:

  • High CPU

  • Increasing memory usage

  • Unusual request latency

  • Growing request volume

  • Dependency failures

A dashboard that combines logs, traces, and metrics gives developers more context than logs alone.

A Practical Debugging Scenario

Imagine an Aspire application with:

Web
 |
 v
API
 |
 +---- PostgreSQL
 |
 +---- Redis

The web application reports that orders cannot be loaded.

Start with the dashboard.

Step 1: Check Resource State

Web       Running
API       Running
Postgres  Running
Redis     Running

No obvious container has failed.

Step 2: Inspect API Logs

You find:

Timeout while executing database query

Step 3: Inspect Database

PostgreSQL is running, but metrics indicate unusually high resource consumption.

Step 4: Inspect the Trace

The request spends most of its time waiting for the database.

Step 5: Debug Application Code

Inspect the query and determine whether it is missing an appropriate index or processing too much data.

The problem was not a container startup failure. It was an application performance issue visible through the distributed runtime.

This is where a centralized dashboard becomes valuable.

Containers vs Local Processes

Aspire applications can involve a mixture of resources.

For example:

API              Local Process
Worker           Container
PostgreSQL       Container
Redis            Container
Frontend         Local Process

The dashboard provides a common view even though the underlying execution models differ.

This is useful during development because teams do not need to mentally separate every service into a different debugging workflow.

When to Use Container Debugging

Container debugging is particularly useful when the issue appears only after containerization.

Examples include:

  • Different environment variables

  • Missing files

  • Incorrect working directory

  • Different runtime versions

  • Network connectivity issues

  • Port mapping problems

  • Container startup failures

  • Dependency readiness problems

  • OS-level differences

If the application behaves correctly outside the container but fails inside it, inspect the container environment before rewriting application logic.

Common Mistakes

Debugging the Wrong Resource

Start with resource status and logs before changing application code.

Assuming Running Means Healthy

A running container can still contain an unhealthy application.

Ignoring Dependency Failures

An API may fail because PostgreSQL or Redis failed earlier.

Hardcoding Container Addresses

Use Aspire resource references and configuration mechanisms rather than manually maintaining container IP addresses.

Treating Production and Development as Identical

Local Aspire environments are designed for development workflows. Production container orchestration may have different networking, security, scaling, and observability requirements.

Changing Multiple Things at Once

Change one configuration or code path at a time so you can identify the actual cause.

Troubleshooting Checklist

When a containerized Aspire resource fails, follow this sequence:

[ ] Check resource status
[ ] Inspect startup logs
[ ] Check health status
[ ] Verify dependencies
[ ] Check environment variables
[ ] Check endpoints and ports
[ ] Inspect traces
[ ] Review metrics
[ ] Attach a debugger when appropriate
[ ] Reproduce the failure
[ ] Fix the smallest relevant component
[ ] Restart and verify

This approach prevents random configuration changes from making the original problem harder to diagnose.

Best Practices

  1. Keep the AppHost configuration readable.

  2. Give resources meaningful names.

  3. Use resource references instead of hardcoded dependency addresses.

  4. Check logs before changing code.

  5. Use health checks for important dependencies.

  6. Use traces for distributed request problems.

  7. Use metrics to investigate performance issues.

  8. Test containerized behavior early in development.

  9. Keep development and production assumptions separate.

  10. Document custom container configuration.

  11. Keep container images and runtimes current.

  12. Reproduce issues with the same configuration used by the failing environment.

Advantages and Disadvantages

Advantages

  • Centralized resource visibility

  • Easier log inspection

  • Better understanding of distributed dependencies

  • Faster identification of failed resources

  • Useful combination of logs, traces, and metrics

  • Simplifies local distributed application development

  • Reduces the need to manage every service independently

Disadvantages

  • Adds another development abstraction

  • Does not replace application-level debugging

  • Container-specific problems still require container knowledge

  • Production environments may behave differently

  • Complex distributed applications still require systematic troubleshooting

Summary

.NET Aspire's dashboard provides a useful control point for debugging distributed applications that include containerized resources.

Instead of starting with individual container commands, developers can begin with resource status, logs, traces, metrics, endpoints, and health information. This makes it easier to determine whether a failure originates in application code, a dependency, configuration, networking, or the container environment itself.

The most effective workflow combines Aspire's distributed-system visibility with normal .NET debugging tools. Use the dashboard to understand what is happening across the application, then move into code-level debugging when the failing component has been identified.

For teams building containerized .NET applications, this approach can make local debugging considerably more structured and repeatable.