Skip to main content
TechExplainedTechExplained
|
How-toLevel: Advanced

How to set up CI/CD for Microsoft Fabric

The control plane in code, the data plane through a release mechanism: which of the four deployment patterns fits you, and why your network choice may have already made that call.

TechExplained 10 min readPublished: 6 August 2026Last updated: 6 August 2026
#fabric#ci/cd#terraform#deployment pipelines#devops#how-to
Architect at a screen showing a Fabric release pipeline running from workspace to production.
All how-tos
  1. 01

    Separate the control plane from the data plane

    Fabric gives you four ways to deploy items and two languages to describe infrastructure. Turning that into a tooling debate means choosing too early. The question to answer first is which things rarely change and have wide blast radius, and which change every sprint and stay local.

    LayerOwnerToolingPace
    Azure foundationPlatform engineeringBicep or TerraformRarely
    Fabric control planePlatform engineeringTerraform or REST APIsRarely
    Fabric itemsData engineering and BIGit, fabric-cicd or pipelinesEvery sprint
    Release orchestrationDevOpsAzure DevOps or GitHub ActionsEvery change

    And what actually sits in each layer:

    • Azure foundation: resource groups, storage, Key Vault, managed identities, Log Analytics and the Fabric capacity itself.
    • Fabric control plane: workspaces, capacity assignment, domains, connections, roles and the Git wiring of each workspace.
    • Fabric items: lakehouses, warehouses, notebooks, data pipelines, dataflows, semantic models, reports, eventhouses, Spark environments and variable libraries.
    • Release orchestration: validation, approvals, the order of the steps and the checks afterwards.

    That line is not a matter of taste. Creating a workspace touches identity, capacity and networking, and you want to see that in a plan before it happens. Changing a notebook touches nothing outside that workspace, and you specifically do not want to push it through a platform team's approval round.

    Decision
    put the control plane in code and let the data plane move through a release mechanism. Skip that line and one of two things happens: your infrastructure gets clicked together by hand, or your notebook change waits for a terraform apply.
  2. 02

    Pick one deployment mechanism for your items

    Microsoft describes four patterns. They are not mutually exclusive, but you want one of them as the main road per solution. Two mechanisms on the same workspace means two sources of truth.

    ApproachSource of truthFitsLimitation
    Git with updateFromGitGitTeams with dev, test and prod branchesMerge conflicts
    fabric-cicd or Items APIGitTrunk-based teams that scriptBuild it yourself
    Deployment pipelinesWorkspaceWorkspace-to-workspace promotionManual setup
    API-driven, multi-tenantExternal configISVs deploying to many customersHigh initial complexity

    The first two put Git in the lead role and differ mainly in who performs the deployment: Fabric itself, or your pipeline. The third inverts it. There the Development workspace is the source and you promote content to the next stage, with item pairing, deployment rules and a deployment history. That works well as long as you keep the first stage connected to Git; without Git you have promotion without a way back.

    There is one constraint that can make this decision for you, and it does not appear in any list of benefits. Deployment pipelines do not work together with inbound and outbound access protection on a workspace. It is mutual, too: if a workspace sits in a deployment pipeline you can no longer enable outbound access protection, and if that protection is already on you cannot add the workspace to a pipeline.

    Decision
    settle your network requirements before your deployment mechanism. If the workload needs workspace-level network isolation, deployment pipelines are off the table and you are left with Git integration or fabric-cicd.
    Pitfall
    not every item type is supported by every mechanism, and that shifts with each release. Check the support matrix for the item types you actually use before you pick the mechanism, not after.
  3. 03

    Set up workspaces and Git integration

    Give every environment its own workspace. In Fabric the workspace is the security and lifecycle boundary, so folders or name prefixes inside a single workspace do not give you the separation you think you have.

    EnvironmentWorkspaceIdentityData source
    DevelopmentPer developer or per teamDevelopersDevelopment data
    TestOne integration workspaceCI/CD service principalTest data at volume
    ProductionNo manual changesDedicated production principalProduction sources

    Connect the first workspace to Git and put the items in a dedicated folder, for example /fabric. Fabric writes each item as a folder named {display name}.{item type} with a .platform file holding the metadata and the logical identity. If your pipeline YAML and scripts live in that same folder, the sync gets confused.

    Keep feature branches short and delete them after the merge. Fabric items are generated files; the longer two people work next to each other, the better the odds of a merge you have to settle by hand.

    Decision
    one repository for the whole solution, with a separate folder per workspace and a separate folder for your scripts and workflows. A repository per workspace sounds tidy, but then nobody holds a single version of the whole.
    Pitfall
    never edit logicalId in a .platform file. Fabric uses that identity to tie an item in Git to an item in the workspace. Change it and the link is gone, and the next sync produces a duplicate.
  4. 04

    Put the infrastructure in code

    The Fabric capacity is an ordinary Azure resource and belongs in the same IaC as the rest of your Azure foundation. For the Fabric objects on top of it, the workspaces and their Git wiring, you need the Terraform provider for Fabric or the REST APIs.

    Bicep is the obvious choice if the rest of your organisation already works Azure-native. Terraform wins as soon as you want Azure and Fabric in one plan, because then the capacity and the workspace hanging off it share a state.

    resource cap 'Microsoft.Fabric/capacities@2023-11-01' = {
      name: 'fabprod001'
      location: location
      sku: {
        name: 'F32'
        tier: 'Fabric'
      }
      properties: {
        administration: {
          members: [capacityAdminObjectId]
        }
      }
    }
    

    The workspace and its Git wiring in Terraform:

    resource "fabric_workspace" "dev" {
      display_name = "ws-sales-dev"
      capacity_id  = data.fabric_capacity.cap.id
    }
    
    resource "fabric_workspace_git" "dev" {
      workspace_id            = fabric_workspace.dev.id
      initialization_strategy = "PreferRemote"
    
      git_provider_details = {
        git_provider_type = "AzureDevOps"
        organization_name = var.ado_org
        project_name      = var.ado_project
        repository_name   = var.ado_repo
        branch_name       = "main"
        directory_name    = "/fabric"
      }
    
      git_credentials = {
        source        = "ConfiguredConnection"
        connection_id = fabric_connection.ado.id
      }
    }
    

    Put the Terraform state in protected cloud storage. State holds resource IDs, configuration and sometimes secrets, and a state file on a laptop or in the repo is exactly the kind of risk this whole setup is meant to remove.

    Note
    there is also a newer preview version of this resource. For production IaC you pin to the stable version, because a preview version may change without notice and you do not want a terraform plan that says something different from yesterday.
    Decision
    Bicep for Azure-native organisations, Terraform as soon as Azure and Fabric have to appear in the same plan. The worst answer is a bit of both, because then no single plan shows the whole.
  5. 05

    Sort out identity, and the four places it goes wrong

    Automation should run as a service principal, not as an employee. A personal account in a pipeline falls outside your normal access management and breaks the day someone changes roles.

    MethodWhenWhy
    OIDC or workload identity federationGitHub Actions, Azure DevOpsNo secret to rotate
    Managed identityAzure-hosted runnersNo credentials to manage
    Service principal with certificateWhere federation is not possibleEasier to govern than a secret
    Service principal with secretLast resortKey Vault only, with rotation
    PATGitHub source control onlyTied to a person, so fragile

    What sinks almost every first automated deployment is not the authentication but the authorisation around it. A service principal has to be allowed in four places, and all four live somewhere else:

    1. The tenant setting Service principals can create workspaces, connections, and deployment pipelines, for creating those three things.
    2. The tenant setting Service principals can call Fabric public APIs, for all other API traffic.
    3. An explicit role on the workspace itself. Membership of the tenant setting grants no access to content.
    4. Access in your Git provider. In Azure DevOps the principal has to be known in the organisation or project before the Git wiring works.

    Those first two settings do not cover for each other. Enable only the first and your pipeline will happily create a workspace and then fail on the first item call, with an error that does not say which of the two is missing.

    Decision
    federation over secrets, and scope the principal per environment. One principal that can reach everything is the reason a test deployment eventually touches production.
  6. 06

    Build the pipeline and the road to production

    Azure DevOps and GitHub Actions do the same work here: validate, update infrastructure, deploy items, test and put production behind a gate. The difference is where the governance lives.

    Azure DevOpsGitHub Actions
    FitsOrganisations on Azure ReposTeams that keep everything in the repo
    AuthenticationService connection with federationOIDC through azure/login
    ApprovalEnvironments with approvalsEnvironments with reviewers
    SecretsKey Vault variable groupEnvironment secrets

    A workflow that puts the two layers in the right order:

    name: fabric-cicd
    
    on:
      push:
        branches: [main]
        paths:
          - "fabric/**"
          - "infra/**"
    
    permissions:
      id-token: write
      contents: read
    
    jobs:
      deploy-test:
        runs-on: ubuntu-latest
        environment: test
        steps:
          - uses: actions/checkout@v4
    
          - uses: azure/login@v2
            with:
              client-id: ${{ secrets.AZURE_CLIENT_ID }}
              tenant-id: ${{ secrets.AZURE_TENANT_ID }}
              subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
    
          - name: Update the control plane
            run: |
              terraform -chdir=infra init
              terraform -chdir=infra apply -auto-approve
    
          - name: Deploy the items
            run: |
              pip install fabric-cicd azure-identity
              python scripts/deploy.py --environment test
    

    For environment-specific values use a variable library rather than parameter files. It is supported by Git, deployment pipelines and the APIs, and it can carry its own value set per stage, so you no longer need a find-and-replace step between test and production.

    The order from commit to production:

    1. Developer creates a short branch and works in a dedicated workspace.
    2. The change is committed to Git as an item definition.
    3. The pull request validates IaC, YAML, item definitions and secrets.
    4. After review the merge lands on the integration branch.
    5. The pipeline updates the control plane first.
    6. A check confirms workspace, capacity, roles and Git status.
    7. Only then do the items go out, through the mechanism from step 2.
    8. A script activates the value set and repairs broken references.
    9. Integration tests run on realistic data volume and capacity.
    10. An approval guards the transition to production.
    11. Production runs under its own principal with minimal rights.
    12. Monitoring confirms the release does what it should.

    For monitoring afterwards the retention periods are worth knowing, because they decide how long you can still investigate an incident:

    SourceForRetention
    Monitoring hubJob status and history per itemContinuous
    Workspace monitoringQueryable logs and metrics30 days
    Capacity Metrics appConsumption and throttling14 days
    Audit logs in PurviewWho did what to which item30 days
    Decision
    one pipeline that runs both layers in a fixed order, with production behind an approval. Two separate pipelines that each do half look tidier, but then nobody guards the order between them.
    Important
    Git stores your item definitions, not your data. A git revert restores a notebook but does not undo what that notebook wrote to a lakehouse. So work out your rollback in terms of data impact and not just definitions, and rehearse it once before you need it.
    Note
    workspace monitoring and Log Analytics cannot both be on for the same workspace. Choose deliberately, because switching means removing the existing wiring first.

Common mistakes

The six you meet in almost every Fabric estate:

MistakeWhy it hurtsBetter
One workspace for all environmentsProduction changes by accidentA workspace per environment
Item definitions among scriptsGit sync goes wrongA separate folder, e.g. /fabric
Hard-coded workspace and item IDsWorks in dev, breaks in testVariable library
Deployment pipeline without GitPromotion with no way backConnect the first stage to Git
Assuming the data comes alongThe lakehouse is empty after deployA load script after the deployment
No approval for productionEvery merge is a production releaseAn environment with reviewers

What to do next

This setup assumes your tenant, capacities and workspace topology are already in place, because everything in this guide hangs off them. See How to set up Microsoft Fabric. If you run Databricks alongside Fabric, then How to set up CI/CD for Azure Databricks is the counterpart to this guide, with the same split between platform and workload.

The process at a glance

Click a step for its key decision

Decision

Separate the control plane from the data plane

put the control plane in code and let the data plane move through a release mechanism. Skip that line and one of two things happens: your infrastructure gets clicked together by hand, or your notebook change waits for a terraform apply.

How to set up CI/CD for Microsoft Fabric | TechExplained