-
-
Notifications
You must be signed in to change notification settings - Fork 0
07 Deploy
flowline deploy packs the solution from the repo and imports it into a target environment.
It is the promotion step — moving a tested solution from DEV through test, UAT, and into production.
flowline deploy test
flowline deploy uat
flowline deploy prod
flowline deploy https://contoso-staging.crm4.dynamics.com # or a direct URLflowline push # push code to DEV
flowline sync # pull Maker Portal changes back to source
git commit
flowline deploy test
flowline deploy prod
deploy always operates from source control — it packs from the repo, not from DEV. Flowline
blocks if there are uncommitted changes under Package/, Plugins/Plugins.csproj, or
WebResources/WebResources.csproj (exit code 12), or if local changes have not been
synced to DEV yet (exit code 15 — run flowline sync first, or pass --force drift to skip the check).
Each step below has its own section further down, with the flag that skips it:
- Validate target — confirms the environment exists and its managed/unmanaged type matches what you're deploying.
-
DTAP gate — the solution must already exist in the nearest predecessor environment, at an equal or higher version. (
--skip-dtap-check) -
Pack the solution from
Package/src. -
Solution checker gate — blocks on any Critical finding. (
--skip-solution-check) -
Pre-deploy backup of the target environment. (
--no-backup) -
Orphan cleanup (pre-import) — reports, and for unmanaged solutions also deletes, components no longer in source. (
--no-delete) - Import — as a Dataverse Upgrade if the managed solution already exists in the target, otherwise a plain import. Plugin steps and workflows are always activated on import; unmanaged imports also publish all customization changes (see Managed vs unmanaged).
- Orphan cleanup (post-import) — retries anything deferred by a dependency in step 6.
By default, deploy imports the unmanaged solution. Managed/unmanaged mode is configured
exclusively via clone --managed/sync --managed, saved to .flowline — deploy only ever reads
it, never sets it:
flowline sync --managed # opt into managed for this solution; sync --managed false reverts
flowline deploy prod # uses whatever .flowline currently has configuredFor managed deploys, deploy checks whether the solution already exists in the target and imports
it as a Dataverse Upgrade when it does, so obsolete components get removed automatically. A
first-time install stays a plain import instead — there's no prior version to upgrade from. See
Further reading for why this matters and Orphan cleanup for
what happens on unmanaged targets, where Upgrade doesn't apply.
Unmanaged imports also publish all customizations in the target environment (pac's
--publish-changes), so UI-affecting changes like forms, views, and web resources take effect
immediately. Managed deploys skip this — managed customizations always import already published.
The first time a solution is imported to a target that doesn't have it yet, deploy asks for
confirmation, since Dataverse won't let you switch that target's managed/unmanaged mode later
without an uninstall. Skip it non-interactively with --force first-import; it never fires once the solution already exists there.
When .flowline defines multiple environment URLs, deploy enforces promotion order:
-
Dev is blocked. Use
flowline pushandflowline syncfor the DEV environment. - Existence check. The solution must already exist in the nearest predecessor (e.g. Test before UAT, UAT before Prod). If it doesn't, deploy blocks.
- Version check. The predecessor's installed version must be ≥ the local version. If it isn't, deploy blocks and shows both version numbers.
flowline deploy prod --skip-dtap-check # bypass existence and version checksA notice prints when the gate is skipped, identifying which predecessor was bypassed.
After packing, deploy runs pac solution check against the packed solution and blocks the deploy if any Critical-severity finding is reported. High/Medium/Low/Informational findings are reported but non-blocking.
The full report is written to artifacts/solution-check/ — check there if you want details beyond the terminal summary.
flowline deploy prod --skip-solution-check # bypass the solution checker gateAfter the solution checker gate passes, deploy takes a manual, on-demand backup of the target environment via pac admin backup before any pre-import Dataverse mutation. A backup failure aborts the deploy. This runs for every deploy target, not just Prod.
flowline deploy prod --no-backup # bypass the pre-deploy backupPlain solution imports are additive — components removed from the local solution since the last
deploy don't get removed by the import itself. For unmanaged deploys, Flowline diffs the target
against local Solution.xml before import and closes that gap directly:
- AUTO components (plugin assemblies, steps, web resources, custom APIs, workflows) — deleted if solo, or removed from the solution if shared with other solutions.
- MANUAL components (tables, columns, forms, views, roles, connection references, bots) — listed in the report but never touched. Remove them via the Maker Portal.
Every orphan is classified into a risk tier, shown in the report:
| Tier | Meaning |
|---|---|
| Prio1 | Blocks deployment — orphaned plugin assembly/type/step/step image |
| Prio2 | Still running deleted logic — active workflow, enabled plugin step, callable custom API, in-use connection reference, published bot |
| Prio3 | No functional impact if left — everything else. Just clutter: leaving them means a less clean environment, not a running problem. |
flowline deploy test --no-delete # print the orphan report without deleting anythingManaged deploys always print this report but never delete anything through it — components already installed as part of a managed solution reject ad-hoc delete calls; only Dataverse's own Upgrade/uninstall path can remove them. When the target already has the solution, that Upgrade runs as part of the import (see Managed vs unmanaged) and the report is a preview of what it's about to remove. On a first-time managed install, there's no Upgrade yet, so flagged components wait for a later redeploy to actually clear.
Components blocked by a dependency during pre-import cleanup are retried after the import completes; failures there are non-blocking and appear as warnings.
Packed solution zips are written to artifacts/. deploy reuses the last packed zip when nothing's
changed since in Package/, Plugins/Plugins.csproj, or WebResources/WebResources.csproj — so
promoting through test → uat → prod builds it once — and prints whether it reused or repacked, and
why, on every run.
On Azure Pipelines and GitHub Actions, the packed zip is surfaced automatically, no setup required:
Azure Pipelines attaches it as a build artifact (named with the solution's version); GitHub Actions
exposes its path as step output artifact-path-<SolutionName> for your own upload step:
- name: Deploy
id: deploy
run: flowline deploy prod
- name: Upload packed solution
uses: actions/upload-artifact@v4
with:
name: ContosoSales
path: ${{ steps.deploy.outputs.artifact-path-ContosoSales }}If each DTAP stage runs on its own CI job, pass the artifact explicitly instead of relying on reuse:
flowline deploy uat --path artifacts/ContosoSales_unmanaged.zip| Option | Description |
|---|---|
<target> |
prod, uat, test, dev, or a direct environment URL |
--path <zip> |
Import this pre-built solution zip instead of packing from source |
--no-delete |
Print the orphan report without executing deletions |
--skip-dtap-check |
Skip existence and version promotion checks |
--skip-solution-check |
Skip the solution checker gate |
--no-backup |
Skip the pre-deploy environment backup |
See 03-Command-Reference#deploy for the full option reference.
pac solution pack supports a mapping file that swaps in local build output for the DLL/web
resources sitting in Package/src, instead of what sync captured from DEV. Flowline doesn't wire
this up: Package/src is what was actually pushed to DEV and exercised there, and mapping at pack
time would ship a binary nobody tested instead. See
11-Philosophy#1-code-flows-up-configuration-flows-down--dev-is-the-verification-gate — deploy
promotes what was tested, not what was built last.
It also doesn't hold up mechanically — plugin package versioning breaks mapping's file-identity lookup, forcing dual map files and build-output post-processing just to keep it working. See neronotte's Your Dataverse solutions deserve better builds and the follow-up When plugin packages break your mapping files for the issues you'll hit going down that road.
- Upgrade or update a solution — Microsoft's own explanation of Update vs. Upgrade, and why only Upgrade removes components.
-
Solution upgrade vs. solution import in Azure DevOps — Rob Wood on the same plain-import-defaults-to-Update gotcha in hand-rolled pipelines, and the conditional
--stage-and-upgradepatterndeployapplies automatically.