Implement Infracost in your Azure DevOps build pipeline

Learn how you can enhance your CI/CD with useful cost information


5 min read

Did you ever wonder how you can see the costs of your infrastructure right in your pipeline or maybe even before you deploy? By saying this I do not only mean that you know what you deploy and that you calculated everything before. I mean what if you have a mechanism as a developer to make sure that the infrastructure you coded really generates the costs you calculated. The solution is: infracost It shows you your cloud costs in your CI/CD pipeline.

What I want to show you in this article is how you can create a basic setup for your pipeline in Azure DevOps. There are a lot of other ways to use this tool but lets start with a basic one:


You need to have an Azure DevOps organisation and a project inside. Further you must have the proper rights to enable an extension for the organisation (or at least you have to know the administrator ๐Ÿ˜‰)

Step 1 - Activate the Azure DevOps extension

Navigate to and activate the Infracost Tasks extension.

Screenshot 2022-09-07 at 9.51.58 PM.png

Step 2 - Create an API Key

If you are running macOS you can use brew to install the CLI Tool. First run brew install infracost and then run infracost --version. It should show version 0.10.11.

Now run infracost auth login. Your browser should open up and you should see a screen like this.

Screenshot 2022-09-07 at 10.34.22 PM.png

Now it is time to set up an account. You can either use your email address but also your existing GitHub Account. It is up to you. Once your account is set up go back to your terminal and run infracost configure get api_key and you should finally get your API Key. Store the key in a variable group in your Azure DevOps project and name it infracost. It should look like this.

Screenshot 2022-09-07 at 10.21.14 PM.png

Step 3 - Code base

Set up your code infrastructure that you have at least one resource which will be deployed. For example a linux virtual machine. Your code base could look like this:

terraform {
  required_version = ">=0.12"

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>2.0"
    random = {
      source  = "hashicorp/random"
      version = "~>3.0"
    tls = {
      source = "hashicorp/tls"
      version = "~>4.0"

provider "azurerm" {
  features {}

resource "random_pet" "rg_name" {
  prefix = var.resource_group_name_prefix

resource "azurerm_resource_group" "rg" {
  location = var.resource_group_location
  name     =

# Create virtual network
resource "azurerm_virtual_network" "my_terraform_network" {
  name                = "myVnet"
  address_space       = [""]
  location            = azurerm_resource_group.rg.location
  resource_group_name =

# Create subnet
resource "azurerm_subnet" "my_terraform_subnet" {
  name                 = "mySubnet"
  resource_group_name  =
  virtual_network_name =
  address_prefixes     = [""]

# Create public IPs
resource "azurerm_public_ip" "my_terraform_public_ip" {
  name                = "myPublicIP"
  location            = azurerm_resource_group.rg.location
  resource_group_name =
  allocation_method   = "Dynamic"

# Create Network Security Group and rule
resource "azurerm_network_security_group" "my_terraform_nsg" {
  name                = "myNetworkSecurityGroup"
  location            = azurerm_resource_group.rg.location
  resource_group_name =

  security_rule {
    name                       = "SSH"
    priority                   = 1001
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "*"
    destination_address_prefix = "*"

# Create network interface
resource "azurerm_network_interface" "my_terraform_nic" {
  name                = "myNIC"
  location            = azurerm_resource_group.rg.location
  resource_group_name =

  ip_configuration {
    name                          = "my_nic_configuration"
    subnet_id                     =
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          =

# Connect the security group to the network interface
resource "azurerm_network_interface_security_group_association" "example" {
  network_interface_id      =
  network_security_group_id =

# Generate random text for a unique storage account name
resource "random_id" "random_id" {
  keepers = {
    # Generate a new ID only when a new resource group is defined
    resource_group =

  byte_length = 8

# Create storage account for boot diagnostics
resource "azurerm_storage_account" "my_storage_account" {
  name                     = "diag${random_id.random_id.hex}"
  location                 = azurerm_resource_group.rg.location
  resource_group_name      =
  account_tier             = "Standard"
  account_replication_type = "LRS"

# Create (and display) an SSH key
resource "tls_private_key" "example_ssh" {
  algorithm = "RSA"
  rsa_bits  = 4096

# Create virtual machine
resource "azurerm_linux_virtual_machine" "my_terraform_vm" {
  name                  = "myVM"
  location              = azurerm_resource_group.rg.location
  resource_group_name   =
  network_interface_ids = []
  size                  = "Standard_DS1_v2"

  os_disk {
    name                 = "myOsDisk"
    caching              = "ReadWrite"
    storage_account_type = "Premium_LRS"

  source_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "18.04-LTS"
    version   = "latest"

  computer_name                   = "myvm"
  admin_username                  = "azureuser"
  disable_password_authentication = true

  admin_ssh_key {
    username   = "azureuser"
    public_key = tls_private_key.example_ssh.public_key_openssh

  boot_diagnostics {
    storage_account_uri = azurerm_storage_account.my_storage_account.primary_blob_endpoint

variable "resource_group_location" {
  default     = "eastus"
  description = "Location of the resource group."

variable "resource_group_name_prefix" {
  default     = "rg"
  description = "Prefix of the resource group name that's combined with a random ID so name is unique in your Azure subscription."

output "resource_group_name" {
  value =

output "public_ip_address" {
  value = azurerm_linux_virtual_machine.my_terraform_vm.public_ip_address

output "tls_private_key" {
  value     = tls_private_key.example_ssh.private_key_pem
  sensitive = true

Step 4 - Build pipeline

Now you can create your basic build pipeline based on yaml.


trigger: none

  vmImage: ubuntu-latest

- group: tfVars

  - stage: Infracost
    - job: Analysis
      - task: InfracostSetup@1
        displayName: 'Infracost : Install'
          apiKey: '$(infracost)'
          version: '0.10.x'
          currency: 'EUR'

      - task: Bash@3
        displayName: 'Infracost : Analysis'
          targetType: 'inline'
          script: |
            infracost breakdown --path .

You will have two tasks. The first task is to set up the connection and the second one is to execute the actual cost analysis.

Step 5 - Run the pipeline

Now if you run your pipeline you will see an output like this, isn't that great?! Screenshot 2022-09-07 at 11.18.48 PM.png

Extra Hint

If you have an EA with Microsoft for example and you get a special discount based on your contract, you can simply set the discount in your infracost account. For that navigate to "Org Settings" and then go to "Custom price books".

Screenshot 2022-09-07 at 11.23.03 PM.png

The costs will change a bit if you rerun the pipeline.

Screenshot 2022-09-07 at 11.32.19 PM.png

Thanks for reading! I hope you liked it.