diff --git a/src/Aspire.Cli/Utils/ConsoleActivityLogger.cs b/src/Aspire.Cli/Utils/ConsoleActivityLogger.cs
index 3276b1a9ecc..86a61bc3ff1 100644
--- a/src/Aspire.Cli/Utils/ConsoleActivityLogger.cs
+++ b/src/Aspire.Cli/Utils/ConsoleActivityLogger.cs
@@ -283,14 +283,14 @@ public void SetFinalResult(bool succeeded)
         if (succeeded)
         {
             _finalStatusHeader = _enableColor
-                ? $"[green]{SuccessSymbol} DEPLOYMENT SUCCEEDED[/]"
-                : $"{SuccessSymbol} DEPLOYMENT SUCCEEDED";
+                ? $"[green]{SuccessSymbol} PIPELINE SUCCEEDED[/]"
+                : $"{SuccessSymbol} PIPELINE SUCCEEDED";
         }
         else
         {
             _finalStatusHeader = _enableColor
-                ? $"[red]{FailureSymbol} DEPLOYMENT FAILED[/]"
-                : $"{FailureSymbol} DEPLOYMENT FAILED";
+                ? $"[red]{FailureSymbol} PIPELINE FAILED[/]"
+                : $"{FailureSymbol} PIPELINE FAILED";
         }
     }
 
diff --git a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppEnvironmentResource.cs b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppEnvironmentResource.cs
index b2c41c06be8..581f0630fcd 100644
--- a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppEnvironmentResource.cs
+++ b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppEnvironmentResource.cs
@@ -1,7 +1,13 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+#pragma warning disable ASPIREPIPELINES001
+#pragma warning disable ASPIREAZURE001
+#pragma warning disable ASPIRECOMPUTE001
+#pragma warning disable ASPIREPUBLISHERS001
+
 using Aspire.Hosting.ApplicationModel;
+using Aspire.Hosting.Pipelines;
 using Azure.Provisioning;
 using Azure.Provisioning.AppContainers;
 using Azure.Provisioning.Primitives;
@@ -11,13 +17,135 @@ namespace Aspire.Hosting.Azure.AppContainers;
 /// 
 /// Represents an Azure Container App Environment resource.
 /// 
-/// The name of the Container App Environment.
-/// The callback to configure the Azure infrastructure for this resource.
-public class AzureContainerAppEnvironmentResource(string name, Action configureInfrastructure) :
-#pragma warning disable ASPIRECOMPUTE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
-    AzureProvisioningResource(name, configureInfrastructure), IAzureComputeEnvironmentResource, IAzureContainerRegistry
-#pragma warning restore ASPIRECOMPUTE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+public class AzureContainerAppEnvironmentResource :
+    AzureProvisioningResource, IAzureComputeEnvironmentResource, IAzureContainerRegistry
 {
+    /// 
+    /// Initializes a new instance of the  class.
+    /// 
+    /// The name of the Container App Environment.
+    /// The callback to configure the Azure infrastructure for this resource.
+    public AzureContainerAppEnvironmentResource(string name, Action configureInfrastructure)
+        : base(name, configureInfrastructure)
+    {
+        // Add pipeline step annotation to create steps and expand deployment target steps
+        Annotations.Add(new PipelineStepAnnotation(async (factoryContext) =>
+        {
+            var model = factoryContext.PipelineContext.Model;
+            var steps = new List();
+
+            var loginToAcrStep = new PipelineStep
+            {
+                Name = $"login-to-acr-{name}",
+                Action = context => AzureEnvironmentResourceHelpers.LoginToRegistryAsync(this, context),
+                Tags = ["acr-login"]
+            };
+
+            // Add print-dashboard-url step
+            var printDashboardUrlStep = new PipelineStep
+            {
+                Name = $"print-dashboard-url-{name}",
+                Action = ctx => PrintDashboardUrlAsync(ctx),
+                Tags = ["print-summary"],
+                DependsOnSteps = [AzureEnvironmentResource.ProvisionInfrastructureStepName],
+                RequiredBySteps = [WellKnownPipelineSteps.Deploy]
+            };
+
+            steps.Add(loginToAcrStep);
+            steps.Add(printDashboardUrlStep);
+
+            // Expand deployment target steps for all compute resources
+            // This ensures the push/provision steps from deployment targets are included in the pipeline
+            foreach (var computeResource in model.GetComputeResources())
+            {
+                var deploymentTarget = computeResource.GetDeploymentTargetAnnotation(this)?.DeploymentTarget;
+
+                if (deploymentTarget != null && deploymentTarget.TryGetAnnotationsOfType(out var annotations))
+                {
+                    // Resolve the deployment target's PipelineStepAnnotation and expand its steps
+                    // We do this because the deployment target is not in the model
+                    foreach (var annotation in annotations)
+                    {
+                        var childFactoryContext = new PipelineStepFactoryContext
+                        {
+                            PipelineContext = factoryContext.PipelineContext,
+                            Resource = deploymentTarget
+                        };
+
+                        var deploymentTargetSteps = await annotation.CreateStepsAsync(childFactoryContext).ConfigureAwait(false);
+
+                        foreach (var step in deploymentTargetSteps)
+                        {
+                            // Ensure the step is associated with the deployment target resource
+                            step.Resource ??= deploymentTarget;
+                        }
+
+                        steps.AddRange(deploymentTargetSteps);
+                    }
+                }
+            }
+
+            return steps;
+        }));
+
+        // Add pipeline configuration annotation to wire up dependencies
+        // This is where we wire up the build steps created by the resources
+        Annotations.Add(new PipelineConfigurationAnnotation(context =>
+        {
+            var acrLoginSteps = context.GetSteps(this, "acr-login");
+
+            // Wire up build step dependencies
+            // Build steps are created by ProjectResource and ContainerResource
+            foreach (var computeResource in context.Model.GetComputeResources())
+            {
+                var deploymentTarget = computeResource.GetDeploymentTargetAnnotation(this)?.DeploymentTarget;
+
+                if (deploymentTarget is null)
+                {
+                    continue;
+                }
+
+                // Execute the PipelineConfigurationAnnotation callbacks on the deployment target
+                if (deploymentTarget.TryGetAnnotationsOfType(out var annotations))
+                {
+                    foreach (var annotation in annotations)
+                    {
+                        annotation.Callback(context);
+                    }
+                }
+
+                context.GetSteps(deploymentTarget, WellKnownPipelineTags.PushContainerImage)
+                       .DependsOn(acrLoginSteps);
+            }
+
+            // This ensures that resources that have to be built before deployments are handled
+            foreach (var computeResource in context.Model.GetBuildResources())
+            {
+                context.GetSteps(computeResource, WellKnownPipelineTags.BuildCompute)
+                        .RequiredBy(WellKnownPipelineSteps.Deploy)
+                        .DependsOn(WellKnownPipelineSteps.DeployPrereq);
+            }
+
+            // Make print-summary step depend on provisioning of this environment
+            var provisionSteps = context.GetSteps(this, WellKnownPipelineTags.ProvisionInfrastructure);
+            var printDashboardUrlSteps = context.GetSteps(this, "print-summary");
+            printDashboardUrlSteps.DependsOn(provisionSteps);
+
+            acrLoginSteps.DependsOn(provisionSteps);
+        }));
+    }
+
+    private async Task PrintDashboardUrlAsync(PipelineStepContext context)
+    {
+        var domainValue = await ContainerAppDomain.GetValueAsync(context.CancellationToken).ConfigureAwait(false);
+
+        var dashboardUrl = $"https://aspire-dashboard.ext.{domainValue}";
+
+        await context.ReportingStep.CompleteAsync(
+            $"Dashboard available at [dashboard URL]({dashboardUrl})",
+            CompletionState.Completed,
+            context.CancellationToken).ConfigureAwait(false);
+    }
     internal bool UseAzdNamingConvention { get; set; }
 
     /// 
diff --git a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppResource.cs b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppResource.cs
index bf69a22f348..f8ba630359c 100644
--- a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppResource.cs
+++ b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppResource.cs
@@ -1,21 +1,149 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+#pragma warning disable ASPIRECOMPUTE001
+#pragma warning disable ASPIREPIPELINES001
+#pragma warning disable ASPIREPUBLISHERS001
+#pragma warning disable ASPIREAZURE001
+#pragma warning disable ASPIREPIPELINES003
+
 using Aspire.Hosting.ApplicationModel;
+using Aspire.Hosting.Pipelines;
+using Aspire.Hosting.Publishing;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
 
 namespace Aspire.Hosting.Azure.AppContainers;
 
 /// 
 /// Represents an Azure Container App resource.
 /// 
-/// The name of the resource in the Aspire application model.
-/// Callback to configure the Azure resources.
-/// The target compute resource that this Azure Container App is being created for.
-public class AzureContainerAppResource(string name, Action configureInfrastructure, IResource targetResource)
-    : AzureProvisioningResource(name, configureInfrastructure)
+public class AzureContainerAppResource : AzureProvisioningResource
 {
+    /// 
+    /// Initializes a new instance of the  class.
+    /// 
+    /// The name of the resource in the Aspire application model.
+    /// Callback to configure the Azure resources.
+    /// The target compute resource that this Azure Container App is being created for.
+    public AzureContainerAppResource(string name, Action configureInfrastructure, IResource targetResource)
+        : base(name, configureInfrastructure)
+    {
+        TargetResource = targetResource;
+
+        // Add pipeline step annotation for push
+        Annotations.Add(new PipelineStepAnnotation((factoryContext) =>
+        {
+            // Get the registry from the target resource's deployment target annotation
+            var deploymentTargetAnnotation = targetResource.GetDeploymentTargetAnnotation();
+            if (deploymentTargetAnnotation?.ContainerRegistry is not IContainerRegistry registry)
+            {
+                // No registry available, skip push
+                return [];
+            }
+
+            var steps = new List();
+
+            if (targetResource.RequiresImageBuildAndPush())
+            {
+                // Create push step for this deployment target
+                var pushStep = new PipelineStep
+                {
+                    Name = $"push-{targetResource.Name}",
+                    Action = async ctx =>
+                    {
+                        var containerImageBuilder = ctx.Services.GetRequiredService();
+
+                        await AzureEnvironmentResourceHelpers.PushImageToRegistryAsync(
+                            registry,
+                            targetResource,
+                            ctx,
+                            containerImageBuilder).ConfigureAwait(false);
+                    },
+                    Tags = [WellKnownPipelineTags.PushContainerImage]
+                };
+
+                steps.Add(pushStep);
+            }
+
+            if (!targetResource.TryGetEndpoints(out var endpoints))
+            {
+                endpoints = [];
+            }
+
+            var anyPublicEndpoints = endpoints.Any(e => e.IsExternal);
+
+            var printResourceSummary = new PipelineStep
+            {
+                Name = $"print-{targetResource.Name}-summary",
+                Action = async ctx =>
+                {
+                    var containerAppEnv = (AzureContainerAppEnvironmentResource)deploymentTargetAnnotation.ComputeEnvironment!;
+
+                    var domainValue = await containerAppEnv.ContainerAppDomain.GetValueAsync(ctx.CancellationToken).ConfigureAwait(false);
+
+                    if (anyPublicEndpoints)
+                    {
+                        var endpoint = $"https://{targetResource.Name.ToLowerInvariant()}.{domainValue}";
+
+                        ctx.ReportingStep.Log(LogLevel.Information, $"Successfully deployed **{targetResource.Name}** to [{endpoint}]({endpoint})", enableMarkdown: true);
+                    }
+                    else
+                    {
+                        ctx.ReportingStep.Log(LogLevel.Information, $"Successfully deployed **{targetResource.Name}** to Azure Container Apps environment **{containerAppEnv.Name}**. No public endpoints were configured.", enableMarkdown: true);
+                    }
+                },
+                Tags = ["print-summary"],
+                RequiredBySteps = [WellKnownPipelineSteps.Deploy]
+            };
+
+            var deployStep = new PipelineStep
+            {
+                Name = $"deploy-{targetResource.Name}",
+                Action = _ => Task.CompletedTask,
+                Tags = [WellKnownPipelineTags.DeployCompute]
+            };
+
+            deployStep.DependsOn(printResourceSummary);
+
+            steps.Add(printResourceSummary);
+            steps.Add(deployStep);
+
+            return steps;
+        }));
+
+        // Add pipeline configuration annotation to wire up dependencies
+        Annotations.Add(new PipelineConfigurationAnnotation((context) =>
+        {
+            // Find the push step for this resource
+            var pushSteps = context.GetSteps(this, WellKnownPipelineTags.PushContainerImage);
+
+            // Make push step depend on build steps of the target resource
+            var buildSteps = context.GetSteps(targetResource, WellKnownPipelineTags.BuildCompute);
+
+            pushSteps.DependsOn(buildSteps);
+
+            // Make push step depend on the registry being provisioned
+            var deploymentTargetAnnotation = targetResource.GetDeploymentTargetAnnotation();
+            if (deploymentTargetAnnotation?.ContainerRegistry is IResource registryResource)
+            {
+                var registryProvisionSteps = context.GetSteps(registryResource, WellKnownPipelineTags.ProvisionInfrastructure);
+
+                pushSteps.DependsOn(registryProvisionSteps);
+            }
+
+            var provisionSteps = context.GetSteps(this, WellKnownPipelineTags.ProvisionInfrastructure);
+
+            // Make provision steps depend on push steps
+            provisionSteps.DependsOn(pushSteps);
+
+            // Ensure summary step runs after provision
+            context.GetSteps(this, "print-summary").DependsOn(provisionSteps);
+        }));
+    }
+
     /// 
     /// Gets the target resource that this Azure Container App is being created for.
     /// 
-    public IResource TargetResource { get; } = targetResource;
+    public IResource TargetResource { get; }
 }
diff --git a/src/Aspire.Hosting.Azure.AppContainers/ContainerAppEnvironmentContext.cs b/src/Aspire.Hosting.Azure.AppContainers/ContainerAppEnvironmentContext.cs
index b40a06b1084..3387616d398 100644
--- a/src/Aspire.Hosting.Azure.AppContainers/ContainerAppEnvironmentContext.cs
+++ b/src/Aspire.Hosting.Azure.AppContainers/ContainerAppEnvironmentContext.cs
@@ -43,7 +43,7 @@ public async Task CreateContainerAppAsync(IResource resource
             await context.ProcessResourceAsync(cancellationToken).ConfigureAwait(false);
         }
 
-        var provisioningResource = new AzureContainerAppResource(resource.Name, context.BuildContainerApp, resource)
+        var provisioningResource = new AzureContainerAppResource(resource.Name + "-containerapp", context.BuildContainerApp, resource)
         {
             ProvisioningBuildOptions = provisioningOptions.ProvisioningBuildOptions
         };
diff --git a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentContext.cs b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentContext.cs
index dae7f2d65d9..b451f2f1da3 100644
--- a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentContext.cs
+++ b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentContext.cs
@@ -41,7 +41,7 @@ public async Task CreateAppServiceAsync(IResource resource,
             await context.ProcessAsync(cancellationToken).ConfigureAwait(false);
         }
 
-        var provisioningResource = new AzureAppServiceWebSiteResource(resource.Name, context.BuildWebSite, resource)
+        var provisioningResource = new AzureAppServiceWebSiteResource(resource.Name + "-website", context.BuildWebSite, resource)
         {
             ProvisioningBuildOptions = provisioningOptions.ProvisioningBuildOptions
         };
diff --git a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentResource.cs b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentResource.cs
index 62788a0a46e..0a4309cc7d2 100644
--- a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentResource.cs
+++ b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentResource.cs
@@ -1,7 +1,13 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+#pragma warning disable ASPIREPIPELINES001
+#pragma warning disable ASPIREAZURE001
+#pragma warning disable ASPIRECOMPUTE001
+#pragma warning disable ASPIREPUBLISHERS001
+
 using Aspire.Hosting.ApplicationModel;
+using Aspire.Hosting.Pipelines;
 using Azure.Provisioning;
 using Azure.Provisioning.AppService;
 using Azure.Provisioning.Expressions;
@@ -12,15 +18,134 @@ namespace Aspire.Hosting.Azure;
 /// 
 /// Represents an Azure App Service Environment resource.
 /// 
-/// The name of the Azure App Service Environment.
-/// The callback to configure the Azure infrastructure for this resource.
-public class AzureAppServiceEnvironmentResource(string name, Action configureInfrastructure) :
-    AzureProvisioningResource(name, configureInfrastructure),
-#pragma warning disable ASPIRECOMPUTE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+public class AzureAppServiceEnvironmentResource :
+    AzureProvisioningResource,
     IAzureComputeEnvironmentResource,
     IAzureContainerRegistry
-#pragma warning restore ASPIRECOMPUTE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
 {
+    /// 
+    /// Initializes a new instance of the  class.
+    /// 
+    /// The name of the Azure App Service Environment.
+    /// The callback to configure the Azure infrastructure for this resource.
+    public AzureAppServiceEnvironmentResource(string name, Action configureInfrastructure)
+        : base(name, configureInfrastructure)
+    {
+        // Add pipeline step annotation to create steps and expand deployment target steps
+        Annotations.Add(new PipelineStepAnnotation(async (factoryContext) =>
+        {
+            var model = factoryContext.PipelineContext.Model;
+            var steps = new List();
+
+            var loginToAcrStep = new PipelineStep
+            {
+                Name = $"login-to-acr-{name}",
+                Action = context => AzureEnvironmentResourceHelpers.LoginToRegistryAsync(this, context),
+                Tags = ["acr-login"]
+            };
+
+            // Add print-dashboard-url step
+            var printDashboardUrlStep = new PipelineStep
+            {
+                Name = $"print-dashboard-url-{name}",
+                Action = ctx => PrintDashboardUrlAsync(ctx),
+                Tags = ["print-summary"],
+                DependsOnSteps = [AzureEnvironmentResource.ProvisionInfrastructureStepName],
+                RequiredBySteps = [WellKnownPipelineSteps.Deploy]
+            };
+
+            steps.Add(loginToAcrStep);
+            steps.Add(printDashboardUrlStep);
+
+            // Expand deployment target steps for all compute resources
+            // This ensures the push/provision steps from deployment targets are included in the pipeline
+            foreach (var computeResource in model.GetComputeResources())
+            {
+                var deploymentTarget = computeResource.GetDeploymentTargetAnnotation(this)?.DeploymentTarget;
+
+                if (deploymentTarget != null && deploymentTarget.TryGetAnnotationsOfType(out var annotations))
+                {
+                    // Resolve the deployment target's PipelineStepAnnotation and expand its steps
+                    // We do this because the deployment target is not in the model
+                    foreach (var annotation in annotations)
+                    {
+                        var childFactoryContext = new PipelineStepFactoryContext
+                        {
+                            PipelineContext = factoryContext.PipelineContext,
+                            Resource = deploymentTarget
+                        };
+
+                        var deploymentTargetSteps = await annotation.CreateStepsAsync(childFactoryContext).ConfigureAwait(false);
+
+                        foreach (var step in deploymentTargetSteps)
+                        {
+                            // Ensure the step is associated with the deployment target resource
+                            step.Resource ??= deploymentTarget;
+                        }
+
+                        steps.AddRange(deploymentTargetSteps);
+                    }
+                }
+            }
+
+            return steps;
+        }));
+
+        // Add pipeline configuration annotation to wire up dependencies
+        // This is where we wire up the build steps created by the resources
+        Annotations.Add(new PipelineConfigurationAnnotation(context =>
+        {
+            var acrLoginSteps = context.GetSteps(this, "acr-login");
+
+            // Wire up build step dependencies
+            // Build steps are created by ProjectResource and ContainerResource
+            foreach (var computeResource in context.Model.GetComputeResources())
+            {
+                var deploymentTarget = computeResource.GetDeploymentTargetAnnotation(this)?.DeploymentTarget;
+
+                if (deploymentTarget is null)
+                {
+                    continue;
+                }
+
+                // Execute the PipelineConfigurationAnnotation callbacks on the deployment target
+                if (deploymentTarget.TryGetAnnotationsOfType(out var annotations))
+                {
+                    foreach (var annotation in annotations)
+                    {
+                        annotation.Callback(context);
+                    }
+                }
+
+                context.GetSteps(deploymentTarget, WellKnownPipelineTags.PushContainerImage)
+                       .DependsOn(acrLoginSteps);
+            }
+
+            // This ensures that resources that have to be built before deployments are handled
+            foreach (var computeResource in context.Model.GetBuildResources())
+            {
+                context.GetSteps(computeResource, WellKnownPipelineTags.BuildCompute)
+                        .RequiredBy(WellKnownPipelineSteps.Deploy)
+                        .DependsOn(WellKnownPipelineSteps.DeployPrereq);
+            }
+
+            // Make print-summary step depend on provisioning of this environment
+            var printSummarySteps = context.GetSteps(this, "print-summary");
+            var provisionSteps = context.GetSteps(this, WellKnownPipelineTags.ProvisionInfrastructure);
+            printSummarySteps.DependsOn(provisionSteps);
+        }));
+    }
+
+    private async Task PrintDashboardUrlAsync(PipelineStepContext context)
+    {
+        var dashboardUri = await DashboardUriReference.GetValueAsync(context.CancellationToken).ConfigureAwait(false);
+
+        await context.ReportingStep.CompleteAsync(
+            $"Dashboard available at [dashboard URL]({dashboardUri})",
+            CompletionState.Completed,
+            context.CancellationToken).ConfigureAwait(false);
+    }
+
     // We don't want these to be public if we end up with an app service
     // per compute resource.
     internal BicepOutputReference PlanIdOutputReference => new("planId", this);
@@ -34,7 +159,7 @@ public class AzureAppServiceEnvironmentResource(string name, Action
     /// Gets the suffix added to each web app created in this App Service Environment.
     /// 
-    private BicepOutputReference WebSiteSuffix => new("webSiteSuffix", this);
+    internal BicepOutputReference WebSiteSuffix => new("webSiteSuffix", this);
 
     /// 
     /// Gets or sets a value indicating whether the Aspire dashboard should be included in the container app environment.
@@ -87,13 +212,13 @@ public class AzureAppServiceEnvironmentResource(string name, Action GetWebSiteSuffixBicep() =>
         BicepFunction.GetUniqueString(BicepFunction.GetResourceGroup().Id);
 
-    ReferenceExpression IAzureContainerRegistry.ManagedIdentityId => 
+    ReferenceExpression IAzureContainerRegistry.ManagedIdentityId =>
         ReferenceExpression.Create($"{ContainerRegistryManagedIdentityId}");
 
-    ReferenceExpression IContainerRegistry.Name => 
+    ReferenceExpression IContainerRegistry.Name =>
         ReferenceExpression.Create($"{ContainerRegistryName}");
 
-    ReferenceExpression IContainerRegistry.Endpoint => 
+    ReferenceExpression IContainerRegistry.Endpoint =>
         ReferenceExpression.Create($"{ContainerRegistryUrl}");
 
     ReferenceExpression IComputeEnvironmentResource.GetHostAddressExpression(EndpointReference endpointReference)
@@ -107,15 +232,15 @@ public override ProvisionableResource AddAsExistingResource(AzureResourceInfrast
     {
         var bicepIdentifier = this.GetBicepIdentifier();
         var resources = infra.GetProvisionableResources();
-        
+
         // Check if an AppServicePlan with the same identifier already exists
         var existingPlan = resources.OfType().SingleOrDefault(plan => plan.BicepIdentifier == bicepIdentifier);
-        
+
         if (existingPlan is not null)
         {
             return existingPlan;
         }
-        
+
         // Create and add new resource if it doesn't exist
         var plan = AppServicePlan.FromExisting(bicepIdentifier);
 
diff --git a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceWebSiteResource.cs b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceWebSiteResource.cs
index a5274e5b6c6..2f30ac7bb51 100644
--- a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceWebSiteResource.cs
+++ b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceWebSiteResource.cs
@@ -1,21 +1,143 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+#pragma warning disable ASPIRECOMPUTE001
+#pragma warning disable ASPIREPIPELINES001
+#pragma warning disable ASPIREPUBLISHERS001
+#pragma warning disable ASPIREAZURE001
+#pragma warning disable ASPIREPIPELINES003
+
 using Aspire.Hosting.ApplicationModel;
+using Aspire.Hosting.Pipelines;
+using Aspire.Hosting.Publishing;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
 
 namespace Aspire.Hosting.Azure;
 
 /// 
 /// Represents an Azure App Service Web Site resource.
 /// 
-/// The name of the resource in the Aspire application model.
-/// Callback to configure the Azure resources.
-/// The target resource that this Azure Web Site is being created for.
-public class AzureAppServiceWebSiteResource(string name, Action configureInfrastructure, IResource targetResource)
-    : AzureProvisioningResource(name, configureInfrastructure)
+public class AzureAppServiceWebSiteResource : AzureProvisioningResource
 {
+    /// 
+    /// Initializes a new instance of the  class.
+    /// 
+    /// The name of the resource in the Aspire application model.
+    /// Callback to configure the Azure resources.
+    /// The target resource that this Azure Web Site is being created for.
+    public AzureAppServiceWebSiteResource(string name, Action configureInfrastructure, IResource targetResource)
+        : base(name, configureInfrastructure)
+    {
+        TargetResource = targetResource;
+
+        // Add pipeline step annotation for push
+        Annotations.Add(new PipelineStepAnnotation((factoryContext) =>
+        {
+            // Get the registry from the target resource's deployment target annotation
+            var deploymentTargetAnnotation = targetResource.GetDeploymentTargetAnnotation();
+            if (deploymentTargetAnnotation?.ContainerRegistry is not IContainerRegistry registry)
+            {
+                // No registry available, skip push
+                return [];
+            }
+
+            var steps = new List();
+
+            if (targetResource.RequiresImageBuildAndPush())
+            {
+                // Create push step for this deployment target
+                var pushStep = new PipelineStep
+                {
+                    Name = $"push-{targetResource.Name}",
+                    Action = async ctx =>
+                    {
+                        var containerImageBuilder = ctx.Services.GetRequiredService();
+
+                        await AzureEnvironmentResourceHelpers.PushImageToRegistryAsync(
+                            registry,
+                            targetResource,
+                            ctx,
+                            containerImageBuilder).ConfigureAwait(false);
+                    },
+                    Tags = [WellKnownPipelineTags.PushContainerImage]
+                };
+
+                steps.Add(pushStep);
+            }
+
+            if (!targetResource.TryGetEndpoints(out var endpoints))
+            {
+                endpoints = [];
+            }
+
+            var printResourceSummary = new PipelineStep
+            {
+                Name = $"print-{targetResource.Name}-summary",
+                Action = async ctx =>
+                {
+                    var computerEnv = (AzureAppServiceEnvironmentResource)deploymentTargetAnnotation.ComputeEnvironment!;
+
+                    var websiteSuffix = await computerEnv.WebSiteSuffix.GetValueAsync(ctx.CancellationToken).ConfigureAwait(false);
+
+                    var hostName = $"{targetResource.Name.ToLowerInvariant()}-{websiteSuffix}";
+                    if (hostName.Length > 60)
+                    {
+                        hostName = hostName.Substring(0, 60);
+                    }
+                    var endpoint = $"https://{hostName}.azurewebsites.net";
+                    ctx.ReportingStep.Log(LogLevel.Information, $"Successfully deployed **{targetResource.Name}** to [{endpoint}]({endpoint})", enableMarkdown: true);
+                },
+                Tags = ["print-summary"]
+            };
+
+            var deployStep = new PipelineStep
+            {
+                Name = $"deploy-{targetResource.Name}",
+                Action = _ => Task.CompletedTask,
+                Tags = [WellKnownPipelineTags.DeployCompute]
+            };
+
+            deployStep.DependsOn(printResourceSummary);
+
+            steps.Add(deployStep);
+            steps.Add(printResourceSummary);
+
+            return steps;
+        }));
+
+        // Add pipeline configuration annotation to wire up dependencies
+        Annotations.Add(new PipelineConfigurationAnnotation((context) =>
+        {
+            // Find the push step for this resource
+            var pushSteps = context.GetSteps(this, WellKnownPipelineTags.PushContainerImage);
+
+            var provisionSteps = context.GetSteps(this, WellKnownPipelineTags.ProvisionInfrastructure);
+
+            // Make push step depend on build steps of the target resource
+            var buildSteps = context.GetSteps(targetResource, WellKnownPipelineTags.BuildCompute);
+
+            pushSteps.DependsOn(buildSteps);
+
+            // Make push step depend on the registry being provisioned
+            var deploymentTargetAnnotation = targetResource.GetDeploymentTargetAnnotation();
+            if (deploymentTargetAnnotation?.ContainerRegistry is IResource registryResource)
+            {
+                var registryProvisionSteps = context.GetSteps(registryResource, WellKnownPipelineTags.ProvisionInfrastructure);
+
+                pushSteps.DependsOn(registryProvisionSteps);
+            }
+
+            // The app deployment should depend on the push step
+            provisionSteps.DependsOn(pushSteps);
+
+            // Ensure summary step runs after provision
+            context.GetSteps(this, "print-summary").DependsOn(provisionSteps);
+        }));
+    }
+
     /// 
     /// Gets the target resource that this Azure Web Site is being created for.
     /// 
-    public IResource TargetResource { get; } = targetResource;
+    public IResource TargetResource { get; }
 }
diff --git a/src/Aspire.Hosting.Azure/Aspire.Hosting.Azure.csproj b/src/Aspire.Hosting.Azure/Aspire.Hosting.Azure.csproj
index ad9b7fd4e33..5011dc7e00f 100644
--- a/src/Aspire.Hosting.Azure/Aspire.Hosting.Azure.csproj
+++ b/src/Aspire.Hosting.Azure/Aspire.Hosting.Azure.csproj
@@ -40,6 +40,8 @@
 
   
     
+    
+    
   
 
 
diff --git a/src/Aspire.Hosting.Azure/AzureBicepResource.cs b/src/Aspire.Hosting.Azure/AzureBicepResource.cs
index 25506dce210..35141a07936 100644
--- a/src/Aspire.Hosting.Azure/AzureBicepResource.cs
+++ b/src/Aspire.Hosting.Azure/AzureBicepResource.cs
@@ -16,6 +16,7 @@
 using Azure;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
 
 namespace Aspire.Hosting.Azure;
 
@@ -44,11 +45,12 @@ public AzureBicepResource(string name, string? templateFile = null, string? temp
         {
             // Initialize the provisioning task completion source during step creation
             ProvisioningTaskCompletionSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
-            
+
             var provisionStep = new PipelineStep
             {
                 Name = $"provision-{name}",
-                Action = async ctx => await ProvisionAzureBicepResourceAsync(ctx, this).ConfigureAwait(false)
+                Action = async ctx => await ProvisionAzureBicepResourceAsync(ctx, this).ConfigureAwait(false),
+                Tags = [WellKnownPipelineTags.ProvisionInfrastructure]
             };
             provisionStep.RequiredBy(AzureEnvironmentResource.ProvisionInfrastructureStepName);
             provisionStep.DependsOn(AzureEnvironmentResource.CreateProvisioningContextStepName);
@@ -255,6 +257,7 @@ private static async Task ProvisionAzureBicepResourceAsync(PipelineStepContext c
         // Skip if the resource is excluded from publish
         if (resource.IsExcludedFromPublish())
         {
+            context.Logger.LogDebug("Resource {ResourceName} is excluded from publish. Skipping provisioning.", resource.Name);
             return;
         }
 
@@ -262,19 +265,20 @@ private static async Task ProvisionAzureBicepResourceAsync(PipelineStepContext c
         if (resource.ProvisioningTaskCompletionSource != null &&
             resource.ProvisioningTaskCompletionSource.Task.IsCompleted)
         {
+            context.Logger.LogDebug("Resource {ResourceName} is already provisioned. Skipping provisioning.", resource.Name);
             return;
         }
 
         var bicepProvisioner = context.Services.GetRequiredService();
         var configuration = context.Services.GetRequiredService();
-        
+
         // Find the AzureEnvironmentResource from the application model
         var azureEnvironment = context.Model.Resources.OfType().FirstOrDefault();
         if (azureEnvironment == null)
         {
             throw new InvalidOperationException("AzureEnvironmentResource must be present in the application model.");
         }
-        
+
         var provisioningContext = await azureEnvironment.ProvisioningContextTask.Task.ConfigureAwait(false);
 
         var resourceTask = await context.ReportingStep
diff --git a/src/Aspire.Hosting.Azure/AzureEnvironmentResource.cs b/src/Aspire.Hosting.Azure/AzureEnvironmentResource.cs
index f54b86d3e70..92e619d860b 100644
--- a/src/Aspire.Hosting.Azure/AzureEnvironmentResource.cs
+++ b/src/Aspire.Hosting.Azure/AzureEnvironmentResource.cs
@@ -5,21 +5,13 @@
 #pragma warning disable ASPIREAZURE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
 #pragma warning disable ASPIREPIPELINES003 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
 #pragma warning disable ASPIREINTERACTION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
-#pragma warning disable ASPIREPIPELINES001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
 
 using System.Diagnostics.CodeAnalysis;
-using System.Text.Json;
-using System.Text.Json.Nodes;
 using Aspire.Hosting.ApplicationModel;
 using Aspire.Hosting.Azure.Provisioning;
 using Aspire.Hosting.Azure.Provisioning.Internal;
-using Aspire.Hosting.Dcp.Process;
 using Aspire.Hosting.Pipelines;
-using Aspire.Hosting.Publishing;
-using Azure;
 using Azure.Core;
-using Azure.Identity;
-using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Options;
 
@@ -32,8 +24,6 @@ namespace Aspire.Hosting.Azure;
 [Experimental("ASPIREAZURE001", UrlFormat = "https://aka.ms/dotnet/aspire/diagnostics#{0}")]
 public sealed class AzureEnvironmentResource : Resource
 {
-    private const string DefaultImageStepTag = "default-image-tags";
-
     /// 
     /// The name of the step that creates the provisioning context.
     /// 
@@ -42,7 +32,7 @@ public sealed class AzureEnvironmentResource : Resource
     /// 
     /// The name of the step that provisions Azure infrastructure resources.
     /// 
-    internal const string ProvisionInfrastructureStepName = "provision-azure-bicep-resources";
+    public static readonly string ProvisionInfrastructureStepName = "provision-azure-bicep-resources";
 
     /// 
     /// Gets or sets the Azure location that the resources will be deployed to.
@@ -65,8 +55,6 @@ public sealed class AzureEnvironmentResource : Resource
     /// 
     internal TaskCompletionSource ProvisioningContextTask { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
 
-    private readonly List _computeResourcesToBuild = [];
-
     /// 
     /// Initializes a new instance of the  class.
     /// 
@@ -83,16 +71,18 @@ public AzureEnvironmentResource(string name, ParameterResource location, Paramet
             var publishStep = new PipelineStep
             {
                 Name = $"publish-{Name}",
-                Action = ctx => PublishAsync(ctx)
+                Action = ctx => PublishAsync(ctx),
+                RequiredBySteps = [WellKnownPipelineSteps.Publish],
+                DependsOnSteps = [WellKnownPipelineSteps.PublishPrereq]
             };
-            publishStep.RequiredBy(WellKnownPipelineSteps.Publish);
 
             var validateStep = new PipelineStep
             {
-                Name = "validate-azure-cli-login",
-                Action = ctx => ValidateAzureCliLoginAsync(ctx)
+                Name = "validate-azure-login",
+                Action = ctx => ValidateAzureLoginAsync(ctx),
+                RequiredBySteps = [WellKnownPipelineSteps.Deploy],
+                DependsOnSteps = [WellKnownPipelineSteps.DeployPrereq]
             };
-            validateStep.DependsOn(WellKnownPipelineSteps.ParameterPrompt);
 
             var createContextStep = new PipelineStep
             {
@@ -102,7 +92,9 @@ public AzureEnvironmentResource(string name, ParameterResource location, Paramet
                     var provisioningContextProvider = ctx.Services.GetRequiredService();
                     var provisioningContext = await provisioningContextProvider.CreateProvisioningContextAsync(ctx.CancellationToken).ConfigureAwait(false);
                     ProvisioningContextTask.TrySetResult(provisioningContext);
-                }
+                },
+                RequiredBySteps = [WellKnownPipelineSteps.Deploy],
+                DependsOnSteps = [WellKnownPipelineSteps.DeployPrereq]
             };
             createContextStep.DependsOn(validateStep);
 
@@ -110,87 +102,14 @@ public AzureEnvironmentResource(string name, ParameterResource location, Paramet
             {
                 Name = ProvisionInfrastructureStepName,
                 Action = _ => Task.CompletedTask,
-                Tags = [WellKnownPipelineTags.ProvisionInfrastructure]
-            };
-            provisionStep.DependsOn(createContextStep);
-
-            var addImageTagsStep = new PipelineStep
-            {
-                Name = DefaultImageStepTag,
-                Action = ctx => DefaultImageTags(ctx),
-                Tags = [DefaultImageStepTag],
-            };
-            addImageTagsStep.DependsOn(WellKnownPipelineSteps.ParameterPrompt);
-
-            var buildStep = new PipelineStep
-            {
-                Name = "build-container-images",
-                Action = ctx => BuildContainerImagesAsync(ctx),
-                Tags = [WellKnownPipelineTags.BuildCompute]
-            };
-            buildStep.DependsOn(addImageTagsStep);
-
-            var pushStep = new PipelineStep
-            {
-                Name = "push-container-images",
-                Action = ctx => PushContainerImagesAsync(ctx)
+                Tags = [WellKnownPipelineTags.ProvisionInfrastructure],
+                RequiredBySteps = [WellKnownPipelineSteps.Deploy],
+                DependsOnSteps = [WellKnownPipelineSteps.DeployPrereq]
             };
-            pushStep.DependsOn(buildStep);
-            pushStep.DependsOn(provisionStep);
 
-            var deployStep = new PipelineStep
-            {
-                Name = "deploy-compute-resources",
-                Action = async ctx =>
-                {
-                    var provisioningContext = await ProvisioningContextTask.Task.ConfigureAwait(false);
-                    await DeployComputeResourcesAsync(ctx, provisioningContext).ConfigureAwait(false);
-                },
-                Tags = [WellKnownPipelineTags.DeployCompute]
-            };
-            deployStep.DependsOn(pushStep);
-            deployStep.DependsOn(provisionStep);
-
-            var printDashboardUrlStep = new PipelineStep
-            {
-                Name = "print-dashboard-url",
-                Action = ctx => PrintDashboardUrlAsync(ctx)
-            };
-            printDashboardUrlStep.DependsOn(deployStep);
-            printDashboardUrlStep.RequiredBy(WellKnownPipelineSteps.Deploy);
-
-            return [publishStep, validateStep, createContextStep, provisionStep, addImageTagsStep, buildStep, pushStep, deployStep, printDashboardUrlStep];
-        }));
-
-        Annotations.Add(new PipelineConfigurationAnnotation(context =>
-        {
-            var defaultImageTags = context.GetSteps(this, DefaultImageStepTag).Single();
-            var myBuildStep = context.GetSteps(this, WellKnownPipelineTags.BuildCompute).Single();
-
-            var computeResources = context.Model.Resources
-                .Where(r => r.RequiresImageBuild())
-                .ToList();
-
-            foreach (var computeResource in computeResources)
-            {
-                var computeResourceBuildSteps = context.GetSteps(computeResource, WellKnownPipelineTags.BuildCompute);
-                if (computeResourceBuildSteps.Any())
-                {
-                    // add the appropriate dependencies to the compute resource's build steps
-                    foreach (var computeBuildStep in computeResourceBuildSteps)
-                    {
-                        computeBuildStep.DependsOn(defaultImageTags);
-                        myBuildStep.DependsOn(computeBuildStep);
-                    }
-                }
-                else
-                {
-                    // No build step exists for this compute resource, so we add it to the main build step
-                    _computeResourcesToBuild.Add(computeResource);
-                }
-            }
+            provisionStep.DependsOn(createContextStep);
 
-            return Task.CompletedTask;
+            return [publishStep, validateStep, createContextStep, provisionStep];
         }));
 
         Annotations.Add(ManifestPublishingCallbackAnnotation.Ignore);
@@ -200,26 +119,6 @@ public AzureEnvironmentResource(string name, ParameterResource location, Paramet
         PrincipalId = principalId;
     }
 
-    private static Task DefaultImageTags(PipelineStepContext context)
-    {
-        var computeResources = context.Model.Resources
-            .Where(r => r.RequiresImageBuild())
-            .ToList();
-
-        var deploymentTag = $"aspire-deploy-{DateTime.UtcNow:yyyyMMddHHmmss}";
-        foreach (var resource in computeResources)
-        {
-            if (resource.TryGetLastAnnotation(out _))
-            {
-                continue;
-            }
-            resource.Annotations.Add(
-                new DeploymentImageTagCallbackAnnotation(_ => deploymentTag));
-        }
-
-        return Task.CompletedTask;
-    }
-
     private Task PublishAsync(PipelineStepContext context)
     {
         var azureProvisioningOptions = context.Services.GetRequiredService>();
@@ -234,19 +133,14 @@ private Task PublishAsync(PipelineStepContext context)
         return publishingContext.WriteModelAsync(context.Model, this);
     }
 
-    private static async Task ValidateAzureCliLoginAsync(PipelineStepContext context)
+    private static async Task ValidateAzureLoginAsync(PipelineStepContext context)
     {
         var tokenCredentialProvider = context.Services.GetRequiredService();
 
-        if (tokenCredentialProvider.TokenCredential is not AzureCliCredential azureCliCredential)
-        {
-            return;
-        }
-
         try
         {
             var tokenRequest = new TokenRequestContext(["https://management.azure.com/.default"]);
-            await azureCliCredential.GetTokenAsync(tokenRequest, context.CancellationToken)
+            await tokenCredentialProvider.TokenCredential.GetTokenAsync(tokenRequest, context.CancellationToken)
                 .ConfigureAwait(false);
 
             await context.ReportingStep.CompleteAsync(
@@ -263,436 +157,4 @@ await context.ReportingStep.CompleteAsync(
             throw;
         }
     }
-
-    private async Task BuildContainerImagesAsync(PipelineStepContext context)
-    {
-        if (!_computeResourcesToBuild.Any())
-        {
-            return;
-        }
-
-        var containerImageBuilder = context.Services.GetRequiredService();
-
-        await containerImageBuilder.BuildImagesAsync(
-            _computeResourcesToBuild,
-            new ContainerBuildOptions
-            {
-                TargetPlatform = ContainerTargetPlatform.LinuxAmd64
-            },
-            context.CancellationToken).ConfigureAwait(false);
-    }
-
-    private static async Task PushContainerImagesAsync(PipelineStepContext context)
-    {
-        var containerImageBuilder = context.Services.GetRequiredService();
-        var processRunner = context.Services.GetRequiredService();
-        var configuration = context.Services.GetRequiredService();
-
-        var computeResources = context.Model.GetComputeResources()
-            .Where(r => r.RequiresImageBuildAndPush())
-            .ToList();
-
-        if (!computeResources.Any())
-        {
-            return;
-        }
-
-        var resourcesByRegistry = new Dictionary>();
-        foreach (var computeResource in computeResources)
-        {
-            if (TryGetContainerRegistry(computeResource, out var registry))
-            {
-                if (!resourcesByRegistry.TryGetValue(registry, out var resourceList))
-                {
-                    resourceList = [];
-                    resourcesByRegistry[registry] = resourceList;
-                }
-                resourceList.Add(computeResource);
-            }
-        }
-
-        await LoginToAllRegistriesAsync(resourcesByRegistry.Keys, context, processRunner, configuration)
-            .ConfigureAwait(false);
-
-        await PushImagesToAllRegistriesAsync(resourcesByRegistry, context, containerImageBuilder)
-            .ConfigureAwait(false);
-    }
-
-    private static async Task DeployComputeResourcesAsync(PipelineStepContext context, ProvisioningContext provisioningContext)
-    {
-        var bicepProvisioner = context.Services.GetRequiredService();
-        var computeResources = context.Model.GetComputeResources().ToList();
-
-        if (computeResources.Count == 0)
-        {
-            return;
-        }
-
-        var deploymentTasks = computeResources.Select(async computeResource =>
-        {
-            var resourceTask = await context.ReportingStep
-                .CreateTaskAsync($"Deploying **{computeResource.Name}**", context.CancellationToken)
-                .ConfigureAwait(false);
-
-            await using (resourceTask.ConfigureAwait(false))
-            {
-                try
-                {
-                    if (computeResource.GetDeploymentTargetAnnotation() is { } deploymentTarget)
-                    {
-                        if (deploymentTarget.DeploymentTarget is AzureBicepResource bicepResource)
-                        {
-                            await bicepProvisioner.GetOrCreateResourceAsync(
-                                bicepResource, provisioningContext, context.CancellationToken)
-                                .ConfigureAwait(false);
-
-                            var completionMessage = $"Successfully deployed **{computeResource.Name}**";
-
-                            if (deploymentTarget.ComputeEnvironment is IAzureComputeEnvironmentResource azureComputeEnv)
-                            {
-                                completionMessage += TryGetComputeResourceEndpoint(
-                                    computeResource, azureComputeEnv);
-                            }
-
-                            await resourceTask.CompleteAsync(
-                                completionMessage,
-                                CompletionState.Completed,
-                                context.CancellationToken).ConfigureAwait(false);
-                        }
-                        else
-                        {
-                            await resourceTask.CompleteAsync(
-                                $"Skipped **{computeResource.Name}** - no Bicep deployment target",
-                                CompletionState.CompletedWithWarning,
-                                context.CancellationToken).ConfigureAwait(false);
-                        }
-                    }
-                    else
-                    {
-                        await resourceTask.CompleteAsync(
-                            $"Skipped **{computeResource.Name}** - no deployment target annotation",
-                            CompletionState.Completed,
-                            context.CancellationToken).ConfigureAwait(false);
-                    }
-                }
-                catch (Exception ex)
-                {
-                    var errorMessage = ex switch
-                    {
-                        RequestFailedException requestEx =>
-                            $"Deployment failed: {ExtractDetailedErrorMessage(requestEx)}",
-                        _ => $"Deployment failed: {ex.Message}"
-                    };
-                    await resourceTask.CompleteAsync(
-                        $"Failed to deploy {computeResource.Name}: {errorMessage}",
-                        CompletionState.CompletedWithError,
-                        context.CancellationToken).ConfigureAwait(false);
-                    throw;
-                }
-            }
-        });
-
-        await Task.WhenAll(deploymentTasks).ConfigureAwait(false);
-    }
-
-    private static bool TryGetContainerRegistry(IResource computeResource, [NotNullWhen(true)] out IContainerRegistry? containerRegistry)
-    {
-        if (computeResource.GetDeploymentTargetAnnotation() is { } deploymentTarget &&
-            deploymentTarget.ContainerRegistry is { } registry)
-        {
-            containerRegistry = registry;
-            return true;
-        }
-
-        containerRegistry = null;
-        return false;
-    }
-
-    private static async Task LoginToAllRegistriesAsync(IEnumerable registries, PipelineStepContext context, IProcessRunner processRunner, IConfiguration configuration)
-    {
-        var registryList = registries.ToList();
-        if (!registryList.Any())
-        {
-            return;
-        }
-
-        try
-        {
-            var loginTasks = registryList.Select(async registry =>
-            {
-                var registryName = await registry.Name.GetValueAsync(context.CancellationToken).ConfigureAwait(false) ??
-                                 throw new InvalidOperationException("Failed to retrieve container registry information.");
-
-                var loginTask = await context.ReportingStep.CreateTaskAsync($"Logging in to **{registryName}**", context.CancellationToken).ConfigureAwait(false);
-                await using (loginTask.ConfigureAwait(false))
-                {
-                    await AuthenticateToAcrHelper(loginTask, registryName, context.CancellationToken, processRunner, configuration).ConfigureAwait(false);
-                }
-            });
-
-            await Task.WhenAll(loginTasks).ConfigureAwait(false);
-        }
-        catch (Exception)
-        {
-            throw;
-        }
-    }
-
-    private static async Task AuthenticateToAcrHelper(IReportingTask loginTask, string registryName, CancellationToken cancellationToken, IProcessRunner processRunner, IConfiguration configuration)
-    {
-        var command = BicepCliCompiler.FindFullPathFromPath("az") ?? throw new InvalidOperationException("Failed to find 'az' command");
-        try
-        {
-            var loginSpec = new ProcessSpec(command)
-            {
-                Arguments = $"acr login --name {registryName}",
-                ThrowOnNonZeroReturnCode = false
-            };
-
-            // Set DOCKER_COMMAND environment variable if using podman
-            var containerRuntime = GetContainerRuntime(configuration);
-            if (string.Equals(containerRuntime, "podman", StringComparison.OrdinalIgnoreCase))
-            {
-                loginSpec.EnvironmentVariables["DOCKER_COMMAND"] = "podman";
-            }
-
-            var (pendingResult, processDisposable) = processRunner.Run(loginSpec);
-            await using (processDisposable.ConfigureAwait(false))
-            {
-                var result = await pendingResult.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-                if (result.ExitCode != 0)
-                {
-                    await loginTask.FailAsync($"Login to ACR **{registryName}** failed with exit code {result.ExitCode}", cancellationToken: cancellationToken).ConfigureAwait(false);
-                }
-                else
-                {
-                    await loginTask.CompleteAsync($"Successfully logged in to **{registryName}**", CompletionState.Completed, cancellationToken).ConfigureAwait(false);
-                }
-            }
-        }
-        catch (Exception)
-        {
-            throw;
-        }
-    }
-
-    private static string? GetContainerRuntime(IConfiguration configuration)
-    {
-        // Fall back to known config names (primary and legacy)
-        return configuration["ASPIRE_CONTAINER_RUNTIME"] ?? configuration["DOTNET_ASPIRE_CONTAINER_RUNTIME"];
-    }
-
-    private static async Task PushImagesToAllRegistriesAsync(Dictionary> resourcesByRegistry, PipelineStepContext context, IResourceContainerImageBuilder containerImageBuilder)
-    {
-        var allPushTasks = new List();
-
-        foreach (var (registry, resources) in resourcesByRegistry)
-        {
-            var registryName = await registry.Name.GetValueAsync(context.CancellationToken).ConfigureAwait(false) ??
-                             throw new InvalidOperationException("Failed to retrieve container registry information.");
-
-            var resourcePushTasks = resources
-                .Where(r => r.RequiresImageBuildAndPush())
-                .Select(async resource =>
-                {
-                    if (!resource.TryGetContainerImageName(out var localImageName))
-                    {
-                        localImageName = resource.Name.ToLowerInvariant();
-                    }
-
-                    IValueProvider cir = new ContainerImageReference(resource);
-                    var targetTag = await cir.GetValueAsync(context.CancellationToken).ConfigureAwait(false);
-
-                    var pushTask = await context.ReportingStep.CreateTaskAsync($"Pushing **{resource.Name}** to **{registryName}**", context.CancellationToken).ConfigureAwait(false);
-                    await using (pushTask.ConfigureAwait(false))
-                    {
-                        try
-                        {
-                            if (targetTag == null)
-                            {
-                                throw new InvalidOperationException($"Failed to get target tag for {resource.Name}");
-                            }
-                            await TagAndPushImage(localImageName, targetTag, context.CancellationToken, containerImageBuilder).ConfigureAwait(false);
-                            await pushTask.CompleteAsync($"Successfully pushed **{resource.Name}** to `{targetTag}`", CompletionState.Completed, context.CancellationToken).ConfigureAwait(false);
-                        }
-                        catch (Exception ex)
-                        {
-                            await pushTask.CompleteAsync($"Failed to push **{resource.Name}**: {ex.Message}", CompletionState.CompletedWithError, context.CancellationToken).ConfigureAwait(false);
-                            throw;
-                        }
-                    }
-                });
-
-            allPushTasks.AddRange(resourcePushTasks);
-        }
-
-        await Task.WhenAll(allPushTasks).ConfigureAwait(false);
-    }
-
-    private static async Task TagAndPushImage(string localTag, string targetTag, CancellationToken cancellationToken, IResourceContainerImageBuilder containerImageBuilder)
-    {
-        await containerImageBuilder.TagImageAsync(localTag, targetTag, cancellationToken).ConfigureAwait(false);
-        await containerImageBuilder.PushImageAsync(targetTag, cancellationToken).ConfigureAwait(false);
-    }
-
-    private static string TryGetComputeResourceEndpoint(IResource computeResource, IAzureComputeEnvironmentResource azureComputeEnv)
-    {
-        // Check if the compute environment has the default domain output (for Azure Container Apps)
-        // We could add a reference to AzureContainerAppEnvironmentResource here so we can resolve
-        // the `ContainerAppDomain` property but we use a string-based lookup here to avoid adding
-        // explicit references to a compute environment type
-        if (azureComputeEnv is AzureProvisioningResource provisioningResource &&
-            provisioningResource.Outputs.TryGetValue("AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN", out var domainValue))
-        {
-            // Only produce endpoints for resources that have external endpoints
-            if (computeResource.TryGetEndpoints(out var endpoints) && endpoints.Any(e => e.IsExternal))
-            {
-                var endpoint = $"https://{computeResource.Name.ToLowerInvariant()}.{domainValue}";
-                return $" to [{endpoint}]({endpoint})";
-            }
-        }
-
-        // Check if the compute environment is an App Service Environment
-        // if yes, we return the web app endpoint using the webSiteSuffix output (unique string derived from resource group name)
-        if (azureComputeEnv is AzureProvisioningResource appsvcProvisioningResource &&
-            appsvcProvisioningResource.Outputs.TryGetValue("webSiteSuffix", out var webSiteSuffix))
-        {
-            var hostName = $"{computeResource.Name.ToLowerInvariant()}-{webSiteSuffix}";
-            if (hostName.Length > 60)
-            {
-                hostName = hostName.Substring(0, 60);
-            }
-            var endpoint = $"https://{hostName}.azurewebsites.net";
-            return $" to [{endpoint}]({endpoint})";
-        }
-
-        return string.Empty;
-    }
-
-    /// 
-    /// Extracts detailed error information from Azure RequestFailedException responses.
-    /// Parses the following JSON error structures:
-    /// 1. Standard Azure error format: { "error": { "code": "...", "message": "...", "details": [...] } }
-    /// 2. Deployment-specific error format: { "properties": { "error": { "code": "...", "message": "..." } } }
-    /// 3. Nested error details with recursive parsing for deeply nested error hierarchies
-    /// 
-    /// The Azure RequestFailedException containing the error response
-    /// The most specific error message found, or the original exception message if parsing fails
-    private static string ExtractDetailedErrorMessage(RequestFailedException requestEx)
-    {
-        try
-        {
-            var response = requestEx.GetRawResponse();
-            if (response?.Content is not null)
-            {
-                var responseContent = response.Content.ToString();
-                if (!string.IsNullOrEmpty(responseContent))
-                {
-                    if (JsonNode.Parse(responseContent) is JsonObject responseObj)
-                    {
-                        if (responseObj["error"] is JsonObject errorObj)
-                        {
-                            var code = errorObj["code"]?.ToString();
-                            var message = errorObj["message"]?.ToString();
-
-                            if (!string.IsNullOrEmpty(code) && !string.IsNullOrEmpty(message))
-                            {
-                                if (errorObj["details"] is JsonArray detailsArray && detailsArray.Count > 0)
-                                {
-                                    var deepestErrorMessage = ExtractDeepestErrorMessage(detailsArray);
-                                    if (!string.IsNullOrEmpty(deepestErrorMessage))
-                                    {
-                                        return deepestErrorMessage;
-                                    }
-                                }
-
-                                return $"{code}: {message}";
-                            }
-                        }
-
-                        if (responseObj["properties"]?["error"] is JsonObject deploymentErrorObj)
-                        {
-                            var code = deploymentErrorObj["code"]?.ToString();
-                            var message = deploymentErrorObj["message"]?.ToString();
-
-                            if (!string.IsNullOrEmpty(code) && !string.IsNullOrEmpty(message))
-                            {
-                                return $"{code}: {message}";
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        catch (JsonException) { }
-
-        return requestEx.Message;
-    }
-
-    private static string ExtractDeepestErrorMessage(JsonArray detailsArray)
-    {
-        foreach (var detail in detailsArray)
-        {
-            if (detail is JsonObject detailObj)
-            {
-                var detailCode = detailObj["code"]?.ToString();
-                var detailMessage = detailObj["message"]?.ToString();
-
-                if (detailObj["details"] is JsonArray nestedDetailsArray && nestedDetailsArray.Count > 0)
-                {
-                    var deeperMessage = ExtractDeepestErrorMessage(nestedDetailsArray);
-                    if (!string.IsNullOrEmpty(deeperMessage))
-                    {
-                        return deeperMessage;
-                    }
-                }
-
-                if (!string.IsNullOrEmpty(detailCode) && !string.IsNullOrEmpty(detailMessage))
-                {
-                    return $"{detailCode}: {detailMessage}";
-                }
-            }
-        }
-
-        return string.Empty;
-    }
-
-    private static async Task PrintDashboardUrlAsync(PipelineStepContext context)
-    {
-        var dashboardUrl = TryGetDashboardUrl(context.Model);
-
-        if (dashboardUrl != null)
-        {
-            await context.ReportingStep.CompleteAsync(
-                $"Dashboard available at [dashboard URL]({dashboardUrl})",
-                CompletionState.Completed,
-                context.CancellationToken).ConfigureAwait(false);
-        }
-    }
-
-    private static string? TryGetDashboardUrl(DistributedApplicationModel model)
-    {
-        foreach (var resource in model.Resources)
-        {
-            if (resource is IAzureComputeEnvironmentResource &&
-                resource is AzureBicepResource environmentBicepResource)
-            {
-                // If the resource is a compute environment, we can use its properties
-                // to construct the dashboard URL.
-                if (environmentBicepResource.Outputs.TryGetValue($"AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN", out var domainValue))
-                {
-                    return $"https://aspire-dashboard.ext.{domainValue}";
-                }
-                // If the resource is a compute environment (app service), we can use its properties
-                // to get the dashboard URL.
-                if (environmentBicepResource.Outputs.TryGetValue($"AZURE_APP_SERVICE_DASHBOARD_URI", out var dashboardUri))
-                {
-                    return (string?)dashboardUri;
-                }
-            }
-        }
-
-        return null;
-    }
 }
diff --git a/src/Aspire.Hosting.Azure/AzureEnvironmentResourceHelpers.cs b/src/Aspire.Hosting.Azure/AzureEnvironmentResourceHelpers.cs
new file mode 100644
index 00000000000..201cc2d3824
--- /dev/null
+++ b/src/Aspire.Hosting.Azure/AzureEnvironmentResourceHelpers.cs
@@ -0,0 +1,122 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable ASPIRECOMPUTE001
+#pragma warning disable ASPIREPUBLISHERS001
+#pragma warning disable ASPIREPIPELINES001
+#pragma warning disable ASPIREPIPELINES003
+
+using Aspire.Hosting.ApplicationModel;
+using Aspire.Hosting.Azure.Provisioning.Internal;
+using Aspire.Hosting.Dcp.Process;
+using Aspire.Hosting.Pipelines;
+using Aspire.Hosting.Publishing;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Aspire.Hosting.Azure;
+
+/// 
+/// Helper methods for Azure environment resources to handle container image operations.
+/// 
+internal static class AzureEnvironmentResourceHelpers
+{
+    public static async Task LoginToRegistryAsync(IContainerRegistry registry, PipelineStepContext context)
+    {
+        var processRunner = context.Services.GetRequiredService();
+        var configuration = context.Services.GetRequiredService();
+
+        var registryName = await registry.Name.GetValueAsync(context.CancellationToken).ConfigureAwait(false) ??
+                         throw new InvalidOperationException("Failed to retrieve container registry information.");
+
+        var loginTask = await context.ReportingStep.CreateTaskAsync($"Logging in to **{registryName}**", context.CancellationToken).ConfigureAwait(false);
+        await using (loginTask.ConfigureAwait(false))
+        {
+            await AuthenticateToAcrHelper(loginTask, registryName, context.CancellationToken, processRunner, configuration).ConfigureAwait(false);
+        }
+    }
+
+    private static async Task AuthenticateToAcrHelper(IReportingTask loginTask, string registryName, CancellationToken cancellationToken, IProcessRunner processRunner, IConfiguration configuration)
+    {
+        var command = BicepCliCompiler.FindFullPathFromPath("az") ?? throw new InvalidOperationException("Failed to find 'az' command");
+        try
+        {
+            var loginSpec = new ProcessSpec(command)
+            {
+                Arguments = $"acr login --name {registryName}",
+                ThrowOnNonZeroReturnCode = false
+            };
+
+            // Set DOCKER_COMMAND environment variable if using podman
+            var containerRuntime = GetContainerRuntime(configuration);
+            if (string.Equals(containerRuntime, "podman", StringComparison.OrdinalIgnoreCase))
+            {
+                loginSpec.EnvironmentVariables["DOCKER_COMMAND"] = "podman";
+            }
+
+            var (pendingResult, processDisposable) = processRunner.Run(loginSpec);
+            await using (processDisposable.ConfigureAwait(false))
+            {
+                var result = await pendingResult.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+                if (result.ExitCode != 0)
+                {
+                    await loginTask.FailAsync($"Login to ACR **{registryName}** failed with exit code {result.ExitCode}", cancellationToken: cancellationToken).ConfigureAwait(false);
+                }
+                else
+                {
+                    await loginTask.CompleteAsync($"Successfully logged in to **{registryName}**", CompletionState.Completed, cancellationToken).ConfigureAwait(false);
+                }
+            }
+        }
+        catch (Exception)
+        {
+            throw;
+        }
+    }
+
+    private static string? GetContainerRuntime(IConfiguration configuration)
+    {
+        // Fall back to known config names (primary and legacy)
+        return configuration["ASPIRE_CONTAINER_RUNTIME"] ?? configuration["DOTNET_ASPIRE_CONTAINER_RUNTIME"];
+    }
+
+    public static async Task PushImageToRegistryAsync(IContainerRegistry registry, IResource resource, PipelineStepContext context, IResourceContainerImageBuilder containerImageBuilder)
+    {
+        var registryName = await registry.Name.GetValueAsync(context.CancellationToken).ConfigureAwait(false) ??
+                         throw new InvalidOperationException("Failed to retrieve container registry information.");
+
+        if (!resource.TryGetContainerImageName(out var localImageName))
+        {
+            localImageName = resource.Name.ToLowerInvariant();
+        }
+
+        IValueProvider cir = new ContainerImageReference(resource);
+        var targetTag = await cir.GetValueAsync(context.CancellationToken).ConfigureAwait(false);
+
+        var pushTask = await context.ReportingStep.CreateTaskAsync($"Pushing **{resource.Name}** to **{registryName}**", context.CancellationToken).ConfigureAwait(false);
+        await using (pushTask.ConfigureAwait(false))
+        {
+            try
+            {
+                if (targetTag == null)
+                {
+                    throw new InvalidOperationException($"Failed to get target tag for {resource.Name}");
+                }
+                await TagAndPushImage(localImageName, targetTag, context.CancellationToken, containerImageBuilder).ConfigureAwait(false);
+                await pushTask.CompleteAsync($"Successfully pushed **{resource.Name}** to `{targetTag}`", CompletionState.Completed, context.CancellationToken).ConfigureAwait(false);
+            }
+            catch (Exception ex)
+            {
+                await pushTask.CompleteAsync($"Failed to push **{resource.Name}**: {ex.Message}", CompletionState.CompletedWithError, context.CancellationToken).ConfigureAwait(false);
+                throw;
+            }
+        }
+    }
+
+    private static async Task TagAndPushImage(string localTag, string targetTag, CancellationToken cancellationToken, IResourceContainerImageBuilder containerImageBuilder)
+    {
+        await containerImageBuilder.TagImageAsync(localTag, targetTag, cancellationToken).ConfigureAwait(false);
+        await containerImageBuilder.PushImageAsync(targetTag, cancellationToken).ConfigureAwait(false);
+    }
+}
diff --git a/src/Aspire.Hosting.Azure/Provisioning/BicepUtilities.cs b/src/Aspire.Hosting.Azure/Provisioning/BicepUtilities.cs
index 89f72f093ea..0e263b7f1a3 100644
--- a/src/Aspire.Hosting.Azure/Provisioning/BicepUtilities.cs
+++ b/src/Aspire.Hosting.Azure/Provisioning/BicepUtilities.cs
@@ -14,30 +14,14 @@ namespace Aspire.Hosting.Azure.Provisioning;
 /// 
 internal static class BicepUtilities
 {
-    // Known values since they will be filled in by the provisioner
-    private static readonly string[] s_knownParameterNames =
-    [
-        AzureBicepResource.KnownParameters.PrincipalName,
-        AzureBicepResource.KnownParameters.PrincipalId,
-        AzureBicepResource.KnownParameters.PrincipalType,
-        AzureBicepResource.KnownParameters.UserPrincipalId,
-        AzureBicepResource.KnownParameters.Location,
-    ];
-
     /// 
     /// Converts the parameters to a JSON object compatible with the ARM template.
     /// 
-    public static async Task SetParametersAsync(JsonObject parameters, AzureBicepResource resource, bool skipDynamicValues = false, CancellationToken cancellationToken = default)
+    public static async Task SetParametersAsync(JsonObject parameters, AzureBicepResource resource, CancellationToken cancellationToken = default)
     {
         // Convert the parameters to a JSON object
         foreach (var parameter in resource.Parameters)
         {
-            if (skipDynamicValues &&
-                (s_knownParameterNames.Contains(parameter.Key) || IsParameterWithGeneratedValue(parameter.Value)))
-            {
-                continue;
-            }
-
             // Execute parameter values which are deferred.
             var parameterValue = parameter.Value is Func