Select the correct pipeline model

The classic flow uses Finance and Operations build tooling and NuGet packages to create an LCS deployable package. The unified developer experience builds X++ using pac d365 build and produces a Power Platform unified package. See Build automation with Microsoft-hosted agents and CI/CD for the unified developer experience.

Recommended stage design

Validate Restore Compile Test Package Publish Deploy metadata NuGet X++ + SSRS SysTest ZIP + manifest artifact feed gated env.
Each stage has a single responsibility. The artifact produced by Package is never rebuilt — it is promoted through each environment gate unchanged.
  1. Validate: conventions, secret detection, metadata structure and dependency graph.
  2. Restore: NuGet packages and build tools at pinned versions.
  3. Compile: X++ models, labels and SSRS reports with warnings-as-errors policy.
  4. Test: unit tests (SysTest) and package integrity checks.
  5. Package: immutable artifact generation with version stamp.
  6. Publish: manifest, SHA-256 checksum, logs and package to artifact feed.
  7. Deploy: protected promotion to each environment with pre-checks and approvals.

NuGet configuration and baseline pinning

The NuGet configuration is the single most important file for build reproducibility. A floating version reference can change compilation output between two identical source commits.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <!-- Microsoft D365 platform feed -->
    <add key="D365-Platform"
         value="https://pkgs.dev.azure.com/{org}/{project}/_packaging/D365-Packages/nuget/v3/index.json" />
    <!-- Custom ISV / internal packages -->
    <add key="Internal"
         value="https://pkgs.dev.azure.com/{org}/{project}/_packaging/Internal/nuget/v3/index.json" />
  </packageSources>
  <packageSourceCredentials>
    <D365-Platform>
      <add key="Username" value="%NUGET_USERNAME%" />
      <add key="ClearTextPassword" value="%SYSTEM_ACCESSTOKEN%" />
    </D365-Platform>
  </packageSourceCredentials>
</configuration>
# packages.config — pin every version explicitly
Microsoft.Dynamics.AX.Platform.DevALM.BuildXpp          7.0.7279.57
Microsoft.Dynamics.AX.Application.DevALM.BuildXpp       10.0.2135.37
Microsoft.Dynamics.AX.ApplicationSuite.DevALM.BuildXpp  10.0.2135.37

Store nuget.config and packages.config at repository root and commit them under version control. The baseline version must match the target environment's application version. A mismatch is caught at compile time but costs build minutes when discovered late.

Readable YAML skeleton

trigger:
  branches:
    include: [ main ]

pr:
  branches:
    include: [ main ]

variables:
- group: d365-build-non-secret
- name: artifactName
  value: d365-package
- name: baselineVersion
  value: '10.0.48'

stages:
- stage: Validate
  jobs:
  - job: Metadata
    pool:
      vmImage: windows-latest
    steps:
    - checkout: self
      clean: true
    - template: templates/validate-metadata.yml

- stage: Build
  dependsOn: Validate
  jobs:
  - job: CompileAndPackage
    pool:
      vmImage: windows-latest
    timeoutInMinutes: 120
    steps:
    - template: templates/restore-d365.yml
      parameters:
        baselineVersion: $(baselineVersion)
    - template: templates/compile-xpp.yml
    - template: templates/run-systests.yml
    - template: templates/create-package.yml
      parameters:
        artifactName: $(artifactName)
    - publish: $(Build.ArtifactStagingDirectory)
      artifact: $(artifactName)

- stage: Deploy_UAT
  dependsOn: Build
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
  jobs:
  - deployment: Promote
    environment: D365-UAT
    strategy:
      runOnce:
        deploy:
          steps:
          - download: current
            artifact: $(artifactName)
          - template: templates/deploy-lcs.yml
            parameters:
              environment: UAT
              lcsProjectId: $(lcsProjectId)

Templates hide implementation detail without hiding pipeline intent. The stage names and dependencies remain readable at a glance.

Pipeline template library

Organise reusable steps in a templates/ folder committed to the same repository (or a dedicated pipeline repository shared across projects):

templates/
├── validate-metadata.yml    # Model descriptor checks, secret scan
├── restore-d365.yml         # NuGet restore with version parameter
├── compile-xpp.yml          # Invoke-D365ModuleCompile / xppc
├── run-systests.yml         # Invoke-D365ModuleTestsInAzure / test runner
├── create-package.yml       # Create-D365Deployable / generate manifest
├── deploy-lcs.yml           # Invoke-D365LcsAssetUpload + Invoke-D365LcsDeployment
└── notify-teams.yml         # Teams webhook notification on failure

Each template takes typed parameters and uses only supported Azure DevOps tasks. Keep templates idempotent: a retry of the same stage must produce the same result.

Restore and baseline

  • Pin build-tool and platform package versions (see NuGet configuration above).
  • Use authenticated feeds through Azure DevOps service connections, never inline credentials.
  • Cache the NuGet package directory between jobs using the Cache task, keyed on the package manifest hash.
  • Fail early (stage Validate) when a required package version is not available in the feed.
  • Document the baseline version in the repository root README.md and in the pipeline variable group.

Version models and artifacts

Application baseline : 10.0.48
Release train        : 2026.07
Build revision       : $(Build.BuildId)  → 8421
Artifact name        : QWO-D365-10.0.48-2026.07.8421.zip
Commit               : $(Build.SourceVersion) → 7c4e90a

manifest.json:
{
  "baseline":   "10.0.48",
  "release":    "2026.07",
  "buildId":    8421,
  "commit":     "7c4e90a",
  "branch":     "refs/heads/main",
  "models":     ["QwoCore", "QwoIntegration", "QwoReporting"],
  "workItems":  ["ADO-1423", "ADO-1510", "ADO-1577"],
  "sha256":     "e3b0c44298fc1c149afbf4c8996fb924...",
  "builtUtc":   "2026-07-22T18:34:00Z"
}

The manifest is stored alongside the ZIP in the artifact feed. Every downstream consumer — UAT, release manager, Microsoft support — can reconstruct the full provenance chain from the artifact name alone.

Quality gates

  • Full compilation of all models in the solution, not only the changed project.
  • Critical and blocking Best Practice warnings treated as errors.
  • Focused SysTest suite on every build; scheduled broader regression suite.
  • Secret and unexpected-binary detection in the repository (e.g., trufflehog or Azure Defender).
  • Metadata and model-dependency graph validation.
  • Package-size threshold with comparison to the previous successful build.

Secrets and identities

Use Azure DevOps service connections, Key Vault-backed variable groups and protected environments. Assign identities the minimum required access to feeds, artifact stores and target environments. Mask tokens at variable-group level and never print sensitive configuration content in build logs.

# Key Vault-linked variable group example
variables:
- group: d365-keyvault-secrets  # links to Azure Key Vault

# Reference in step:
- task: NuGetAuthenticate@1
  displayName: 'Authenticate NuGet feeds'
  # Service connection configured in project settings, not inline credentials

Deployment and approvals

For classic LCS deployments, see Deploy assets by using Azure Pipelines. Unified projects use Power Platform Build Tools and unified packages.

  • One Azure DevOps environment per deployment target (UAT, Pre-Prod, Prod).
  • Each environment defines automated pre-checks and named approvers.
  • Download by immutable artifact identifier and version — never "latest".
  • Record the deployment outcome (success/rollback/abort) in a linked release work item.

Pipeline observability

Track stage duration, failure rate, recurring failure causes, restore time and artifact size trend. Gradual compile-time growth often reveals dependency expansion or undersized agents. Configure alerts for:

  • Build time exceeding 150% of the rolling average.
  • Test failure rate above threshold on main.
  • Artifact size increase greater than 20% vs. previous release.
  • Secret scan findings (immediate block).

Tech Lead checklist

  1. Is the YAML versioned in Git and protected by a PR policy?
  2. Are baseline version and NuGet dependencies explicitly pinned?
  3. Does every build start from a clean workspace (clean: true)?
  4. Is the deployable package generated exactly once and never rebuilt?
  5. Are the manifest and SHA-256 checksum published alongside the artifact?
  6. Do secrets use Key Vault-backed variable groups or service connections?
  7. Does each environment enforce named approvals and automated pre-checks?
  8. Can the full release evidence (build, tests, approvals, checksum) be reconstructed from the artifact name?

Microsoft Learn references

No section matches this search.