Managing Azure DevOps with Terraform

Learn how to provision Azure DevOps projects, repositories, pipelines, and service connections using the Terraform azuredevops provider

Managing Azure DevOps with Terraform

Azure DevOps CI/CD setup — projects, repos, pipelines, service connections, branch policies — is usually clicked together manually and then forgotten. The azuredevops Terraform provider lets you define that setup as code instead.

Prerequisites

  • An Azure DevOps organization
  • A Personal Access Token (PAT) with permissions to manage projects and pipelines
  • Terraform installed (version 1.0.0 or later)

Provider Configuration

terraform {
  required_providers {
    azuredevops = {
      source  = "microsoft/azuredevops"
      version = "~> 1.0"
    }
  }
}

provider "azuredevops" {
  org_service_url       = "https://dev.azure.com/${var.organization_name}"
  personal_access_token = var.azdo_pat
}

Store azdo_pat in a secrets manager or CI secret store — never commit it to the repository.

Project and Repository

resource "azuredevops_project" "main" {
  name       = var.project_name
  description = "Managed by Terraform"
  visibility  = "private"
  version_control   = "Git"
  work_item_template = "Agile"
}

resource "azuredevops_git_repository" "main" {
  project_id = azuredevops_project.main.id
  name       = "${var.project_name}-repo"
  initialization {
    init_type = "Clean"
  }
}

Service Connection to Azure

resource "azuredevops_serviceendpoint_azurerm" "main" {
  project_id            = azuredevops_project.main.id
  service_endpoint_name = "azure-connection"

  credentials {
    serviceprincipalid  = var.service_principal_id
    serviceprincipalkey = var.service_principal_key
  }

  azurerm_spn_tenantid      = var.tenant_id
  azurerm_subscription_id   = var.subscription_id
  azurerm_subscription_name = var.subscription_name
}

Build Pipeline

resource "azuredevops_build_definition" "main" {
  project_id = azuredevops_project.main.id
  name       = "${var.project_name}-ci"

  repository {
    repo_type   = "TfsGit"
    repo_id     = azuredevops_git_repository.main.id
    branch_name = azuredevops_git_repository.main.default_branch
    yml_path    = "azure-pipelines.yml"
  }

  ci_trigger {
    use_yaml = true
  }
}

Branch Policy

resource "azuredevops_branch_policy_min_reviewers" "main" {
  project_id = azuredevops_project.main.id

  settings {
    reviewer_count     = 2
    submitter_can_vote = false

    scope {
      repository_id  = azuredevops_git_repository.main.id
      repository_ref = azuredevops_git_repository.main.default_branch
      match_type     = "Exact"
    }
  }
}

Best Practices

  1. Secrets — keep the PAT and service principal credentials out of state files where possible; use a remote backend with encryption and restricted access.
  2. Least privilege — scope the PAT to only the permissions Terraform needs (project and pipeline management), not full organization admin.
  3. Branch protection — pair azuredevops_build_definition with a branch policy so CI is required before merge, not optional.
  4. State drift — Azure DevOps allows manual pipeline edits in the UI; if teams edit pipelines outside Terraform, terraform plan will show constant drift. Decide on one source of truth.

Conclusion

Provisioning Azure DevOps projects and pipelines with Terraform makes onboarding a new repository (project, repo, service connection, CI trigger, branch policy) a single apply instead of a checklist of manual portal steps.

Additional Resources