Contents

Chapter 1

Agent Pool Sizing Guide

Self-hosted vs Microsoft-hosted agents for data platform CI/CD workloads.

Datanest Digital — https://datanest.dev


Overview

Choosing the right agent pool strategy directly impacts pipeline execution time,

cost, and security. This guide provides sizing recommendations for the four pipeline

types included in this product, with concrete guidance on when to use Microsoft-hosted

agents vs self-hosted agents.


Decision Matrix

FactorMicrosoft-HostedSelf-Hosted
Setup effortNoneMedium to High
MaintenanceNone (managed by Microsoft)OS patching, tool updates
Cost modelPer-minute (free tier: 1,800 min/month)Infrastructure cost only
Network accessPublic internet onlyPrivate VNET, on-premises
Startup latency30-90 secondsNear-instant (if pre-warmed)
Disk space~10 GB freeCustom (100+ GB possible)
Pre-installed toolsStandard setFully customizable
Parallel jobs1 free, then paidLimited by agent count
Stateful cachingNo (ephemeral VMs)Yes (persistent disk)
Private endpoint accessNoYes

Recommendation by Pipeline

Terraform Infrastructure Pipeline

Recommended: Self-hosted (for production), Microsoft-hosted (for dev)

Reason
Terraform state storage may be behind a private endpoint
State files can be large; persistent disk caching avoids re-download
Provider plugins (~500 MB) benefit from disk caching between runs
Production infrastructure changes need network access to private VNETs

Agent Sizing:

  • vCPUs: 2
  • RAM: 4 GB
  • Disk: 50 GB (SSD)
  • Network: VNET with access to state storage and target subscriptions

Databricks DABs Deploy Pipeline

Recommended: Microsoft-hosted (all environments)

Reason
Databricks CLI communicates over HTTPS to workspace public endpoints
No large local dependencies; CLI is a small binary
Python linting and unit tests run quickly on standard agents
No persistent state needed between runs

If using private-link Databricks workspaces: Switch to self-hosted agents

with VNET connectivity to the workspace's private endpoint.

Agent Sizing (if self-hosted):

  • vCPUs: 2
  • RAM: 4 GB
  • Disk: 30 GB (SSD)

ADF ARM Deploy Pipeline

Recommended: Microsoft-hosted (all environments)

Reason
ARM template deployments use Azure Resource Manager public APIs
No large binaries or build artifacts
Pipeline is lightweight and short-running (typically 2-5 minutes)

Agent Sizing (if self-hosted):

  • vCPUs: 2
  • RAM: 2 GB
  • Disk: 20 GB

SQL Database Migration Pipeline

Recommended: Self-hosted (for staging and production)

Reason
SQL servers are typically behind private endpoints or firewall rules
DACPAC builds require .NET SDK which benefits from NuGet package caching
sqlpackage operations against large databases can be memory-intensive
Schema drift reports need direct database connectivity

Agent Sizing:

  • vCPUs: 4
  • RAM: 8 GB
  • Disk: 50 GB (SSD)
  • Network: VNET with SQL Server private endpoint access

Self-Hosted Agent Setup

Infrastructure Options

OptionProsCons
Azure VM Scale Sets (VMSS)Auto-scaling, managed lifecycleHigher complexity
Azure Container InstancesFast startup, cost-efficientNo persistent disk
Azure Kubernetes ServiceScalable, reusable clustersOperational overhead
Single Azure VMSimple, cheapNo auto-scaling

Azure VM Scale Sets provide the best balance of auto-scaling, cost control, and

maintenance simplicity for self-hosted agents.

bash
# Create a VMSS for pipeline agents
az vmss create \
  --name "vmss-azdo-agents" \
  --resource-group "rg-azdo-agents" \
  --image "Ubuntu2204" \
  --vm-sku "Standard_D2s_v5" \
  --instance-count 0 \
  --upgrade-policy-mode manual \
  --vnet-name "vnet-data-platform" \
  --subnet "snet-azdo-agents" \
  --admin-username "azureagent" \
  --generate-ssh-keys

Register as an Azure DevOps agent pool:

1. Go to Organization Settings > Agent pools > Add pool.

2. Select Azure virtual machine scale set.

3. Select the VMSS resource and configure:

SettingRecommended Value
Maximum number of VMs4
Number of agents to keep on standby1
Delay in minutes before deleting excess idle agents30
Grace period (minutes)15

Agent Software Requirements

Install these tools on the self-hosted agent image:

ToolVersionRequired By
Azure CLILatestAll pipelines
Terraform1.7.xTerraform pipeline
Databricks CLI0.232.xDABs pipeline
.NET SDK8.xSQL migration pipeline
sqlpackageLatestSQL migration pipeline
Python3.11.xDABs pipeline, linting
GitLatestAll pipelines
jqLatestVarious script steps
ruff0.4.xPython linting
Docker (optional)LatestContainer-based builds

Cost Optimization

Microsoft-Hosted

  • Free tier: 1 free parallel job with 1,800 minutes/month.
  • Paid: Each additional parallel job is approximately $40/month.
  • Optimize by reducing pipeline duration (parallel stages, caching).

Self-Hosted

  • VMSS with auto-scale to zero: Pay only when pipelines are running.
  • Spot instances: Use Azure Spot VMs for dev/test agent pools (up to 90% savings).

Not recommended for production deployments due to eviction risk.

  • Reserved instances: For always-on standby agents, 1-year reservations save ~35%.

Cost Estimation (Monthly)

ScenarioMicrosoft-HostedSelf-Hosted (VMSS)
50 pipeline runs/month, 10 min avgFree tier~$15 (D2s_v5 spot)
200 runs/month, 10 min avg~$40 (1 extra parallel)~$45 (D2s_v5)
500 runs/month, 15 min avg~$120 (3 extra parallel)~$90 (D2s_v5)

Security Considerations

1. Network isolation: Place self-hosted agents in a dedicated subnet with NSG rules

that only allow outbound access to required endpoints.

2. Ephemeral agents: Configure VMSS agents to reimage after every job to prevent

credential leakage between pipeline runs.

3. Managed identity: Assign a system-assigned managed identity to the VMSS and use

it for Azure authentication instead of service principal secrets.

4. Agent updates: Enable automatic agent software updates in the pool configuration.

5. Firewall rules: Microsoft-hosted agents use dynamic IP ranges. If your resources

require IP allowlisting, self-hosted agents with a static outbound IP (via NAT Gateway)

are the only reliable option.


Copyright 2024-2026 Datanest Digital. All rights reserved.

Chapter 2

Unified Data Platform CI/CD Architecture Guide

End-to-end CI/CD architecture for deploying infrastructure, compute, orchestration, and storage layers as a single platform.

Datanest Digital — https://datanest.dev


Overview

A modern data platform on Azure consists of multiple layers — infrastructure (Terraform),

compute (Databricks), orchestration (Azure Data Factory), and storage (SQL databases,

Delta Lake). Each layer has distinct deployment concerns, but they share common

dependencies and must be deployed in the correct order.

This guide describes how to compose the four pipelines in this product into a unified

CI/CD architecture that deploys the entire platform end-to-end with proper dependency

ordering, environment isolation, and promotion gates.


Architecture Diagram

┌──────────────────────────────────────────────────────────────────┐
│                        Source Repository                         │
│                                                                  │
│  infrastructure/    databricks/    adf/         database/        │
│  └─ terraform/      └─ bundles/    └─ arm/      └─ dacpac/      │
│     ├─ modules/     └─ src/        └─ params/   └─ schemas/     │
│     └─ envs/        └─ tests/                   └─ migrations/  │
└──────────┬──────────────┬─────────────┬──────────────┬──────────┘
           │              │             │              │
           ▼              ▼             ▼              ▼
┌──────────────┐  ┌────────────┐  ┌──────────┐  ┌──────────────┐
│  Terraform   │  │  DABs      │  │  ADF ARM │  │  SQL DACPAC  │
│  Pipeline    │  │  Pipeline  │  │  Pipeline │  │  Pipeline    │
└──────┬───────┘  └─────┬──────┘  └────┬─────┘  └──────┬───────┘
       │                │              │                │
       ▼                ▼              ▼                ▼
┌──────────────────────────────────────────────────────────────────┐
│                     Environment: dev                             │
│  VNET, Storage, Key Vault │ Databricks │ ADF │ SQL Database     │
└──────────────────────────────┬───────────────────────────────────┘
                               │ (automated)
                               ▼
┌──────────────────────────────────────────────────────────────────┐
│                     Environment: staging                         │
│  VNET, Storage, Key Vault │ Databricks │ ADF │ SQL Database     │
└──────────────────────────────┬───────────────────────────────────┘
                               │ (approval gate)
                               ▼
┌──────────────────────────────────────────────────────────────────┐
│                     Environment: prod                            │
│  VNET, Storage, Key Vault │ Databricks │ ADF │ SQL Database     │
└──────────────────────────────────────────────────────────────────┘

Deployment Order

Infrastructure must exist before application-layer resources can be deployed.

The correct dependency chain is:

1. Terraform Infrastructure (VNET, Storage, Key Vault, Databricks workspace, ADF, SQL Server)
       │
       ├── 2a. Databricks DABs Deploy (notebooks, jobs, DLT pipelines)
       │
       ├── 2b. ADF ARM Deploy (pipelines, datasets, linked services)
       │
       └── 2c. SQL Database Migration (schemas, stored procedures, views)

Steps 2a, 2b, and 2c can run in parallel after Terraform completes, because

they target independent resources.


Orchestrator Pipeline

Create a top-level orchestrator pipeline that invokes the four component pipelines

in the correct order:

yaml
# pipelines/orchestrator.yml
trigger:
  branches:
    include:
      - main
  paths:
    include:
      - infrastructure/**
      - databricks/**
      - adf/**
      - database/**

parameters:
  - name: environment
    type: string
    default: dev
    values: [dev, staging, prod]

stages:
  # --- Stage 1: Infrastructure ---
  - stage: Infrastructure
    displayName: "Deploy Infrastructure"
    jobs:
      - template: terraform-infrastructure.yml
        parameters:
          environment: ${{ parameters.environment }}

  # --- Stage 2: Application Layer (parallel) ---
  - stage: ApplicationLayer
    displayName: "Deploy Application Layer"
    dependsOn: Infrastructure
    jobs:
      - template: databricks-dabs-deploy.yml
        parameters:
          environment: ${{ parameters.environment }}

      - template: adf-arm-deploy.yml
        parameters:
          environment: ${{ parameters.environment }}

      - template: sql-database-migration.yml
        parameters:
          environment: ${{ parameters.environment }}

  # --- Stage 3: Validation ---
  - stage: Validation
    displayName: "End-to-End Validation"
    dependsOn: ApplicationLayer
    jobs:
      - job: IntegrationTests
        steps:
          - script: echo "Run integration test suite"

Selective Deployment

Not every commit touches all four layers. Use path-based triggers to avoid

unnecessary deployments:

Change ScopePipelines Triggered
infrastructure/** onlyTerraform only
databricks/** onlyDABs only
adf/** onlyADF ARM only
database/** onlySQL migration only
Multiple pathsAll affected pipelines

Each pipeline has its own trigger.paths.include filter. The orchestrator pipeline

triggers on any path change and can skip unchanged components using conditions.

Conditional Stage Execution

yaml
stages:
  - stage: Infrastructure
    condition: |
      or(
        contains(variables['Build.SourceVersionMessage'], '[infra]'),
        eq(variables['Build.Reason'], 'Manual'),
        contains(join(',', split(variables['System.PullRequest.TargetBranch'], '/')), 'main')
      )

Environment Isolation

Resource Naming Convention

<resource-type>-<project>-<environment>
Resourcedevstagingprod
Resource Grouprg-data-platform-devrg-data-platform-stagingrg-data-platform-prod
Storage Accountstdataplatformdevstdataplatformstagingstdataplatformprod
Key Vaultkv-dataplatform-devkv-dataplatform-stagingkv-dataplatform-prod
Databricks Workspacedbw-dataplatform-devdbw-dataplatform-stagingdbw-dataplatform-prod
Data Factoryadf-dataplatform-devadf-dataplatform-stagingadf-dataplatform-prod
SQL Serversql-dataplatform-devsql-dataplatform-stagingsql-dataplatform-prod

Subscription Strategy

ModelDescriptionRecommended For
Single subscriptionAll environments in one subscription with RGsSmall teams, low cost
Multi-subscriptionSeparate subscription per environmentEnterprise, compliance
Management groupSubscriptions grouped under a management groupLarge organizations

The multi-subscription model is recommended for production data platforms because

it provides the strongest isolation boundary for RBAC, cost tracking, and quota

management.


Artifact Flow

Each pipeline stage publishes artifacts that downstream stages can consume:

Terraform Plan → Plan Artifact (.tfplan)
                      │
Terraform Apply → Output Artifact (terraform-outputs.json)
                      │
                      ├── DABs Deploy reads workspace URL from outputs
                      ├── ADF Deploy reads linked service endpoints from outputs
                      └── SQL Migration reads server FQDN from outputs

Passing Terraform Outputs to Downstream Pipelines

yaml
# In the Terraform Apply stage:
- script: |
    terraform output -json > $(Build.ArtifactStagingDirectory)/outputs.json
  displayName: "Export Outputs"

- task: PublishBuildArtifacts@1
  inputs:
    PathtoPublish: "$(Build.ArtifactStagingDirectory)/outputs.json"
    ArtifactName: "terraform-outputs"

# In a downstream stage:
- task: DownloadBuildArtifacts@1
  inputs:
    artifactName: "terraform-outputs"

- script: |
    DATABRICKS_HOST=$(jq -r '.databricks_host.value' terraform-outputs/outputs.json)
    echo "##vso[task.setvariable variable=DATABRICKS_HOST]$DATABRICKS_HOST"
  displayName: "Parse Terraform Outputs"

Rollback Strategy

Infrastructure (Terraform)

Terraform state tracks the desired state. Rollback options:

1. Revert the commit and re-run the pipeline (preferred).

2. Target-specific resource with terraform apply -target=.

3. Import previous state if state was corrupted.

Avoid terraform destroy as a rollback mechanism in production.

Databricks (DABs)

1. Redeploy previous bundle version from a tagged Git commit.

2. DABs tracks deployment state; redeploying an older commit restores previous

notebook and job definitions.

ADF (ARM Templates)

1. Incremental mode (default) only adds/updates resources; it does not delete.

2. Rollback by redeploying the previous ARM template version from Git history.

3. ADF also supports the live/publish model with adf_publish branch as a fallback.

SQL (DACPAC)

1. DACPAC is declarative — it converges schema to the desired state.

2. Rollback by deploying the previous DACPAC artifact.

3. For data-destructive changes, take a database snapshot before deployment.


Monitoring and Observability

DORA Metrics

Track these four metrics to measure CI/CD effectiveness:

MetricTarget (Elite)Measured By
Deployment FrequencyMultiple per dayPipeline run count per day
Lead Time for Changes< 1 hourCommit timestamp → prod deploy
Change Failure Rate< 5%Failed deploys / total deploys
Time to Restore Service< 1 hourFailure detection → successful fix

Use the monitoring/pipeline-dashboard.yml definition to set up dashboards that

track these metrics automatically.

Alerting

Configure alerts for:

  • Pipeline failure (immediate notification via Teams/Slack)
  • Pipeline duration exceeding 2x the 30-day average
  • Approval requests pending longer than 4 hours
  • Consecutive failures on the same pipeline (3+ in a row)

Repository Structure

Recommended repository layout for the unified data platform:

repo-root/
├── .azuredevops/
│   └── pipelines/
│       ├── orchestrator.yml
│       ├── terraform-infrastructure.yml
│       ├── databricks-dabs-deploy.yml
│       ├── adf-arm-deploy.yml
│       ├── sql-database-migration.yml
│       └── pr-validation.yml
├── templates/
│   ├── shared-steps.yml
│   └── notification-template.yml
├── infrastructure/
│   └── terraform/
│       ├── main.tf
│       ├── variables.tf
│       ├── outputs.tf
│       ├── modules/
│       │   ├── networking/
│       │   ├── storage/
│       │   ├── databricks/
│       │   ├── data-factory/
│       │   └── sql-server/
│       └── environments/
│           ├── dev.tfvars
│           ├── staging.tfvars
│           └── prod.tfvars
├── databricks/
│   ├── databricks.yml          # DABs bundle config
│   ├── resources/
│   │   ├── jobs.yml
│   │   └── dlt_pipelines.yml
│   └── src/
│       ├── notebooks/
│       └── libraries/
├── adf/
│   └── arm-templates/
│       ├── ARMTemplateForFactory.json
│       └── ARMTemplateParametersForFactory.json
├── database/
│   └── DataPlatformDB/
│       ├── DataPlatformDB.sqlproj
│       ├── Tables/
│       ├── Views/
│       ├── StoredProcedures/
│       └── PostDeployment/
├── tests/
│   ├── unit/
│   ├── integration/
│   └── smoke/
└── docs/
    └── architecture/

Implementation Checklist

Use this checklist when setting up the unified CI/CD architecture:

  • [ ] Create Azure subscriptions (or resource groups) for dev, staging, prod
  • [ ] Provision Terraform state storage account per environment
  • [ ] Create service principals with scoped RBAC per environment
  • [ ] Create service connections in Azure DevOps (see config/service-connections.md)
  • [ ] Create variable groups with Key Vault linking (see config/variable-groups.md)
  • [ ] Configure branch policies (see config/branch-policies.md)
  • [ ] Import pipeline YAML files into Azure DevOps
  • [ ] Configure environment approval gates (see config/release-gates.json)
  • [ ] Create Azure DevOps environments: dev, staging, prod (per pipeline)
  • [ ] Set up notification webhooks (Teams, Slack)
  • [ ] Import the monitoring dashboard definition
  • [ ] Run an end-to-end deployment to dev to validate the full chain
  • [ ] Promote to staging with approval gate verification
  • [ ] Document any customizations in the repository wiki

Copyright 2024-2026 Datanest Digital. All rights reserved.

Azure DevOps for Data Pipelines v1.0.0 — Free Preview