Skip to content

GitHub Actions

This page provides complete, copy-ready workflow files for deploying Unified Data Platform Deployment with GitHub Actions. These workflows have been proven end-to-end against live Fabric workspaces.

For a working reference repository, see github.com/PatrickGallucci/udp-cicd-example.


Repository example harness

The udp-cicd repository includes a manual-only workflow at .github/workflows/deployment-examples.yml. It builds the selected commit and drives the shared PowerShell harness across all 14 folders under examples/. Use it to validate the shipped catalogue, not as an automatic production deployment workflow for a consuming project.

Input Purpose
mode Selects Preflight, Validate, Plan, or Deploy
example_ids Optionally limits the run to comma-separated IDs such as 01,04
inputs_path Points to reviewed, repository-relative non-secret input JSON
keep_resources Retains isolated deployment resources for investigation
allow_tenant_admin_changes Unlocks the separately guarded example 09 mutation path

Preflight and Validate receive no protected environment or Azure token. Plan and Deploy require refs/heads/main, the protected deployment-tests environment, and OIDC authentication. Pester is installed and run, and the CLI is restored and built, in a separate prerequisite job with no OIDC permission or deployment secrets. Plan and deployment jobs consume the prebuilt CLI artifact before Azure authentication. The workflow publishes the Pester result separately from the run evidence, including when a job fails.

The implementation and safety contract are documented in deployment-tests/README.md. For the matching Azure DevOps setup, see Run the deployment examples in Azure DevOps.


1. Setting up secrets

Go to your repository on GitHub: Settings > Secrets and variables > Actions. Add the following repository secrets:

The generic application workflows below use a client secret for broad runner compatibility. Prefer the OIDC pattern used by the repository example harness when your GitHub organization and Azure tenant support federation.

Secret Description
AZURE_TENANT_ID Your Entra ID (Azure AD) tenant GUID
AZURE_CLIENT_ID The service principal's application (client) ID
AZURE_CLIENT_SECRET The service principal's client secret

These secrets are referenced as ${{ secrets.AZURE_TENANT_ID }}, etc., in the workflow files below.

See Service Principal Setup for instructions on creating the service principal and granting it workspace access.


2. Setting up environments with approval gates

Go to Settings > Environments in your repository. Create the following environments:

Environment Configuration
dev No protection rules. Deployments run automatically on merge.
staging Optional: add required reviewers if you want a gate before staging.
production Required reviewers. Add one or more team members who must approve before production deploys run. Optionally add a wait timer (e.g., 5 minutes) to allow time to cancel.

You can also scope environment secrets. If dev and prod use different service principals, add the AZURE_CLIENT_ID and AZURE_CLIENT_SECRET secrets at the environment level instead of the repository level.


3. CI workflow: PR validation

This workflow runs on every pull request that touches the deployment definition. It validates the schema and policies, then runs a plan to show what would change.

Create .github/workflows/udp-ci.yml:

name: Fabric CI

on:
  pull_request:
    paths:
      - 'udp.yml'
      - 'notebooks/**'
      - 'sql/**'
      - 'agent/**'
      - 'pipelines/**'

jobs:
  validate:
    name: Validate Deployment
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '9.0.x'

      - name: Install udp-cicd
        run: dotnet tool install --global udp-cicd

      - name: Validate
        run: udp-cicd validate --strict

  plan:
    name: Plan (${{ matrix.target }})
    runs-on: ubuntu-latest
    needs: validate
    strategy:
      matrix:
        target: [dev]
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '9.0.x'

      - name: Install udp-cicd
        run: dotnet tool install --global udp-cicd

      - name: Plan deployment
        run: udp-cicd plan -t ${{ matrix.target }}
        env:
          AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
          AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
          AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}

4. CD workflow: deploy on merge

This workflow runs when changes are merged to main. It deploys sequentially through dev, staging, and production, with an approval gate before production.

Create .github/workflows/udp-cd.yml:

name: Fabric CD

on:
  push:
    branches: [main]
    paths:
      - 'udp.yml'
      - 'notebooks/**'
      - 'sql/**'
      - 'agent/**'
      - 'pipelines/**'

jobs:
  # ── Deploy to Dev (automatic) ─────────────────────
  deploy-dev:
    name: Deploy to Dev
    runs-on: ubuntu-latest
    environment: dev
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '9.0.x'

      - name: Install udp-cicd
        run: dotnet tool install --global udp-cicd

      - name: Deploy
        run: udp-cicd deploy --target dev -y
        env:
          AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
          AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
          AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}

      - name: Verify deployment
        run: udp-cicd status --target dev
        env:
          AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
          AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
          AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}

  # ── Deploy to Staging (automatic after dev) ────────
  deploy-staging:
    name: Deploy to Staging
    runs-on: ubuntu-latest
    needs: deploy-dev
    environment: staging
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '9.0.x'

      - name: Install udp-cicd
        run: dotnet tool install --global udp-cicd

      - name: Deploy
        run: udp-cicd deploy --target staging -y
        env:
          AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
          AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
          AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}

      - name: Verify deployment
        run: udp-cicd status --target staging
        env:
          AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
          AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
          AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}

  # ── Deploy to Production (requires approval) ──────
  deploy-prod:
    name: Deploy to Production
    runs-on: ubuntu-latest
    needs: deploy-staging
    environment: production
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '9.0.x'

      - name: Install udp-cicd
        run: dotnet tool install --global udp-cicd

      - name: Deploy
        run: udp-cicd deploy --target prod -y
        env:
          AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
          AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
          AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}

      - name: Verify deployment
        run: udp-cicd status --target prod
        env:
          AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
          AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
          AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}

5. Drift check workflow (scheduled)

This workflow runs on a schedule to detect changes made outside of the deployment pipeline. It reports drift as a workflow annotation and uploads the report as an artifact.

Create .github/workflows/udp-drift.yml:

name: Drift Check

on:
  schedule:
    - cron: '0 8 * * 1-5'  # Weekdays at 8:00 AM UTC
  workflow_dispatch:

jobs:
  drift:
    name: Check Drift (${{ matrix.target }})
    runs-on: ubuntu-latest
    strategy:
      matrix:
        target: [dev, staging, prod]
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '9.0.x'

      - name: Install udp-cicd
        run: dotnet tool install --global udp-cicd

      - name: Check for drift
        id: drift
        run: udp-cicd drift -t ${{ matrix.target }} --format json > drift-report.json
        env:
          AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
          AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
          AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
        continue-on-error: true

      - name: Upload drift report
        if: steps.drift.outcome == 'failure'
        uses: actions/upload-artifact@v4
        with:
          name: drift-report-${{ matrix.target }}
          path: drift-report.json

      - name: Annotate on drift
        if: steps.drift.outcome == 'failure'
        run: echo "::warning::Drift detected in '${{ matrix.target }}' environment. See drift-report artifact for details."

      - name: Fail on drift
        if: steps.drift.outcome == 'failure'
        run: exit 1

6. Destroy workflow (manual dispatch)

This workflow tears down all deployment resources in a target workspace. It requires manual dispatch and confirmation to prevent accidental deletion.

Create .github/workflows/udp-destroy.yml:

name: Destroy Environment

on:
  workflow_dispatch:
    inputs:
      target:
        description: 'Target environment to destroy'
        required: true
        type: choice
        options:
          - dev
          - staging
      confirm:
        description: 'Type the target name to confirm destruction'
        required: true
        type: string

jobs:
  destroy:
    name: Destroy ${{ github.event.inputs.target }}
    runs-on: ubuntu-latest
    environment: ${{ github.event.inputs.target }}
    steps:
      - name: Verify confirmation
        run: |
          if [ "${{ github.event.inputs.confirm }}" != "${{ github.event.inputs.target }}" ]; then
            echo "::error::Confirmation does not match target. Expected '${{ github.event.inputs.target }}', got '${{ github.event.inputs.confirm }}'."
            exit 1
          fi

      - uses: actions/checkout@v4

      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '9.0.x'

      - name: Install udp-cicd
        run: dotnet tool install --global udp-cicd

      - name: Destroy resources
        run: udp-cicd destroy -t ${{ github.event.inputs.target }} -y
        env:
          AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
          AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
          AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}

Production protection

The prod option is intentionally excluded from the destroy workflow's target choices. If you need to destroy production resources, do so through a separate process with additional safeguards.


7. Example of a working pipeline run

A successful CD run looks like this in the GitHub Actions UI:

Fabric CD                                    ✓ completed in 4m 32s
├── Deploy to Dev                            ✓ 1m 12s
│   ├── Checkout                             ✓ 2s
│   ├── Setup .NET 9                         ✓ 8s
│   ├── Install udp-cicd                   ✓ 15s
│   ├── Deploy                               ✓ 42s
│   │   Deploying to target: dev
│   │   Workspace: contoso-analytics-dev
│   │     ✓ Lakehouse: bronze (unchanged)
│   │     ✓ Lakehouse: silver (unchanged)
│   │     ✓ Notebook: ingest_to_bronze (updated)
│   │     ✓ Pipeline: daily_ingest (unchanged)
│   │   Deployed 4 resources (1 updated, 3 unchanged)
│   └── Verify deployment                    ✓ 5s
├── Deploy to Staging                        ✓ 1m 08s
│   └── ...
└── Deploy to Production                     ⏳ waiting for approval
    └── PatrickGallucci approved               ✓ 2m 12s

8. Reference repository

A complete working example with all four workflows, a multi-target udp.yml, and sample notebooks is available at:

github.com/PatrickGallucci/udp-cicd-example