diff --git a/src/frontend/src/content/docs/deployment/pipelines.mdx b/src/frontend/src/content/docs/deployment/pipelines.mdx
index 1eeb8aab4..6407bf247 100644
--- a/src/frontend/src/content/docs/deployment/pipelines.mdx
+++ b/src/frontend/src/content/docs/deployment/pipelines.mdx
@@ -200,11 +200,11 @@ The pipeline system uses fine-grained steps to provide precise control and visib
Aspire registers a default set of well-known steps that serve as integration points for the deployment pipeline. These steps provide a standardized way for resources and applications to participate in common deployment workflows.
-### Entry point steps
+### Command completion steps
-- **`WellKnownPipelineSteps.Deploy`**: The primary entry point for the `aspire deploy` command. This step orchestrates the complete deployment process including infrastructure provisioning, image building, and application deployment.
+- **`WellKnownPipelineSteps.Deploy`**: The final command-completion aggregate for `aspire deploy`. The command finishes after every step required by this aggregate completes.
-- **`WellKnownPipelineSteps.Publish`**: The entry point for the `aspire publish` command, which typically generates deployment artifacts without executing the actual deployment.
+- **`WellKnownPipelineSteps.Publish`**: The final command-completion aggregate for `aspire publish`, which typically generates deployment artifacts without executing the deployment.
- **`WellKnownPipelineSteps.Build`**: The entry point for the `aspire do build` command, which builds container images for compute resources defined in the application.
@@ -214,13 +214,39 @@ Aspire registers a default set of well-known steps that serve as integration poi
- **`WellKnownPipelineSteps.BuildPrereq`**: Defines steps that are pre-requisites for building, such as dependency resolution, environment setup, and build tool validation.
-- **`WellKnownPipelineSteps.DeployPrereqs`**: Defines steps that are pre-requisites for deployment, such as authentication, environment validation, and prerequisite resource checks.
+- **`WellKnownPipelineSteps.DeployPrereq`**: Defines steps that are prerequisites for deployment, such as authentication, environment validation, and prerequisite resource checks.
-- **`WellKnownPipelineSteps.PublishPrereqs`**: Defines steps that are pre-requisites for publishing, such as build environment setup and artifact preparation.
+- **`WellKnownPipelineSteps.PublishPrereq`**: Defines steps that are prerequisites for publishing, such as build environment setup and artifact preparation.
- **`WellKnownPipelineSteps.PushPrereq`**: Defines steps that are pre-requisites for pushing container images, such as registry authentication and connection validation.
-These well-known steps create a contract that allows different parts of the system to integrate predictably. For example, a custom authentication step can declare itself as required by `DeployPrereqs`, ensuring it runs before any deployment operations begin.
+These well-known steps create a contract that allows different parts of the system to integrate predictably. For example, a custom authentication step can declare itself as required by `DeployPrereq`, ensuring it runs before any deployment operations begin.
+
+### Finalization steps
+
+- **`WellKnownPipelineSteps.DeployFinalize`**: A barrier that runs after ordinary deployment work and before post-finalize hooks.
+- **`WellKnownPipelineSteps.PublishFinalize`**: A barrier that runs after ordinary publishing work and before post-finalize hooks.
+
+The publish and deploy commands use the same finalization structure:
+
+```mermaid
+flowchart LR
+ publishPrereq["publish-prereq"] --> publishWork["normal publish work"]
+ publishWork --> publishFinalize["publish-finalize"]
+ publishFinalize --> publishHooks["post-finalize hooks"]
+ publishHooks --> publish["publish"]
+
+ deployPrereq["deploy-prereq"] --> deployWork["normal deploy work"]
+ deployWork --> deployFinalize["deploy-finalize"]
+ deployFinalize --> deployHooks["post-finalize hooks"]
+ deployHooks --> deploy["deploy"]
+```
+
+Attach ordinary publish or deploy work to the corresponding finalizer with `requiredBy`. For example, a deployment step that must finish before finalization is required by `WellKnownPipelineSteps.DeployFinalize`.
+
+For a post-finalize hook, use both dependency directions: depend on the corresponding finalizer, then make the hook required by the final command aggregate. The dependency places the hook after ordinary command work, while the requirement makes the final `publish` or `deploy` aggregate wait for it.
+
+Existing integrations that attach steps directly to `WellKnownPipelineSteps.Publish` or `WellKnownPipelineSteps.Deploy` remain compatible. These legacy steps can run in parallel with the finalizer branch, so use the finalization pattern when a step needs a defined position before or after finalization.
### Resource-contributed steps
@@ -287,7 +313,7 @@ builder.Pipeline.AddStep("validate-deployment", async context =>
// Custom validation logic
await ValidateApiHealth(context);
await ValidateDatabaseConnection(context);
-}, requiredBy: WellKnownPipelineSteps.Deploy);
+}, requiredBy: WellKnownPipelineSteps.DeployFinalize);
#pragma warning restore ASPIREPIPELINES001
// Define resources
@@ -307,7 +333,7 @@ builder.pipeline.addStep("validate-deployment", async (context) => {
// Custom validation logic
await validateApiHealth(context);
await validateDatabaseConnection(context);
-}, { requiredBy: ["deploy"] });
+}, { requiredBy: ["deploy-finalize"] });
// Define resources
const database = await builder.addPostgres("myapp-db");
@@ -450,7 +476,8 @@ aspire do deploy --include-exception-details
```
-For complete command reference, see [aspire do command](/reference/cli/commands/aspire-do/).
+ For complete command reference, see [aspire do
+ command](/reference/cli/commands/aspire-do/).
### Discovering available steps
@@ -497,13 +524,13 @@ var builder = DistributedApplication.CreateBuilder(args);
builder.Pipeline.AddStep("validate", async (context) =>
{
context.Logger.LogInformation("Running validation checks...");
-
+
// Your custom validation logic
await ValidateApiEndpoints(context);
await CheckDatabaseConnection(context);
-
+
context.Logger.LogInformation("Validation completed successfully");
-}, requiredBy: WellKnownPipelineSteps.Deploy);
+}, requiredBy: WellKnownPipelineSteps.DeployFinalize);
// Define resources
var database = builder.AddPostgres("db");
@@ -533,13 +560,13 @@ var api = builder.AddProject("api")
{
// Custom deployment logic for this resource
pipelineContext.Logger.LogInformation("Custom API deployment starting...");
-
+
// Your deployment logic here
await DeployApiAsync(pipelineContext, cancellationToken);
-
+
pipelineContext.Logger.LogInformation("Custom API deployment completed");
},
- RequiredBySteps = [WellKnownPipelineSteps.Deploy]
+ RequiredBySteps = [WellKnownPipelineSteps.DeployFinalize]
};
});
```
@@ -554,7 +581,7 @@ builder.Pipeline.AddStep("database-migration", async (context) =>
{
context.Logger.LogInformation("Running database migrations...");
await RunMigrations(context);
-},
+},
dependsOn: ["provision-database"],
requiredBy: ["deploy-apiservice"]);
```
@@ -569,7 +596,10 @@ You can choose from two dependency types to fix the step order:
Resources can also customize how they participate in the pipeline using `WithPipelineConfiguration`, which provides control over step ordering and resource-specific pipeline behavior.
## Common use cases
@@ -591,15 +621,17 @@ builder.Pipeline.AddStep("validate-production", async (context) =>
context.Logger.LogInformation("Running production-specific validations...");
await ValidateProductionReadiness(context);
}
-}, requiredBy: WellKnownPipelineSteps.Deploy);
+}, requiredBy: WellKnownPipelineSteps.DeployFinalize);
// Add post-deployment smoke tests
-builder.Pipeline.AddStep("smoke-tests", async (context) =>
-{
- context.Logger.LogInformation("Running smoke tests...");
- await RunSmokeTests(context);
-},
-dependsOn: [WellKnownPipelineSteps.Deploy]);
+builder.Pipeline.AddStep(
+ "smoke-tests",
+ async context =>
+ {
+ await RunSmokeTestsAsync(context.CancellationToken);
+ },
+ dependsOn: WellKnownPipelineSteps.DeployFinalize,
+ requiredBy: WellKnownPipelineSteps.Deploy);
builder.Build().Run();
```
@@ -633,7 +665,7 @@ builder.Pipeline.AddStep("optimize-images", async (context) =>
{
context.Logger.LogInformation("Optimizing container images...");
await OptimizeImages(context);
-},
+},
dependsOn: [WellKnownPipelineSteps.Build],
requiredBy: [WellKnownPipelineSteps.Push]);
@@ -653,15 +685,15 @@ var database = builder.AddPostgres("db");
builder.Pipeline.AddStep("migrate-database", async (context) =>
{
context.Logger.LogInformation("Running database migrations...");
-
+
// Get database connection string from context
var connectionString = await GetConnectionString(context, database);
-
+
// Run migrations
await RunDatabaseMigrations(connectionString, context.Logger);
-
+
context.Logger.LogInformation("Database migrations completed");
-},
+},
dependsOn: ["provision-database"],
requiredBy: ["deploy-apiservice"]);
@@ -679,12 +711,14 @@ Aspire 13.0 replaces the publishing callback system with the more flexible pipel
The old publishing callback system has been removed and replaced with pipeline steps:
**Removed APIs:**
+
- `WithPublishingCallback` extension method
- `PublishingContext` and `PublishingCallbackAnnotation`
- `DeployingContext` and `DeployingCallbackAnnotation`
- `IDistributedApplicationPublisher` interface
**New APIs:**
+
- `WithPipelineStepFactory` extension method
- `PipelineStep` class
- `builder.Pipeline.AddStep` method
@@ -733,7 +767,7 @@ var api = builder.AddProject("api")
// Custom deployment logic
await CustomDeployAsync(pipelineContext, cancellationToken);
},
- RequiredBySteps = [WellKnownPipelineSteps.Deploy]
+ RequiredBySteps = [WellKnownPipelineSteps.DeployFinalize]
};
});
```
@@ -764,10 +798,13 @@ builder.Pipeline.AddStep("notify-deployment", async (context) =>
{
// Custom logic
await SendDeploymentNotification(context);
-},
-dependsOn: [WellKnownPipelineSteps.Deploy]);
+},
+dependsOn: WellKnownPipelineSteps.DeployFinalize,
+requiredBy: WellKnownPipelineSteps.Deploy);
```
+Depending on `DeployFinalize` places the notification after ordinary deployment work. Making the notification required by `Deploy` ensures the command waits for it before reporting completion.
+
#### Complex deployment workflow
**Before (Aspire 9.x):**
@@ -797,7 +834,7 @@ builder.Pipeline.AddStep("provision-infra", async (context) =>
builder.Pipeline.AddStep("migrate-database", async (context) =>
{
await RunDatabaseMigrations(context);
-},
+},
dependsOn: ["provision-infra"],
requiredBy: ["deploy-application"]);
@@ -830,5 +867,6 @@ The pipeline system provides several advantages over publishing callbacks:
- **Reusability**: Steps can be reused across different deployment scenarios.