diff --git a/src/Aspire.Hosting/Aspire.Hosting.csproj b/src/Aspire.Hosting/Aspire.Hosting.csproj
index 5b47ff4cdc4..76868939098 100644
--- a/src/Aspire.Hosting/Aspire.Hosting.csproj
+++ b/src/Aspire.Hosting/Aspire.Hosting.csproj
@@ -21,6 +21,7 @@
     
     
     
+    
     
     
     
diff --git a/src/Aspire.Hosting/Backchannel/AppHostRpcTarget.cs b/src/Aspire.Hosting/Backchannel/AppHostRpcTarget.cs
index fe50ca47efb..b481e2861f0 100644
--- a/src/Aspire.Hosting/Backchannel/AppHostRpcTarget.cs
+++ b/src/Aspire.Hosting/Backchannel/AppHostRpcTarget.cs
@@ -9,6 +9,7 @@
 using Aspire.Hosting.Publishing;
 using Aspire.Hosting.Utils;
 using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
 using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
@@ -21,7 +22,8 @@ internal class AppHostRpcTarget(
     IServiceProvider serviceProvider,
     IDistributedApplicationEventing eventing,
     PublishingActivityProgressReporter activityReporter,
-    IHostApplicationLifetime lifetime
+    IHostApplicationLifetime lifetime,
+    DistributedApplicationOptions options
     ) 
 {
     public async IAsyncEnumerable<(string Id, string StatusText, bool IsComplete, bool IsError)> GetPublishingActivitiesAsync([EnumeratorCancellation]CancellationToken cancellationToken)
@@ -101,6 +103,29 @@ public Task PingAsync(long timestamp, CancellationToken cancellationToken)
 
     public Task<(string BaseUrlWithLoginToken, string? CodespacesUrlWithLoginToken)> GetDashboardUrlsAsync()
     {
+        return GetDashboardUrlsAsync(CancellationToken.None);
+    }
+
+    public async Task<(string BaseUrlWithLoginToken, string? CodespacesUrlWithLoginToken)> GetDashboardUrlsAsync(CancellationToken cancellationToken)
+    {
+        if (!options.DashboardEnabled)
+        {
+            logger.LogError("Dashboard URL requested but dashboard is disabled.");
+            throw new InvalidOperationException("Dashboard URL requested but dashboard is disabled.");
+        }
+
+        // Wait for the dashboard to be healthy before returning the URL. This next statement has several
+        // layers of hacks. Some to work around devcontainer/codespaces port forwarding behavior, and one to
+        // temporarily work around the fact that resource events abuse the state to mark the resource as
+        // hidden instead of having another field. There is a corresponding modification in the ResourceHealthService
+        // which allows the dashboard resource to trigger health reports even though it never enters
+        // the Running state. This is a hack. The reason we can't just check HealthStatus is because
+        // the current implementation of HealthStatus depends on the state of the resource as well.
+        await resourceNotificationService.WaitForResourceAsync(
+            KnownResourceNames.AspireDashboard,
+            re => re.Snapshot.HealthReports.All(h => h.Status == HealthStatus.Healthy),
+            cancellationToken).ConfigureAwait(false);
+
         var dashboardOptions = serviceProvider.GetService>();
 
         if (dashboardOptions is null)
@@ -122,11 +147,11 @@ public Task PingAsync(long timestamp, CancellationToken cancellationToken)
 
         if (baseUrlWithLoginToken == codespacesUrlWithLoginToken)
         {
-            return Task.FromResult<(string, string?)>((baseUrlWithLoginToken, null));
+            return (baseUrlWithLoginToken, null);
         }
         else
         {
-            return Task.FromResult((baseUrlWithLoginToken, codespacesUrlWithLoginToken));
+            return (baseUrlWithLoginToken, codespacesUrlWithLoginToken);
         }
     }
 
diff --git a/src/Aspire.Hosting/Dashboard/DashboardLifecycleHook.cs b/src/Aspire.Hosting/Dashboard/DashboardLifecycleHook.cs
index 2d22a5db2d1..bd9e337b06f 100644
--- a/src/Aspire.Hosting/Dashboard/DashboardLifecycleHook.cs
+++ b/src/Aspire.Hosting/Dashboard/DashboardLifecycleHook.cs
@@ -127,7 +127,6 @@ private void AddDashboardResource(DistributedApplicationModel model)
         nameGenerator.EnsureDcpInstancesPopulated(dashboardResource);
 
         ConfigureAspireDashboardResource(dashboardResource);
-
         // Make the dashboard first in the list so it starts as fast as possible.
         model.Resources.Insert(0, dashboardResource);
     }
@@ -179,6 +178,7 @@ private void ConfigureAspireDashboardResource(IResource dashboardResource)
         dashboardResource.Annotations.Add(new ResourceSnapshotAnnotation(snapshot));
 
         dashboardResource.Annotations.Add(new EnvironmentCallbackAnnotation(ConfigureEnvironmentVariables));
+        dashboardResource.Annotations.Add(new HealthCheckAnnotation(KnownHealthCheckNames.DasboardHealthCheck));
     }
 
     internal async Task ConfigureEnvironmentVariables(EnvironmentCallbackContext context)
diff --git a/src/Aspire.Hosting/DistributedApplicationBuilder.cs b/src/Aspire.Hosting/DistributedApplicationBuilder.cs
index 9eb7c04ebc4..fcab487bad2 100644
--- a/src/Aspire.Hosting/DistributedApplicationBuilder.cs
+++ b/src/Aspire.Hosting/DistributedApplicationBuilder.cs
@@ -19,6 +19,7 @@
 using Aspire.Hosting.Lifecycle;
 using Aspire.Hosting.Orchestrator;
 using Aspire.Hosting.Publishing;
+using Aspire.Hosting.Utils;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -331,6 +332,20 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options)
                 _innerBuilder.Services.AddLifecycleHook();
                 _innerBuilder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, ConfigureDefaultDashboardOptions>());
                 _innerBuilder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, ValidateDashboardOptions>());
+
+                // Dashboard health check.
+                _innerBuilder.Services.AddHealthChecks().AddUrlGroup(sp => {
+
+                    var dashboardOptions = sp.GetRequiredService>().Value;
+                    if (StringUtils.TryGetUriFromDelimitedString(dashboardOptions.DashboardUrl, ";", out var firstDashboardUrl))
+                    {
+                        return firstDashboardUrl;
+                    }
+                    else
+                    {
+                        throw new DistributedApplicationException($"The dashboard resource '{KnownResourceNames.AspireDashboard}' does not have endpoints.");
+                    }
+                }, KnownHealthCheckNames.DasboardHealthCheck);
             }
 
             if (options.EnableResourceLogging)
diff --git a/src/Aspire.Hosting/Health/ResourceHealthCheckService.cs b/src/Aspire.Hosting/Health/ResourceHealthCheckService.cs
index bf831314053..cefe7f32102 100644
--- a/src/Aspire.Hosting/Health/ResourceHealthCheckService.cs
+++ b/src/Aspire.Hosting/Health/ResourceHealthCheckService.cs
@@ -39,7 +39,10 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
                     }
                 }
 
-                if (resourceEvent.Snapshot.State?.Text == KnownResourceStates.Running)
+                // HACK: We are special casing the Aspire dashboard here until we address the issue of the Hidden state
+                //       making it impossible to determine whether a hidden resource is running or not. When that change
+                //       is made we can remove the special case logic here for the dashboard.
+                if (resourceEvent.Snapshot.State?.Text == KnownResourceStates.Running || resourceEvent.Resource.Name == KnownResourceNames.AspireDashboard)
                 {
                     if (state == null)
                     {
diff --git a/src/Shared/KnownHealthCheckNames.cs b/src/Shared/KnownHealthCheckNames.cs
new file mode 100644
index 00000000000..5bf0276584c
--- /dev/null
+++ b/src/Shared/KnownHealthCheckNames.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Aspire;
+
+internal static class KnownHealthCheckNames
+{
+    /// 
+    /// Common name for dashboard health check.
+    /// 
+    public const string DasboardHealthCheck = "aspire_dashboard_check";
+}