Why CI/CD for Dynamics 365
Exporting a solution by hand and importing it somewhere else works until the day it doesn't. A pipeline makes every release the same release.
Repeatable
The same steps, in the same order, for every release and every environment.
Traceable
Every deployment links back to a commit, a work item and the person who approved it.
Safe
Quality checks and approvals stop problems before they reach production users.
Fast
Small, frequent releases instead of a stressful big bang every quarter.
A pipeline, stage by stage
This is the shape of a typical release pipeline for a Dynamics 365 solution. Watch it run, or select a stage to see what happens there.
Commit
A developer exports the unmanaged solution from Dev, unpacks it into source files and pushes a branch. A pull request starts the pipeline.
Environment strategy
Multi-environment approach for quality assurance:
The solution changes form once, in the build, and is never edited by hand downstream.
Unmanaged in development, managed everywhere else
- Development: Unmanaged solutions for active development
- Build/integration: Automated build and test environment
- User acceptance testing (UAT): Stakeholder validation
- Production: Managed solutions with change control
- Sandbox: Training and experimentation environments
Solution management
- Managed vs unmanaged solutions: Proper solution layering strategy
- Solution segmentation: Modular solutions for independent deployment
- Solution patching: Hot fixes and incremental updates
- Dependency management: Clean dependency chains and upgrade paths
Source control and branching
The unpacked solution lives in Git next to the plugin and PCF source, so every change is reviewed like code.
- Short-lived feature branches, merged through pull requests with a required build.
- One solution unpacked per folder, so diffs show exactly which form, view or flow changed.
- Version numbers set by the pipeline, never by hand.
- Main is always deployable; releases are tagged.
An Azure DevOps pipeline in YAML
A trimmed-down example using the Power Platform Build Tools. Real pipelines add the plugin build, tests and approvals per environment.
1trigger: 2 branches: 3 include: [ main ]4 5pool: 6 vmImage: windows-latest7 8variables: 9 SolutionName: ContosoCore10 11stages: 12- stage: Build13 jobs: 14 - job: Pack15 steps: 16 - task: PowerPlatformToolInstaller@217 - task: PowerPlatformPackSolution@218 inputs: 19 SolutionSourceFolder: src/$(SolutionName)20 SolutionOutputFile: $(Build.ArtifactStagingDirectory)/$(SolutionName)_managed.zip21 SolutionType: Managed22 - task: PowerPlatformChecker@223 inputs: 24 authenticationType: PowerPlatformSPN25 PowerPlatformSPN: pp-build26 FilesToAnalyze: $(Build.ArtifactStagingDirectory)/*.zip27 RuleSet: 0ad12346-e108-40b8-a956-9a8f95ea18c928 - publish: $(Build.ArtifactStagingDirectory)29 artifact: drop30 31- stage: DeployTest32 dependsOn: Build33 jobs: 34 - deployment: Import35 environment: Test36 strategy: 37 runOnce: 38 deploy: 39 steps: 40 - checkout: self41 - task: PowerPlatformToolInstaller@242 - task: PowerPlatformImportSolution@243 inputs: 44 authenticationType: PowerPlatformSPN45 PowerPlatformSPN: pp-test46 SolutionInputFile: $(Pipeline.Workspace)/drop/$(SolutionName)_managed.zip47 UseDeploymentSettingsFile: true48 DeploymentSettingsFile: settings/test.json49 HoldingSolution: true50 AsyncOperation: true51 MaxAsyncWaitTime: 6052 - task: PowerPlatformApplySolutionUpgrade@253 inputs: 54 authenticationType: PowerPlatformSPN55 PowerPlatformSPN: pp-test56 SolutionName: $(SolutionName)57 AsyncOperation: true58 MaxAsyncWaitTime: 60Choosing the tooling
Build Tools and GitHub Actions wrap the same Power Platform CLI commands; Pipelines in Power Platform is a built-in Dataverse service that you can also trigger from the CLI. The choice depends on where your team already works.
Pipelines in Power Platform
Built into the maker experience. Ideal for makers and smaller teams that want governed deployments without running a DevOps platform. Target environments must be Managed Environments, deployments stay within one tenant, and each solution is developed in a single development environment.
Azure DevOps
Full control with YAML pipelines, Power Platform Build Tools, approvals, boards and test plans in one place.
GitHub Actions
The same capabilities for teams whose code already lives on GitHub, using Microsoft's Power Platform actions.
CI/CD automation
I implement automated deployment pipelines using:
- Azure DevOps: Build and release pipelines with Power Platform Build Tools
- GitHub Actions: Git-based workflows for solution deployment
- Power Platform CLI: Command-line automation for solution operations
- Solution checker: Automated code quality and best practice validation
Configuration per environment
A solution should not contain anything that differs between environments. Those values live outside it:
- Environment variables for URLs, feature flags and settings.
- Connection references, so flows bind to each environment's own connections.
- A deployment settings file per environment, applied automatically at import.
- Secrets in Azure Key Vault, referenced rather than copied.
Automated testing
- Unit tests for plugins and custom APIs, run in the build with a faked Dataverse context.
- Solution Checker on every build, with critical rules failing the pipeline.
- UI tests for the processes that must never break, run against Test after each deployment.
- Smoke tests after the Production deployment, with the result posted to the team channel.
Practices that keep releases boring
Deploy with a service principal registered as an application user, never a personal account.
Build once and promote the same artifact through every environment.
Use upgrades rather than updates when components are removed, so deleted items disappear cleanly.
Keep no unmanaged changes in Test or Production; treat them as defects.
Split large implementations into a few solutions with clear dependencies.
Generate release notes from the linked work items, so everyone knows what changed.