Skip to content

Pipelines

Every Forge repository gets its pipelines from the templates that scaffolded it. Service templates compose feature templates, and each feature contributes YAML files under .azdo/ plus the metadata that registers them in Azure DevOps and wires up branch policies. The same files can also be generated from an Aspire AppHost.

This page is the source of truth for what each pipeline does, what triggers it, and which one to run. Pipeline names below use the scaffold metadata tokens {project_id} and {front_end_project_id}; your repository substitutes its own identifiers at scaffold time. For diagnosing a pipeline that is failing, see Pipeline Troubleshooting.


🧭 Which pipeline do I run?

Run the deploy pipeline for the service you are deploying. Each service pipeline deploys everything that service owns β€” the API pipeline deploys your application and its full auth configuration in a single run.

Deploying… Run Deploys
An API {project_id}-api App infrastructure, container, and all auth stages
A front end {front_end_project_id} Web infrastructure and static site
An event subscription {project_id}-sub Subscription infrastructure and function app
An event service {project_id} Event service infrastructure and container
A docs site {project_id}-docs Published documentation site (manual)
A package or module {project_id} Published package artifact

Shortcut pipelines

Pipeline Use when
{project_id}-auth You changed only infra/auth and want it applied without a full API deploy

You rarely need the auth pipeline

The API pipeline already runs the auth stages β€” auth_ext_okta_client, auth_ext_app, auth_ext_user, and auth_corp. The standalone auth pipeline exists purely as a faster path for auth-only changes. See Deployment Workflow.

PR validation

Validation is chosen automatically by branch policy path filters β€” you do not pick it.

You changed… Validation that runs
src/**, infra/api/**, *.sln, generated OpenAPI {project_id}-api-pr
infra/auth/**, .azdo/azure-pipelines-auth*.yml {project_id}-auth-pr
infra/web/**, src/*.Frontend/** {front_end_project_id}-pr
infra/sub/**, src/*.Subscriptions/**, src/*.ServiceBus.Seed/** {project_id}-sub-pr
docs/**, mkdocs.yml, .azdo/vars/docs.yml {project_id}-docs-pr
Package sources in a package repository {project_id}-pr

The auth split is about review, not deployment

infra/auth is excluded from the API PR validation filter so that auth changes route to the auth validation and the auth approver group. It is not excluded because auth deploys separately β€” the API pipeline still applies auth on merge. A pull request touching both application code and infra/auth runs both validations and requires both reviewer groups.


πŸ“ What your repository gets

Service templates do not ship pipelines directly. They run a setup script that composes feature templates, and each feature contributes its own .azdo files. The pipeline set in a repository therefore depends on the template and the options chosen at scaffold time.

API services (saif-api-exp, saif-api-proc, saif-api-sys)

All three compose an identical feature set:

Feature template Included when Pipelines contributed
saif-feature-base Always None β€” contributes .azdo/vars/base.yml only
saif-feature-api application_type == api -api, -api-pr, -auth, -auth-pr, -pr-slot-cleanup
saif-feature-front-end-react front_end_type == react {front_end_project_id}, {front_end_project_id}-pr
saif-feature-dependabot Always -dependabot
saif-feature-docs-site include_docs_site == true -docs, -docs-pr
saif-feature-database-oracle / -cosmosdb Database selected None
saif-feature-webhook has_webhook == true None

So a processing API with no front end and no docs site has six pipelines; the same API with React and a docs site has ten.

Front-end services (saif-frontend-service)

Feature template Included when Pipelines contributed
saif-feature-base Always None
saif-feature-front-end-react Always {front_end_project_id}, {front_end_project_id}-pr
saif-feature-web-standalone Always Overrides the web pipelines, adds -pr-slot-cleanup
saif-feature-docs-site include_docs_site == true -docs, -docs-pr

saif-feature-web-standalone runs after saif-feature-front-end-react and overwrites azure-pipelines-web*.yml with the standalone variants. There is no API, auth, or Dependabot pipeline.

Added after scaffolding

Some features are applied to an existing repository rather than at creation:

saif new saif-feature-event-subscription   # adds -sub and -sub-pr
saif new saif-feature-docs-site            # adds -docs and -docs-pr
saif new saif-feature-dependabot           # adds -dependabot

Standalone repository templates

saif-event-service, saif-nuget-package, saif-typespec-package, saif-terraform-module, saif-dynatrace-config, saif-azure-terraform-team, saif-business-roles, and saif-test-tools are not composed from features β€” each is its own repository type with a fixed pipeline set, listed below.


πŸ—οΈ How pipelines are wired

Registration and branch policies

Pipelines are not registered by hand. Each template declares them in .template.config/saif-cli.host.json, and the SAIF CLI creates the Azure DevOps pipeline definitions and branch policies from that declaration:

{
  "pipelines": [
    {
      "path": ".azdo/azure-pipelines-api-pr.yml",
      "name": "{project_id}-api-pr",
      "validations": [
        {
          "branch": "refs/heads/main",
          "displayName": "Api Validation",
          "pathFilter": "/*;!/infra/auth/*;!/infra/web/*"
        }
      ]
    }
  ]
}
Field Meaning
path The YAML file in the repository
name The pipeline name in Azure DevOps ({project_id} is substituted at scaffold)
validations Creates a build validation branch policy; pathFilter decides when it runs

A pipeline without a validations entry is never attached to a branch policy. That is why deploy pipelines use trigger: for CI and PR pipelines use trigger: none β€” the PR run is started by the branch policy, not by the YAML.

Path filter syntax

pathFilter is a semicolon-separated list. A leading ! excludes:

/*;!/infra/auth/*;!/infra/web/*;!/src/*.Frontend/*

Reads as: everything, except auth, except web infrastructure, except frontend source.

Shared templates

Pipeline logic lives in SAIF/pipeline-templates, not in your repository. Repository YAML only supplies parameters:

resources:
  repositories:
    - repository: templates
      type: git
      name: SAIF/pipeline-templates
      ref: refs/heads/releases/v3

extends:
  template: azure-dotnet-api-v3.yml@templates
  parameters:
    applicationName: ${{ variables.ApplicationName }}
    projectId: ${{ variables.ProjectId }}

Orchestrators

The shared templates are thin wrappers. Two orchestrators do the real work, and the deploy and PR variants of a pipeline are the same orchestrator with a different enablePreview value:

Orchestrator Used by Components
/v2/orchestrators/azure-app-orchestrator-v3.yml azure-dotnet-api-v3.yml, azure-dotnet-api-pr-v3.yml, azure-react-web-v3.yml, azure-react-web-pr-v3.yml App, web, and auth
/v2/orchestrators/azure-auth-orchestrator.yml azure-auth.yml, azure-auth-pr.yml Auth only
Parameter Effect
enablePreview: true PR validation β€” Terraform plan and validation only
enablePreview: false Full deployment β€” Terraform apply and deploy
enableApi Enables the api_* components
enableFrontEnd Enables the web_* components
deployToSlot Creates an App Service deployment slot per PR

The auth orchestrator runs auth_ext_okta_client, auth_ext_app, auth_ext_user, and auth_corp β€” exactly the same auth components the app orchestrator runs. That is why the standalone auth pipeline is a shortcut rather than a separate deployment path.


πŸ“¦ Application pipelines

API (saif-feature-api)

Pipeline Azure DevOps name Trigger Shared template
azure-pipelines-api.yml {project_id}-api CI on main β€” .azdo, infra, src, *.sln; excludes infra/auth azure-dotnet-api-v3.yml
azure-pipelines-api-pr.yml {project_id}-api-pr Branch policy azure-dotnet-api-pr-v3.yml
azure-pipelines-auth.yml {project_id}-auth Manual β€” optional shortcut for auth-only changes azure-auth.yml
azure-pipelines-auth-pr.yml {project_id}-auth-pr Branch policy azure-auth-pr.yml
azure-pipelines-pr-slot-cleanup.yml {project_id}-pr-slot-cleanup Webhook on PR closed azure-pr-slot-cleanup.yml

azure-dotnet-api-v3.yml deploys the application and the auth configuration β€” both extend the same orchestrator, /v2/orchestrators/azure-app-orchestrator-v3.yml, which runs these components:

setup_pr_slot_cleanup β†’ bootstrap β†’ auth_ext_okta_client
  β†’ api_infra β†’ api_crypto β†’ api_container β†’ api_openapi β†’ feature_flags β†’ tag_api
  β†’ web_infra β†’ web_container β†’ web_private_link β†’ tag_web
  β†’ auth_ext_app β†’ auth_ext_user β†’ auth_corp

auth_ext_app, auth_ext_user, and auth_corp all depend on auth_ext_okta_client. The API and web components are gated by the enableApi and enableFrontEnd parameters, so the API pipeline runs the api_* components and the web pipeline runs the web_* components β€” both run the auth components.

infra/auth is excluded from the CI trigger so that auth-only commits do not force a full application redeploy. The auth components still execute whenever the pipeline runs for any other reason.

When the API is scaffolded with a React front end, the API trigger additionally excludes .azdo/azure-pipelines-web*.yml, infra/web, and src/*.Frontend, so front-end changes do not redeploy the API.

The slot cleanup pipeline is driven by a PRClosed-{project_id} webhook resource rather than a branch trigger. See PR Slot Deployments.

Web (saif-feature-front-end-react, saif-feature-web-standalone)

Pipeline Azure DevOps name Trigger Shared template
azure-pipelines-web.yml {front_end_project_id} CI on main β€” .azdo, infra, src/*.Frontend; excludes infra/api, infra/auth azure-react-web-v3.yml
azure-pipelines-web-pr.yml {front_end_project_id}-pr Branch policy; standalone also declares a pr: trigger on .azdo, infra, src/*.Frontend azure-react-web-pr-v3.yml
azure-pipelines-pr-slot-cleanup.yml {front_end_project_id}-pr-slot-cleanup Webhook on PR closed (standalone only) azure-pr-slot-cleanup.yml

Event subscription (saif-feature-event-subscription)

Pipeline Azure DevOps name Trigger Shared template
azure-pipelines-sub.yml {project_id}-sub CI on main β€” .azdo, infra, src, *.sln; excludes infra/auth, infra/api, infra/web azure-dotnet-sub-v2.yml
azure-pipelines-sub-pr.yml {project_id}-sub-pr Branch policy azure-dotnet-sub-pr-v2.yml

Event service (saif-event-service)

Pipeline Azure DevOps name Trigger Shared template
azure-pipelines.yml {project_id} CI on main azure-event-service.yml
azure-pipelines-pr.yml {project_id}-pr Branch policy azure-event-service-pr.yml

πŸ“š Documentation and scanning pipelines

Docs site (saif-feature-docs-site)

Pipeline Azure DevOps name Trigger Shared template
azure-docs.yml {project_id}-docs Manual (trigger: none, pr: none) azure-docs-v2.yml
azure-docs-pr.yml {project_id}-docs-pr pr: trigger on main β€” docs/*, mkdocs.yml, .azdo/vars/docs.yml azure-docs-pr-v2.yml

Docs PR validation uses a pr: trigger

Unlike the API and web PR pipelines, azure-docs-pr.yml declares its own pr: trigger in YAML rather than relying solely on a branch policy path filter. Publishing the site is a deliberate manual step β€” run {project_id}-docs when you want the site updated. See Documentation Site.

Dependabot (saif-feature-dependabot)

Pipeline Azure DevOps name Trigger Behaviour
dependabot-scan.yml {project_id}-dependabot Daily cron 0 11 * * * (3 AM PST), always: true Runs the dependabot@2 task; opens update PRs with squash merge and auto-complete

Can also be run manually for an immediate scan.


πŸ“¦ Package and infrastructure pipelines

These repositories publish artifacts rather than deploying an application. All follow the same two-pipeline shape: CI on main publishes, and a branch-policy PR pipeline validates.

Template Deploy pipeline PR pipeline Shared templates
saif-nuget-package {project_id} {project_id}-pr package-dotnet-nuget.yml / package-dotnet-nuget-pr.yml
saif-typespec-package {project_id} {project_id}-pr package-typespec-npm.yml / package-typespec-npm-pr.yml
saif-terraform-module {project_id} {project_id}-pr package-terraform.yml / package-terraform-pr.yml
saif-dynatrace-config {project_id} {project_id}-pr dynatrace-terraform.yml / dynatrace-terraform-pr.yml
saif-azure-terraform-team Azure.Terraform Azure.Terraform-pr azure-terraform-infrastructure-owner-v2.yml / -pr-v2.yml

Business roles (saif-business-roles)

Pipeline Azure DevOps name Trigger Shared template
azure-pipelines.yml {project_id} CI on main β€” infra/entra/business-roles.yml or infra/okta/business-roles.yml entra-terraform-business-roles.yml or okta-terraform-business-roles.yml

No PR pipeline. The provider-specific path and template are chosen at scaffold time based on whether the application is corp or external.

Test tools (saif-test-tools)

Pipeline Azure DevOps name Trigger Shared template
gen-token-pipeline.yml {project_id}-gen-np-jwt Manual (branch include list is none) okta-testing-gen-np-accesstoken.yml
np-roles-pipeline.yml {project_id}-np-roles CI on main okta-terraform-np-roles-user.yml
external-test-users-pipeline.yml {project_id}-external-test-users Manual okta-terraform-external-test-users.yml

See Create Test Tools Repository.


βš™οΈ Generating pipelines with Aspire publish

aspire publish can generate the .azdo files from your AppHost instead of you maintaining them by hand. It writes to the same paths, overwriting in place, and derives the file set from the resources registered in AppHost.cs.

Registered resource Generates
API service azure-pipelines-api.yml, azure-pipelines-api-pr.yml, vars/api.yml
Security azure-pipelines-auth.yml, azure-pipelines-auth-pr.yml, vars/auth.yml
Frontend azure-pipelines-web.yml, azure-pipelines-web-pr.yml, vars/web.yml
Subscription azure-pipelines-sub.yml, azure-pipelines-sub-pr.yml, vars/sub.yml
Event service azure-pipelines-event-service.yml, azure-pipelines-event-service-pr.yml, vars/event-service.yml

vars/base.yml is always generated.

Event service filenames differ from the template

The saif-event-service template ships azure-pipelines.yml and azure-pipelines-pr.yml. Aspire publish generates azure-pipelines-event-service.yml and azure-pipelines-event-service-pr.yml. In a repository scaffolded from the template, publishing adds new files rather than overwriting the existing ones β€” reconcile them and update the pipeline definition paths in Azure DevOps before deleting either set.

Aspire publish does not generate docs, Dependabot, or PR slot cleanup pipelines. See Aspire Publish.


πŸ”§ Variable files

Repository YAML pulls variables from local vars/ templates and from the shared templates repository.

Local variable files

File Defines Used by
.azdo/vars/base.yml ApplicationName, BusinessDomain, ProjectId, Owner, Tenant All pipelines in the repo
.azdo/vars/api.yml ApplicationType, ApiType, OpenAPIFileName, version components API pipelines
.azdo/vars/web.yml FrontEndProjectId, version components Web pipelines
.azdo/vars/auth.yml Version components for auth releases Auth pipelines
.azdo/vars/sub.yml FunctionAppProjectId, version components Subscription pipelines
.azdo/vars/docs.yml projectId, documentationSourceDirectory, outputDirectory, siteType, containerFolderName Docs pipelines
.azdo/vars/event-service.yml openApiFileName, secretsVariableGroupName, date-based version components Event service pipelines
.azdo/vars.yml ApplicationName, Owner, ApplicationType, ProjectId, BusinessDomain, version components Package and config repos (single-file variant)

Shared variable templates

Template Provides
saif-vars.yml@templates Platform-wide values
Variables/environments.yml@templates Environment-to-subscription mapping (deploy pipelines only)
Variables/versioning.yml@templates MajorVersion, MinorVersion, PatchVersion, pre-release separator

Environment mapping lives in the shared templates repository β€” there is no local environments.yml. See Environments.

Variable groups

Group Purpose
TerraformCloud Terraform Cloud credentials
Dynatrace Observability configuration
{project_id} Per-project secrets (Oracle-backed APIs)

❓ Why didn't my pipeline run?

Symptom Cause
No PR validation appeared Changed paths do not match any pathFilter, or no validations entry exists
Auth validation ran instead of API The change touched infra/auth, which the API filter excludes
API deploy did not run after merge Changed paths fall inside the trigger's exclude list
Both API and auth validations ran Expected β€” the PR touches both scopes and needs both reviewer groups
Auth changes merged but nothing deployed infra/auth is excluded from the API CI trigger β€” run {project_id}-api or {project_id}-auth
Auth stages skipped inside the API run The pipeline detected no auth configuration changes
Docs pipeline did not publish azure-docs.yml is manual by design
No pipeline exists for a feature you added Database, webhook, and base features contribute no pipelines

πŸ“š Resources