GitHub Actions¶
This page provides complete, copy-ready workflow files for deploying Fabric Automation Bundles with GitHub Actions.
For a working reference repository, see github.com/dereknguyenio/fabric-fab-cicd-example.
Setting up secrets¶
Go to your repository on GitHub: Settings > Secrets and variables > Actions. Add the following repository secrets:
| 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.
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.
CI workflow: PR validation¶
This workflow runs on every pull request that touches the bundle definition. It validates the schema and policies, then runs a plan to show what would change.
Create .github/workflows/fabric-ci.yml:
name: Fabric CI
on:
pull_request:
paths:
- 'fabric.yml'
- 'notebooks/**'
- 'sql/**'
- 'agent/**'
- 'pipelines/**'
jobs:
validate:
name: Validate Bundle
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install fab-bundle
run: pip install fabric-automation-bundles
- name: Validate
run: fab-bundle 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-python@v5
with:
python-version: '3.12'
- name: Install fab-bundle
run: pip install fabric-automation-bundles
- name: Plan deployment
run: fab-bundle 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 }}
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/fabric-cd.yml:
name: Fabric CD
on:
push:
branches: [main]
paths:
- 'fabric.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-python@v5
with:
python-version: '3.12'
- name: Install fab-bundle
run: pip install fabric-automation-bundles
- name: Deploy
run: fab-bundle 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: fab-bundle 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-python@v5
with:
python-version: '3.12'
- name: Install fab-bundle
run: pip install fabric-automation-bundles
- name: Deploy
run: fab-bundle 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: fab-bundle 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-python@v5
with:
python-version: '3.12'
- name: Install fab-bundle
run: pip install fabric-automation-bundles
- name: Deploy
run: fab-bundle 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: fab-bundle 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 }}
Drift check workflow (scheduled)¶
This workflow runs on a schedule to detect changes made outside of the bundle pipeline. It reports drift as a workflow annotation and uploads the report as an artifact.
Create .github/workflows/fabric-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-python@v5
with:
python-version: '3.12'
- name: Install fab-bundle
run: pip install fabric-automation-bundles
- name: Check for drift
id: drift
run: fab-bundle 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
Destroy workflow (manual dispatch)¶
This workflow tears down all bundle resources in a target workspace. It requires manual dispatch and confirmation to prevent accidental deletion.
Create .github/workflows/fabric-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-python@v5
with:
python-version: '3.12'
- name: Install fab-bundle
run: pip install fabric-automation-bundles
- name: Destroy resources
run: fab-bundle 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.
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 Python 3.12 ✓ 8s
│ ├── Install fab-bundle ✓ 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
└── dereknguyenio approved ✓ 2m 12s
Reference repository¶
A complete working example with all four workflows, a multi-target fabric.yml, and sample notebooks is available at: