Skip to content

Fabric CICD - Two Different Worlds

Where the low-code and code-first halves of Fabric CI/CD fail to meet, and my thoughts on what would close the gap


I ended the Deployment Plans post saying it feels like there are two parallel worlds in Fabric CI/CD, with a gap between them. I am writing this post both as a cathartic act and to consolidate my thoughts.

Two Worlds

The low-code world lives in the portal. You work in a workspace and git integration saves it to a branch. Branch-out gives you a workspace of your own. Deployment pipelines copy items from stage to stage, deployment rules and Variable Libraries hold the values that change per stage, and a Deployment Plan sets the order and runs steps around the copy. Everything runs inside Fabric.

The code-first world lives in a repo. You publish the repo into a workspace with fabric-cicd (or fab deploy, which wraps it), and parameter.yml swaps out values on the way in. A YAML pipeline runs whatever needs to happen before and after.

Mixed Messages

The documentation reflects the same split. There are reams of it, scattered far and wide across Microsoft Learn and separate sites for fabric-cicd, the CLI and the Terraform provider.

Documentation
Page What it covers Dated (ms.date)
Guidance
Introduction to CI/CD in Microsoft Fabric The platform at a glance and a reference architecture 23 Aug 2026
CI/CD workflow options Four options: Git integration, Fabric Items APIs, deployment pipelines, ISVs 18 Mar 2026
Fabric CI/CD concepts and best practices The concepts page: environments, branches, item definitions, three release options 28 May 2026
Best practices for lifecycle management The older best practices page, built around git integration and deployment pipelines 15 Jun 2026
Development process Working in isolation: a branched workspace or a client tool 1 Sep 2026
FAQ Licensing, permissions, plans and some definitions 23 Sep 2026
Tutorials
End-to-end automation Terraform creates dev and test, fabric-cicd promotes 23 Jul 2026
Azure DevOps and fabric-cicd Three long-lived branches, approvals, parameter.yml 19 Feb 2026
Bulk import API One main branch, a pipeline calling Bulk Import Item Definitions 24 Sep 2026
Local deployment fabric-cicd or fab deploy from a laptop 19 Feb 2026
Application lifecycle management Git integration plus deployment pipelines, with no code 24 Jul 2026
Features
Git integration Connect a workspace to a branch, the process and the APIs 1 Sep 2026
Deployment pipelines Stages and pairing, the deployment process, rules and APIs 24 Sep 2026
Variable Libraries Value sets, item references and lifecycle 1 Sep 2026
Deployment plans Order and actions for one native deployment 24 Sep 2026
Dependency binding Which references survive a deployment 1 Sep 2026
Source code format Item folders, the .platform file and the logicalId 15 Dec 2025
Tools
fabric-cicd The Python library: deployment overview, parameterization, item types v1.3.0, Aug 2026
Fabric CLI fab, including fab deploy, which wraps fabric-cicd rolling
Terraform provider Workspaces, connections, git links and roles as code rolling

My problem isn't the amount, and it isn't that any of the answers is wrong. CI/CD is a divisive topic, and requirements and personal preference rightly come into it, so I don't expect one answer for everyone. A page that says "do it this way" alienates every team doing it another way. Every approach in the docs works for somebody. My problem is that the docs never say who each answer is for. Every page has a reader in mind, and you can tell from what it assumes: a portal or a repo, a Power BI developer or an engineer. But no page names its persona. The overview is the clearest example. It calls git integration plus deployment pipelines "the most efficient" experience and fabric-cicd "the most widely adopted" tool on the same page, and never says which kind of team should pick which.

If you already know what you're doing, you read past that. Someone coming to CI/CD for the first time can't. They pick the pages that sound like them, and by the third page they are royally confused, because they've wandered across a persona boundary, or into guidance for the same persona written by a different author with their own bias of what is best.

Compare How Microsoft develops with DevOps, which says what Microsoft's own teams do, at what scale, and what they learned. Fabric CI/CD has nothing like it, so every team has to work it out for themselves.

Below are the contradictions, split by the persona each page implies. The concepts page speaks to both, so it turns up in both.

The Low-Code Persona

These pages assume a portal, git integration and deployment pipelines: the overview's "most efficient" experience, the portal tutorial, the older best practices page, the development process page and the deployment pipeline docs. Within that set:

  • Values per stage. The best practices page says use parameters "whenever possible" and deployment rules for data sources, and never mentions Variable Libraries. The concepts page says use Variable Libraries "whenever possible".
  • The dev workspace. The portal tutorial says the whole team shares and edits a git-connected dev workspace, then three steps later says each member should branch out to avoid editing it. The concepts page says treat the first commit from dev as a one-time operation and don't commit from it again, then allows exactly that in "the simplest development scenario".
  • Feature workspaces. The development process page says a separate workspace per developer. The best practices page says keep one and re-point it, or skip the workspace if you use a client tool. The portal tutorial says you can delete it once the branch merges.
  • Other workspaces. The best practices page says split workspaces by team. The binding page says a reference to another workspace never rebinds. The deployment process page says deployment pipelines do rebind them, if the items sit at the same stage position and both pipelines have the same number of stages. The concepts page says use item reference variables, which the item reference page describes as static ids you update manually.
  • Automation. The workflow options page says the deployment pipeline APIs give you a build and release process like the other options. The concepts page says setup always needs manual steps and approvals are limited. Deployment rules can only be created in the UI by the item's owner.

The Code-First Persona

These pages assume a repo, a pipeline, and fabric-cicd or the APIs: option 2 of the workflow options page, the fabric-cicd tutorial, the end-to-end tutorial, the bulk import tutorial, the local deployment tutorial and the fabric-cicd docs. Within that set:

  • Branching. Option 2 is for trunk-based teams. The tutorial it links to needs three long-lived branches. The fabric-cicd docs merge to the default branch and cherry-pick into the upper ones. The bulk import tutorial puts every environment on one main branch. Four pages, three models, one persona.
  • The dev workspace. The fabric-cicd tutorial connects dev to git and commits from it. The fabric-cicd docs say deployed workspaces are only updated by script.
  • Values per environment. The fabric-cicd tutorial does everything with find_replace and never creates a variable library, then has a tip recommending you use one. The workflow options page says you need a build environment with a custom script. The concepts page says Variable Libraries "whenever possible".

Fixing the Documentation

  • Pick a handful of personas, pick one best option per persona and be consistent. Branching, the dev workspace and environment values should each have one recommended answer per persona, and every page for that persona should give the same one. There will be more than one way of doing something, but pick one and run with it.
  • Put each persona's pages in one place. The concepts page, the workflow options page and the older best practices page cover the same ground three different ways. Merge them into one, and stop splitting a persona's guidance across fundamentals, the CI/CD tree and the fabric-cicd site.

The Gaps

Documentation aside, these are the gaps I keep hitting. fabric-cicd plugs the first four, one workspace at a time. The rest are left to you.

Gap fabric-cicd Ask
Binding within a workspace. Auto-binding misses any reference stored as an object id, name or URI rather than a logicalId parameter.yml rewrites anything, if you know what to rewrite A logicalId on every item reference, complete auto-binding coverage, and more items reading Variable Libraries
Binding across workspaces. Git never rebinds. Deployment pipelines only do between pipelines with matching stages $workspace.<name>, with names hard-coded per environment A workspace identity (What's Missing)
Empty workspaces. Nothing to bind to, and the value set stays on Default One workspace at a time Activate the value set named after the environment on every deploy (What's Missing), and item reference variables that store a logicalId
Service principals. Update From Git, Deploy Stage Content and bulk import only work when every item in the operation supports them Same platform limits More support for SPNs
No preview. Compare is a diff between stages, and change review only covers semantic models and dataflows A rule that matches nothing is skipped silently A preview on every deploy

What's Missing

The gaps above have three causes. The tools don't agree on what makes an item the same item. The native features only have partial coverage of the items and tools they should cover. And each world has features the other can't use.

Azure settled most of this years ago: a Bicep template refers to resources by symbolic name, is usually deployed into a resource group per environment, and what-if shows what would change before it does.

Azure Fabric today The ask
Symbolic name in a template A logicalId, for items only A logicalId for workspaces too
Resource group per environment A workspace named by convention An environment on the workspace
Parameter file per environment Value set, or parameter.yml The value set picked by the environment
what-if The compare view A preview on every deploy

LogicalIds First

Every deployment has to decide whether this item is the same one as that one, and each tool decides differently. Bulk import matches on logicalId when name pairing is off. Git uses the logicalId, and when an item with the same name and type has a different one it asks you to overwrite it. Deployment pipelines keep their own pairing link. fabric-cicd looks items up by name and type, so a rename creates a new item.

It should be one rule everywhere: match on the logicalId first, and fall back to the item id, then name and type, only when there isn't one. Assign the logicalId at creation rather than when an item first reaches git or a deployment plan, and let every reference store one, so it resolves to the right copy in whichever environment it lands in. That is what closes the binding gaps above.

Workspaces need the same treatment. They have no logicalId, so nothing says "this is the test copy of the data workspace", and everyone says it with naming conventions instead. Deployment pipelines half-built it. A stage is an environment, and autobinding across pipelines uses the stage position to find the right copy of another workspace. Microsoft started down this road in January 2022, before Fabric existed, and left it inside the pipeline, where you can read it through the pipeline's own API but never from the workspace. Put it on the workspace instead:

What a workspace could carry
{
  "id": "55555555-5555-5555-5555-555555555555",
  "displayName": "contoso-data-test",
  "logicalId": "e553e3b0-0260-4141-a42a-70a24872f88d",
  "environment": "test"
}

logicalId says which workspace this is a copy of, and environment says which copy. A reference stored as workspace logicalId plus item logicalId then resolves against the workspace with the same environment, in git sync, deployment pipelines and bulk import alike. The environment picks the active value set, so a new workspace no longer lands on Default. An empty environment fills in order, since nothing needs an id that doesn't exist yet. And the workspace list can group by logicalId, which does more for sprawl than any naming convention.

Feature Completeness

The rest of the gaps are native features that stop short. Auto-binding, Variable Libraries, the APIs and service principal support each cover some items, some tools or some identities. A feature should work for everything it touches.

Feature Parity

Completeness fixes each feature. This would also resolve parity. For example a team currently has to use parameter.yml to define environment-specific values, because Variable Libraries are not supported for a item type. So a team picks a world by the features it needs rather than how it wants to work, and moving later means starting again.

The two worlds then become two front ends. A team could start with deployment pipelines and move to a devops pipeline without rebuilding anything, and two teams could share a repo with different tools. The only difference is process.

Conclusion

I'm not asking for the low-code side to go away. I'm asking for one rule for what makes an item, or a workspace, the same one, for the native features to be complete, and for both worlds to have them. Right now the code-first tooling is more complete, the two worlds share few features, teams end up in silos, and there's no clear path from one to the other. Rant over.

Comments