diff --git a/src/frontend/src/content/docs/deployment/custom-deployments.mdx b/src/frontend/src/content/docs/deployment/custom-deployments.mdx
index 257478541..1a5cdadc8 100644
--- a/src/frontend/src/content/docs/deployment/custom-deployments.mdx
+++ b/src/frontend/src/content/docs/deployment/custom-deployments.mdx
@@ -86,31 +86,19 @@ The reporter API provides structured access to progress reporting with the follo
## Example: Build container images and report progress
-To use these APIs, add a `PublishingCallbackAnnotation`, a `DeployingCallbackAnnotation`, or both to a resource in your app model. You can annotate custom (or built-in) resources by adding annotations to the `IResource.Annotations` collection.
+To use these APIs, register pipeline steps with `WithPipelineStepFactory` (resource-level) or `builder.Pipeline.AddStep` (application-level). This lets custom resources participate in publish and deploy flows with explicit step dependencies.
As a developer, you can choose to:
-- Use both annotations if your resource needs to do work in both publishing and deployment. For example, build images and generate manifests during publishing, then push images or configure deployment targets during deployment. Publishing always happens before deployment, so you can keep logic for each phase separate.
-- Use only `PublishingCallbackAnnotation` if your resource only needs to do something during publishing. This is common when you just need to build artifacts or images, but don't need to do anything during deployment.
-- Use only `DeployingCallbackAnnotation` if your resource only needs to do something during deployment. This fits cases where you use prebuilt images and just need to deploy or configure them.
+- Register a step that is `requiredBy` `WellKnownPipelineSteps.Publish` when your resource needs custom publishing behavior.
+- Register a step that is `requiredBy` `WellKnownPipelineSteps.Deploy` when your resource needs custom deployment behavior.
+- Register both when your resource performs work in both phases and needs ordering between those steps.
-Choose one or more annotations that match your resource's responsibilities to keep your application model clear and maintainable. This separation lets you clearly define logic for each phase, but you can use both the activity reporter and the resource container image builder in either callback as needed.
+For broader pipeline orchestration patterns, see [Deployment pipelines](/deployment/pipelines/). For custom resource authoring basics, see [Create custom resources](/extensibility/custom-resources/).
-### Example resource with annotations
+### Example resource with pipeline steps
-For example, consider the `ComputeEnvironmentResource` constructor:
-
-```csharp title="ComputeEnvironmentResource.cs"
-public ComputeEnvironmentResource(string name) : base(name)
-{
- Annotations.Add(new PublishingCallbackAnnotation(PublishAsync));
- Annotations.Add(new DeployingCallbackAnnotation(DeployAsync));
-}
-```
-
-When instantiated, it defines both a publishing and deploying callback annotation.
-
-Given the example `ComputeEnvironmentResource` type, imagine you have an extension method that you expose so consumers are able to add the compute environment:
+For example, consider an extension method that registers two pipeline steps for a custom `ComputeEnvironmentResource`:
```csharp title="ComputeEnvironmentResourceExtensions.cs"
public static class ComputeEnvironmentResourceExtensions
@@ -121,16 +109,33 @@ public static class ComputeEnvironmentResourceExtensions
{
var resource = new ComputeEnvironmentResource(name);
- return builder.AddResource(resource);
+ return builder.AddResource(resource)
+ .WithPipelineStepFactory(
+ stepName: $"{name}-build-images",
+ callback: PublishAsync,
+ requiredBy: [WellKnownPipelineSteps.Publish],
+ description: "Build container images and write deployment artifacts.")
+ .WithPipelineStepFactory(
+ stepName: $"{name}-deploy",
+ callback: DeployAsync,
+ dependsOn: [$"{name}-build-images"],
+ requiredBy: [WellKnownPipelineSteps.Deploy],
+ description: "Deploy generated artifacts to the target environment.");
}
}
```
+In the same class, define `PublishAsync` and `DeployAsync` as private static methods that each accept `PipelineStepContext` (shown in the next sections).
+The explicit `dependsOn` relationship ensures the deploy step waits for this resource's image-build step, even when you add other custom publish or deploy steps to the pipeline.
+
The preceding code:
- Defines an extension method on the `IDistributedApplicationBuilder`.
- Accepts a `name` for the compute environment resource, protected by the `ResourceNameAttribute`.
-- Instantiates a `ComputeEnvironmentResource` given the `name` and adds it to the `builder`.
+- Instantiates a `ComputeEnvironmentResource` given the `name`.
+- Registers a publish step and a deploy step with explicit dependencies.
+- Adds both steps through `WithPipelineStepFactory`.
+- Uses the validated resource name as the prefix for stable, unambiguous step names.
### Example AppHost
@@ -153,131 +158,93 @@ builder.Build().Run();
:::note
-Custom resource types and callback-based deployment extensibility aren't yet available in the TypeScript AppHost SDK, so this AppHost example is currently C#-only.
+Custom resource types and custom pipeline-step extensibility aren't yet available in the TypeScript AppHost SDK, so this AppHost example is currently C#-only.
:::
The preceding code uses the `AddComputeEnvironment` extension method to add the `ComputeEnvironmentResource` to the application model.
-### Publishing callback annotation
+### Publish pipeline step
-When you add the `ComputeEnvironmentResource`, it registers a `PublishingCallbackAnnotation`. The callback uses the `PublishAsync` method:
+The publish step callback can build container images and generate deployment artifacts:
-```csharp title="ComputeEnvironmentResource.cs"
-private static async Task PublishAsync(PublishingContext context)
+```csharp title="ComputeEnvironmentResourceExtensions.cs"
+private static async Task PublishAsync(PipelineStepContext context)
{
- var reporter = context.ActivityReporter;
var imageBuilder = context.Services.GetRequiredService();
+ var reportingStep = context.ReportingStep;
+
+ var projectResources = context.Model.Resources
+ .OfType()
+ .ToList();
- // Build container images for all project resources in the application
- await using (var buildStep = await reporter.CreateStepAsync(
- "Build container images", context.CancellationToken))
+ if (projectResources.Count > 0)
{
- // Find all resources that need container images
- var projectResources = context.Model.Resources
- .OfType()
- .ToList();
+ var buildTask = await reportingStep.CreateTaskAsync(
+ $"Building {projectResources.Count} container image(s)",
+ context.CancellationToken);
- if (projectResources.Count > 0)
+ var buildOptions = new ContainerBuildOptions
{
- // Configure how images should be built
- var buildOptions = new ContainerBuildOptions
- {
- ImageFormat = ContainerImageFormat.Oci,
- TargetPlatform = ContainerTargetPlatform.LinuxAmd64,
- OutputPath = Path.Combine(context.OutputPath, "images")
- };
-
- var buildTask = await buildStep.CreateTaskAsync(
- $"Building {projectResources.Count} container image(s)", context.CancellationToken);
-
- // Build all the container images
- await imageBuilder.BuildImagesAsync(
- projectResources, buildOptions, context.CancellationToken);
-
- await buildTask.SucceedAsync(
- $"Built {projectResources.Count} image(s) successfully", context.CancellationToken);
- }
- else
- {
- var skipTask = await buildStep.CreateTaskAsync(
- "No container images to build", context.CancellationToken);
+ ImageFormat = ContainerImageFormat.Oci,
+ TargetPlatform = ContainerTargetPlatform.LinuxAmd64
+ };
- await skipTask.SucceedAsync("Skipped - no project resources found", context.CancellationToken);
- }
+ await imageBuilder.BuildImagesAsync(projectResources, buildOptions, context.CancellationToken);
- await buildStep.SucceedAsync("Container image build completed", context.CancellationToken);
+ await buildTask.SucceedAsync(
+ $"Built {projectResources.Count} image(s) successfully",
+ context.CancellationToken);
}
-
- // Generate deployment manifests
- await using (var manifestStep = await reporter.CreateStepAsync(
- "Generate deployment manifests", context.CancellationToken))
+ else
{
- var bicepTask = await manifestStep.CreateTaskAsync(
- "Write main.bicep", context.CancellationToken);
-
- // Write file to context.OutputPath …
- await bicepTask.SucceedAsync(
- $"main.bicep at {context.OutputPath}", context.CancellationToken);
-
- await manifestStep.SucceedAsync("Manifests ready", context.CancellationToken);
+ var skipTask = await reportingStep.CreateTaskAsync(
+ "No container images to build",
+ context.CancellationToken);
+ await skipTask.SucceedAsync("Skipped - no project resources found", context.CancellationToken);
}
- // Complete the publishing operation
- await reporter.CompletePublishAsync(
- completionMessage: "Publishing pipeline completed successfully",
- completionState: CompletionState.Completed,
- cancellationToken: context.CancellationToken);
+ var manifestTask = await reportingStep.CreateTaskAsync(
+ "Generate deployment manifests",
+ context.CancellationToken);
+ // Write deployment files...
+ await manifestTask.SucceedAsync("Manifests ready", context.CancellationToken);
}
```
The preceding code:
-- Implements a publishing pipeline that builds container images and generates deployment manifests.
+- Implements a publish step that builds container images and generates deployment manifests.
- Uses the `IResourceContainerImageBuilder` API to build container images.
-- Reports progress and completion status using the `PipelineActivityReporter` API.
+- Reports progress with `IReportingStep` tasks from `PipelineStepContext.ReportingStep`.
-Your publishing callback might use `IResourceContainerImageBuilder` to build container images, while your deployment callback might use the built images and push them to a registry or deployment target.
+Your publish step might use `IResourceContainerImageBuilder` to build images, while your deploy step might use those artifacts and push them to a registry or target environment.
-### Deploying callback annotation
+### Deploy pipeline step
-Like the publishing callback, the deploying callback is registered using the `DeployingCallbackAnnotation` and calls the `DeployAsync` method:
+The deploy step callback can apply deployment artifacts and report task-level progress:
-```csharp title="ComputeEnvironmentResource.cs"
-private static async Task DeployAsync(DeployingContext context)
+```csharp title="ComputeEnvironmentResourceExtensions.cs"
+private static async Task DeployAsync(PipelineStepContext context)
{
- var reporter = context.ActivityReporter;
+ var reportingStep = context.ReportingStep;
- await using (var deployStep = await reporter.CreateStepAsync(
- "Deploy to target environment", context.CancellationToken))
- {
- var applyTask = await deployStep.CreateTaskAsync(
- "Apply Kubernetes manifests", context.CancellationToken);
-
- // Simulate deploying to Kubernetes cluster
- await Task.Delay(1_000, context.CancellationToken);
+ var applyTask = await reportingStep.CreateTaskAsync(
+ "Apply Kubernetes manifests",
+ context.CancellationToken);
- await applyTask.SucceedAsync("All workloads deployed", context.CancellationToken);
-
- await deployStep.SucceedAsync("Deployment to cluster completed", context.CancellationToken);
- }
+ // Simulate deploying to Kubernetes cluster
+ await Task.Delay(1_000, context.CancellationToken);
- // Complete the deployment operation
- await reporter.CompletePublishAsync(
- completionMessage: "Deployment completed successfully",
- completionState: CompletionState.Completed,
- isDeploy: true,
- cancellationToken: context.CancellationToken);
+ await applyTask.SucceedAsync("All workloads deployed", context.CancellationToken);
}
```
The preceding code:
- Simulates deploying workloads to a Kubernetes cluster.
-- Uses the `PipelineActivityReporter` API to create and manage deployment steps and tasks.
-- Reports progress and marks each deployment phase as completed.
-- Completes the deployment operation with a final status update.
+- Uses `PipelineStepContext.ReportingStep` to create and complete deployment tasks.
- Handles cancellation through the provided `CancellationToken`.
## Best practices
@@ -295,7 +262,6 @@ When using these APIs, follow these guidelines:
- Encapsulate long-running logical phases in steps rather than emitting raw tasks.
- Keep titles concise (under 60 characters) as the CLI truncates longer strings.
-- Call `CompletePublishAsync` exactly once per publishing or deployment operation.
- Treat warnings as recoverable and allow subsequent steps to proceed.
- Treat errors as fatal and fail fast with clear diagnostics.
- Use asynchronous, cancellation-aware operations to avoid blocking event processing.
diff --git a/src/frontend/src/content/docs/deployment/pipelines.mdx b/src/frontend/src/content/docs/deployment/pipelines.mdx
index 19fdfef8c..81c07be2c 100644
--- a/src/frontend/src/content/docs/deployment/pipelines.mdx
+++ b/src/frontend/src/content/docs/deployment/pipelines.mdx
@@ -660,8 +660,8 @@ The old publishing callback system has been removed and replaced with pipeline s
**Removed APIs:**
- `WithPublishingCallback` extension method
-- `PublishingContext` and `PublishingCallbackAnnotation`
-- `DeployingContext` and `DeployingCallbackAnnotation`
+- `PublishingContext` and the legacy publish callback annotation type
+- `DeployingContext` and the legacy deploy callback annotation type
- `IDistributedApplicationPublisher` interface
**New APIs:**
diff --git a/src/frontend/src/content/docs/diagnostics/aspirepipelines001.mdx b/src/frontend/src/content/docs/diagnostics/aspirepipelines001.mdx
index 43453e487..33b60e196 100644
--- a/src/frontend/src/content/docs/diagnostics/aspirepipelines001.mdx
+++ b/src/frontend/src/content/docs/diagnostics/aspirepipelines001.mdx
@@ -28,7 +28,7 @@ This diagnostic applies to the following pipeline infrastructure APIs:
- `IReportingTask` - Interface for managing tasks within a step
- `CompletionState` - Enumeration for tracking completion status
- `PublishingContext` - Context for publishing operations
-- `PublishingCallbackAnnotation` - Annotation for publishing callbacks
+- Legacy publish callback annotation types
- Related extension methods and implementations
## To correct this error
diff --git a/src/frontend/src/content/docs/diagnostics/aspirepipelines002.mdx b/src/frontend/src/content/docs/diagnostics/aspirepipelines002.mdx
index 9e61a754b..3f6de012f 100644
--- a/src/frontend/src/content/docs/diagnostics/aspirepipelines002.mdx
+++ b/src/frontend/src/content/docs/diagnostics/aspirepipelines002.mdx
@@ -28,7 +28,7 @@ This diagnostic applies to the following deployment state manager APIs:
- `Deploy` property in `PublishingOptions`
- `ClearCache` property in `PublishingOptions`
- `Step` property in `PublishingOptions`
-- `DeployingCallbackAnnotation` - Annotation for deploying callbacks
+- Legacy deploy callback annotation types
- Azure provisioning context providers
- Related extension methods and implementations
diff --git a/src/frontend/src/content/docs/ja/deployment/pipelines.mdx b/src/frontend/src/content/docs/ja/deployment/pipelines.mdx
index f1f8e9f9f..13a85fe63 100644
--- a/src/frontend/src/content/docs/ja/deployment/pipelines.mdx
+++ b/src/frontend/src/content/docs/ja/deployment/pipelines.mdx
@@ -659,8 +659,8 @@ Aspire 13.0はより柔軟なパイプラインシステムで発行コールバ
**削除された API:**
- `WithPublishingCallback` 拡張メソッド
-- `PublishingContext` と `PublishingCallbackAnnotation`
-- `DeployingContext` と `DeployingCallbackAnnotation`
+- `PublishingContext` とレガシー publish コールバック注釈型
+- `DeployingContext` とレガシー deploy コールバック注釈型
- `IDistributedApplicationPublisher` インターフェース
**新しい API:**
diff --git a/src/frontend/src/content/docs/ja/whats-new/aspire-13.mdx b/src/frontend/src/content/docs/ja/whats-new/aspire-13.mdx
index 324e16570..cb07fbb21 100644
--- a/src/frontend/src/content/docs/ja/whats-new/aspire-13.mdx
+++ b/src/frontend/src/content/docs/ja/whats-new/aspire-13.mdx
@@ -1478,8 +1478,8 @@ aspire add javaScript
Aspire 13.0 では、以下の API が削除されました:
**パブリッシング関連のインフラストラクチャ** (`aspire do` に置き換え):
-- `PublishingContext` および `PublishingCallbackAnnotation`
-- `DeployingContext` および `DeployingCallbackAnnotation`
+- `PublishingContext` およびレガシー publish コールバック注釈型
+- `DeployingContext` およびレガシー deploy コールバック注釈型
- `WithPublishingCallback` 拡張メソッド
- `IDistributedApplicationPublisher` インターフェース
- `IPublishingActivityReporter`, `IPublishingStep`, `IPublishingTask` インターフェース
diff --git a/src/frontend/src/content/docs/whats-new/aspire-13.mdx b/src/frontend/src/content/docs/whats-new/aspire-13.mdx
index f813f603a..58153b149 100644
--- a/src/frontend/src/content/docs/whats-new/aspire-13.mdx
+++ b/src/frontend/src/content/docs/whats-new/aspire-13.mdx
@@ -1478,8 +1478,8 @@ aspire add javaScript
The following APIs have been removed in Aspire 13.0:
**Publishing infrastructure** (replaced by `aspire do`):
-- `PublishingContext` and `PublishingCallbackAnnotation`
-- `DeployingContext` and `DeployingCallbackAnnotation`
+- `PublishingContext` and the legacy publish callback annotation type
+- `DeployingContext` and the legacy deploy callback annotation type
- `WithPublishingCallback` extension method
- `IDistributedApplicationPublisher` interface
- `IPublishingActivityReporter`, `IPublishingStep`, `IPublishingTask` interfaces
diff --git a/src/frontend/src/content/docs/whats-new/aspire-9-3.mdx b/src/frontend/src/content/docs/whats-new/aspire-9-3.mdx
index 42578b971..b4a646698 100644
--- a/src/frontend/src/content/docs/whats-new/aspire-9-3.mdx
+++ b/src/frontend/src/content/docs/whats-new/aspire-9-3.mdx
@@ -322,7 +322,7 @@ In multi-replica scenarios, Aspire still uses full replica IDs so you can distin
In 9.2, we shipped our first iteration of "publishers", a flexible way to configure deployments to any cloud in the AppHost. To ensure more flexibility, Aspire 9.3 includes a **new and improved** publisher model that distributes publishing behavior across your application graph instead of relying on a single top-level publisher.
-Rather than selecting a target environment (like Docker or Azure) by calling `AddDockerComposePublisher()` or similar, Aspire now includes a **built-in publisher** that looks for a `PublishingCallbackAnnotation` on each resource. This annotation describes how that resource should be published—for example, as a Docker Compose service, Kubernetes manifest, or Azure Bicep module.
+Rather than selecting a target environment (like Docker or Azure) by calling `AddDockerComposePublisher()` or similar, Aspire now includes a **built-in publisher** that looks for a manifest publishing callback annotation on each resource. This annotation describes how that resource should be published—for example, as a Docker Compose service, Kubernetes manifest, or Azure Bicep module.