Learn how to use Terraform and the azurerm_storage_management_policy resource to automate Azure Blob Storage lifecycle rules. This guide keeps the implementation practical: move blobs to cool or archive tiers, delete old data, and target specific containers or paths without overcomplicating the Terraform code.
Terraform azurerm_storage_management_policy: Azure Blob Lifecycle Rules Made Simple
This guide shows a simple, production-friendly way to create Azure Blob Storage lifecycle management rules with Terraform. The goal is not to build a complicated module. The goal is to help you implement the resource, understand the moving parts, and avoid the common mistakes around prefix matching, blob types, and rule actions.
Quick answer
Use azurerm_storage_management_policy when you want Azure Storage to automatically move blobs between access tiers or delete old blobs based on age. It is commonly used for cost optimisation, retention, archive rules, and cleanup of logs or exported data.
What azurerm_storage_management_policy does
The Terraform azurerm_storage_management_policy resource manages a lifecycle management policy on an Azure Storage Account. In practice, that means Terraform creates the policy once, and Azure Storage evaluates the rules later as part of the storage lifecycle process.
Azure Blob lifecycle management policies can move blob data between hot, cool, cold, and archive tiers, and can also expire data at the end of its life. Microsoft documents this as a rule-based policy for automating blob tiering and deletion. The feature is supported for block blobs and append blobs in supported storage account types. Microsoft Learn: Azure Blob Storage lifecycle management overview
HashiCorp documents the Terraform resource with a rule block, optional filters, and an actions block. The actions block can include base_blob, snapshot, and version actions. Terraform Registry: azurerm_storage_management_policy
When this resource is a good fit
| Scenario | Good use of lifecycle policy | What to be careful with |
|---|---|---|
| Application logs | Move old logs to cool/archive and delete them after a retention period. | Make sure legal or audit retention requirements are understood before deleting. |
| Data exports | Clean up daily or monthly exports that are only needed temporarily. | Avoid applying the rule to the whole storage account unless that is intentional. |
| Compliance archive | Move inactive records to archive after a defined period. | Archive retrieval is slower and may involve rehydration planning. |
| General backups | Manage storage cost for backup-like blob copies. | Lifecycle policy itself is not a full backup strategy. |
Simple Terraform example
If you are new to this resource, start with a normal static rule before introducing dynamic blocks. This is easier to read, easier to debug, and usually enough for one storage account.
main.tf
terraform {
required_version = ">= 1.6.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}
provider "azurerm" {
features {}
}
data "azurerm_resource_group" "example" {
name = var.resource_group_name
}
resource "azurerm_storage_account" "example" {
name = var.storage_account_name
resource_group_name = data.azurerm_resource_group.example.name
location = data.azurerm_resource_group.example.location
account_tier = "Standard"
account_replication_type = "LRS"
account_kind = "StorageV2"
access_tier = "Hot"
min_tls_version = "TLS1_2"
allow_nested_items_to_be_public = false
}
resource "azurerm_storage_container" "documents" {
name = "documents"
storage_account_name = azurerm_storage_account.example.name
container_access_type = "private"
}
resource "azurerm_storage_management_policy" "example" {
storage_account_id = azurerm_storage_account.example.id
rule {
name = "documents-cool-archive-delete"
enabled = true
filters {
blob_types = ["blockBlob"]
prefix_match = ["documents/active/"]
}
actions {
base_blob {
tier_to_cool_after_days_since_modification_greater_than = 30
tier_to_archive_after_days_since_modification_greater_than = 180
delete_after_days_since_modification_greater_than = 2555
}
}
}
}
variables.tf
variable "resource_group_name" {
description = "Name of the existing resource group."
type = string
}
variable "storage_account_name" {
description = "Globally unique Azure Storage Account name."
type = string
}
terraform.tfvars
resource_group_name = "rg-storage-demo"
storage_account_name = "stlifecycleexample01"
The prefix_match detail people often get wrong
In Azure lifecycle policies, prefix_match is not just a random folder name. Microsoft’s policy structure documentation says each prefix must start with a container name. For example, to match blobs under sample-container/blob1/..., the prefix should look like sample-container/blob1. Microsoft Learn: lifecycle management policy structure
| What you want | Example prefix_match | Meaning |
|---|---|---|
| All blobs in one container | ["documents"] |
Targets blobs in the documents container. |
| Only blobs under a virtual folder | ["documents/active/"] |
Targets blobs whose path starts with documents/active/. |
| Multiple containers or paths | ["documents/", "logs/"] |
Targets more than one container or prefix in the same rule. |
prefix_match = ["active/"], Azure may not target what you expect unless active is actually the container name. For most real designs, use container-name/path/.Dynamic rules without overengineering
If you need several lifecycle rules, use a simple map of rules. Avoid wrapping everything inside an extra policy1 layer unless you are building a reusable enterprise module that truly needs multiple policy definitions.
variables.tf
variable "lifecycle_rules" {
description = "Lifecycle rules for the storage management policy."
type = map(object({
enabled = optional(bool, true)
blob_types = optional(list(string), ["blockBlob"])
prefix_match = optional(list(string), [])
tier_to_cool_after_days = optional(number)
tier_to_archive_after_days = optional(number)
delete_after_days = optional(number)
}))
default = {}
}
terraform.tfvars
lifecycle_rules = {
active_documents = {
prefix_match = ["documents/active/"]
tier_to_cool_after_days = 30
tier_to_archive_after_days = 180
delete_after_days = 2555
}
old_exports = {
prefix_match = ["exports/"]
delete_after_days = 90
}
}
main.tf
resource "azurerm_storage_management_policy" "example" {
storage_account_id = azurerm_storage_account.example.id
dynamic "rule" {
for_each = var.lifecycle_rules
content {
name = rule.key
enabled = rule.value.enabled
filters {
blob_types = rule.value.blob_types
prefix_match = rule.value.prefix_match
}
actions {
base_blob {
tier_to_cool_after_days_since_modification_greater_than = rule.value.tier_to_cool_after_days
tier_to_archive_after_days_since_modification_greater_than = rule.value.tier_to_archive_after_days
delete_after_days_since_modification_greater_than = rule.value.delete_after_days
}
}
}
}
}
This version is still flexible, but it remains readable. Each rule has a name, a target, and one or more actions. That is usually what people implementing this Terraform resource need.
Deploy and verify
After saving the Terraform files, run the normal Terraform workflow.
terraform fmt
terraform init
terraform validate
terraform plan
terraform apply
What to check in the plan
- The storage account ID is correct.
- Each lifecycle rule has the expected name.
blob_typesis set to the correct blob type, usuallyblockBlob.prefix_matchstarts with the container name.- The tiering and delete day values match your retention requirements.
What to check in Azure
- Open the Storage Account in the Azure portal.
- Go to Data management > Lifecycle management.
- Confirm the rules are visible and enabled.
- Confirm the filters target the expected containers or paths.
Common mistakes
| Mistake | Why it matters | Better approach |
|---|---|---|
| Using a prefix without the container name | The rule may not target the blobs you expect. | Use container-name/path/, for example documents/active/. |
| Applying the rule to the whole account accidentally | You may tier or delete more data than intended. | Use prefix_match for the first version, then broaden later if needed. |
| Treating lifecycle management as backup | Lifecycle rules manage cost and retention; they do not replace recovery design. | Use backup, soft delete, versioning, replication, or immutability where needed. |
| Overbuilding the Terraform module | A complicated variable structure makes the resource harder to understand and maintain. | Start with one static rule, then move to a simple map only when needed. |
FAQ
Does azurerm_storage_management_policy create backups?
No. It creates lifecycle management rules. Those rules can move or delete blobs based on conditions, but they are not a backup service. For backup and recovery, look at Azure Backup, blob soft delete, versioning, snapshots, replication, or immutable storage depending on the requirement.
Can one storage account have multiple lifecycle rules?
Yes. A storage management policy can contain multiple rules. In Terraform, you can write multiple rule blocks manually or use a dynamic rule block with for_each.
Should I use dynamic blocks?
Use dynamic blocks when the rules are genuinely repeated across environments or storage accounts. If you only have one or two rules, static Terraform is often easier to read and safer for the next person maintaining it.
What blob type should I use?
Most common Azure Blob Storage lifecycle examples use blockBlob. Microsoft’s lifecycle management overview says lifecycle policies are supported for block blobs and append blobs in supported storage account types. Check your workload before assuming the blob type.
How do I target a specific container?
Use prefix_match and start the prefix with the container name. For example, prefix_match = ["documents"] targets the documents container, while prefix_match = ["documents/active/"] targets a path inside that container.
Useful links
- Terraform Registry: azurerm_storage_management_policy
- Microsoft Learn: Azure Blob Storage lifecycle management overview
- Microsoft Learn: lifecycle management policy structure
- Microsoft Learn: configure a lifecycle management policy
- Microsoft Learn: access tiers for blob data
- Terraform documentation: dynamic blocks
- Related article: Terraform lists vs maps
