Tuesday, 24 June 2025

Setting Up Azure Storage Blob Backups with Azure Backup Vault Using Terraform

 To create automated daily backups of Azure storage account blobs we can use Azure Backup Vault resource. It is possible to configure operational backups and vaulted backups for Azure storage blobs with the backup vault backup policies and instances. We need to consider the limitations specified for operational backups and the limitations in vaulted backups, specially the limitation of only 100 blob containers are allowed to be backed up for a given Azure storage account. Another issue with vaulted backups is the newly created containers in an Azure storage will not automatically included in the backups. Therefore, the Azure vaulted backups are working well only when you have predefoined set of blob containers in your implementation, that are configured for vaulted backups. 

The expectation is to autmatically backup daily or in given interval (daily is the minimum interval possible) as shown below.



Let's explore step by step full configuration with terraform, to setup backup blobs in hot and cool storages.

Here is the terrafrom for setting up two storage accounts and its predefined blob containers.

resource "azurerm_resource_group" "instance_rg" {
  name     = "ch-blobbackup-dev-weu-001-rg"
  location = "westeurope"
}

resource "azurerm_resource_group" "shared_rg" {
  name     = "ch-blobbackup-dev-weu-shared-rg"
  location = "westeurope"
}

resource "azurerm_storage_account" "instancestoragecool" {
  name                             = "chdemodevweu001cool"
  location                         = azurerm_resource_group.instance_rg.location
  resource_group_name              = azurerm_resource_group.instance_rg.name
  account_tier                     = "Standard"
  account_replication_type         = "RAGRS"
  account_kind                     = "StorageV2"
  access_tier                      = "Cool"
  allow_nested_items_to_be_public  = false
  min_tls_version                  = "TLS1_2"
  cross_tenant_replication_enabled = false

  blob_properties {
    versioning_enabled  = true
    change_feed_enabled = true

    delete_retention_policy {
      days                     = 12
      permanent_delete_enabled = false
    }

    container_delete_retention_policy {
      days = 30
    }

    restore_policy {
      days = 7
    }
  }

  lifecycle {
    prevent_destroy = true
  }
}
# This will cleanup blob versions after 7 days of creation # and keeps only latest version. If versioning is managed by code remove this policy
resource "azurerm_storage_management_policy" "cool_storage_version_cleanup" {
  storage_account_id = azurerm_storage_account.instancestoragecool.id

  rule {
    name    = "DeletePreviousVersions (auto-created)"
    enabled = true

    filters {
      blob_types = ["blockBlob", "appendBlob"]
      # optional: limit to specific prefixes
      # prefix_match = ["myprefix/"]
    }

    actions {
      version {
        delete_after_days_since_creation = 7
      }
    }
  }
}

resource "azurerm_storage_account" "instancestoragehot" {
  name                             = "chdemodevweu001hot"
  location                         = azurerm_resource_group.instance_rg.location
  resource_group_name              = azurerm_resource_group.instance_rg.name
  account_tier                     = "Standard"
  account_replication_type         = "RAGRS"
  account_kind                     = "StorageV2"
  access_tier                      = "Hot"
  allow_nested_items_to_be_public  = false
  min_tls_version                  = "TLS1_2"
  cross_tenant_replication_enabled = false

  blob_properties {
    versioning_enabled  = true
    change_feed_enabled = true

    delete_retention_policy {
      days                     = 12
      permanent_delete_enabled = false
    }

    container_delete_retention_policy {
      days = 7
    }

    restore_policy {
      days = 7
    }
  }

  lifecycle {
    prevent_destroy = true
  }
}

resource "azurerm_storage_container" "cool_storage_images" {
  name                  = "images"
  storage_account_id    = azurerm_storage_account.instancestoragecool.id
  container_access_type = "private"

  lifecycle {
    prevent_destroy = true
  }
}

resource "azurerm_storage_container" "cool_storage_videos" {
  name                  = "videos"
  storage_account_id    = azurerm_storage_account.instancestoragecool.id
  container_access_type = "private"

  lifecycle {
    prevent_destroy = true
  }
}

resource "azurerm_storage_container" "hot_storage_images" {
  name                  = "images"
  storage_account_id    = azurerm_storage_account.instancestoragehot.id
  container_access_type = "private"

  lifecycle {
    prevent_destroy = true
  }
}

resource "azurerm_storage_container" "hot_storage_videos" {
  name                  = "videos"
  storage_account_id    = azurerm_storage_account.instancestoragehot.id
  container_access_type = "private"

  lifecycle {
    prevent_destroy = true
  }
}

With above terraform code we are creating to storage accounts and two blob containers in each storage. then the generic storage data protection policies are also setup here.

Next we can setup a backup vault using below terraform code. Ideally you should enable soft delete by setting soft_delete     = "On"   

resource "azurerm_data_protection_backup_vault" "backup_vault" {
  name                = "ch-blobbackup-dev-weu-bv"
  location            = azurerm_resource_group.shared_rg.location
  resource_group_name = azurerm_resource_group.shared_rg.name
  datastore_type      = "VaultStore"
  redundancy          = "GeoRedundant"
  soft_delete         = "Off" # Set to Off to delete the backup instances via TF

  identity {
    type = "SystemAssigned"
  }
}


As the next step we need to add role "" to backup vault system managed identity in the scope of both storage accounts.

resource "azurerm_role_assignment" "cool_storage_backup_role" {
  principal_id         = azurerm_data_protection_backup_vault.backup_vault.identity[0].principal_id
  role_definition_name = "Storage Account Backup Contributor"
  scope                = azurerm_storage_account.instancestoragecool.id
}

resource "azurerm_role_assignment" "hot_storage_backup_role" {
  principal_id         = azurerm_data_protection_backup_vault.backup_vault.identity[0].principal_id
  role_definition_name = "Storage Account Backup Contributor"
  scope                = azurerm_storage_account.instancestoragehot.id
}


Then we can setup backup policies in the backup vault. 

# Backup Policy for Blob Storage
resource "azurerm_data_protection_backup_policy_blob_storage" "cool_storage_backup_policy" {
  name     = "${azurerm_storage_account.instancestoragecool.name}-blob-policy"
  vault_id = azurerm_data_protection_backup_vault.backup_vault.id

  operational_default_retention_duration = "P7D"
  vault_default_retention_duration       = "P30D"
  time_zone                              = "W. Europe Standard Time"
  backup_repeating_time_intervals        = ["R/2025-06-23T19:00:00/P1D"] # take backup every day

  depends_on = [azurerm_role_assignment.cool_storage_backup_role]
}

resource "azurerm_data_protection_backup_policy_blob_storage" "hot_storage_backup_policy" {
  name     = "${azurerm_storage_account.instancestoragehot.name}-blob-policy"
  vault_id = azurerm_data_protection_backup_vault.backup_vault.id

  operational_default_retention_duration = "P7D" # ISO 8601 Duration: 7 days - operational backup retention days
  vault_default_retention_duration       = "P7D"
  time_zone                              = "W. Europe Standard Time"
  backup_repeating_time_intervals        = ["R/2025-06-23T19:00:00/P1D"] # take backup every day

  depends_on = [azurerm_role_assignment.hot_storage_backup_role]
}

We can see the backup policy is getting created as shown below. Even though we have set the time zones correctly here (even via Azure portal UI the applied time zone values get created the same way). The backup time always work in UTC. therefore it is not effectve as of now setting a time zone. So the back up will be taken at 19:00 UTC which is 21:00 in west europe (in June). The date specifeid does not matter but the time only matters. You can set it to any previous date in setting up the backup_repeating_time_intervals.

Then we have to define a backup instance in the backup vault to link the backup policy with the Azure storage account. As you can see below it is a must to set the storage_account_container_names list specifyin all blob containers to backup. Any newly added blob container need to be added to instace and reconfigured to ensure they get backed up, and maximum blob containers we can backup for a storage account is 100. 

# Backup Instance to protect Blob Storage
resource "azurerm_data_protection_backup_instance_blob_storage" "cool_storage_backup_instance" {
  name                            = "${azurerm_storage_account.instancestoragecool.name}-backup-instance"
  vault_id                        = azurerm_data_protection_backup_vault.backup_vault.id
  location                        = azurerm_storage_account.instancestoragecool.location
  storage_account_id              = azurerm_storage_account.instancestoragecool.id
  backup_policy_id                = azurerm_data_protection_backup_policy_blob_storage.cool_storage_backup_policy.id
  storage_account_container_names = ["images", "videos"]

  depends_on = [
    azurerm_role_assignment.cool_storage_backup_role,
    azurerm_data_protection_backup_policy_blob_storage.cool_storage_backup_policy,
    azurerm_storage_container.cool_storage_images,
    azurerm_storage_container.cool_storage_videos
  ]
}

resource "azurerm_data_protection_backup_instance_blob_storage" "hot_storage_backup_instance" {
  name                            = "${azurerm_storage_account.instancestoragehot.name}-backup-instance"
  vault_id                        = azurerm_data_protection_backup_vault.backup_vault.id
  location                        = azurerm_storage_account.instancestoragehot.location
  storage_account_id              = azurerm_storage_account.instancestoragehot.id
  backup_policy_id                = azurerm_data_protection_backup_policy_blob_storage.hot_storage_backup_policy.id
  storage_account_container_names = ["images", "videos"]

  depends_on = [
    azurerm_role_assignment.hot_storage_backup_role,
    azurerm_data_protection_backup_policy_blob_storage.hot_storage_backup_policy,
    azurerm_storage_container.hot_storage_images,
    azurerm_storage_container.hot_storage_videos
  ]
}

Here is the configured backup instances.

If you inspect the storage account data protection tab you can see the backup is configured for the storage.


With the backup instaces configured above the backup are taken as per schedule as shown below.

No comments:

Post a Comment