← Back to ArticlesStorage

Blob Storage — Lifecycle Management and Tier Transition Automation

Implementing lifecycle policies to automatically move blobs between tiers, archive old data, and optimize storage costs.

Blob Storage — Lifecycle Management and Tier Transition Automation

Why Lifecycle Management Matters

Azure Blob Storage lifecycle management automates the transition of data between access tiers and handles deletion based on configurable rules. Without lifecycle policies, organizations face three critical challenges:

Cost Optimization

Storage costs vary dramatically across tiers. A 10 TB dataset stored entirely in Hot tier costs approximately $200/month, while the same data in Archive costs under $20/month. Most organizations have data that follows a predictable access pattern — frequently accessed when new, rarely touched after weeks, and almost never read after months. Lifecycle policies align storage costs with actual usage patterns automatically.

Compliance and Retention

Regulatory frameworks (GDPR, HIPAA, SOX, SEC 17a-4) mandate specific retention periods. Lifecycle policies enforce these requirements programmatically — ensuring data is retained for the required duration and deleted precisely when the retention window expires. This eliminates human error from compliance workflows.

Data Governance

Without automation, storage accounts accumulate stale data indefinitely. Lifecycle policies enforce organizational data governance standards, ensuring that temporary files, diagnostic logs, and intermediate processing artifacts don't persist beyond their useful life.


Architecture: Tier Transition Flow

┌─────────────────────────────────────────────────────────────────┐
│                  BLOB LIFECYCLE TRANSITIONS                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌────────┐  30 days  ┌────────┐  60 days  ┌────────┐           │
│  │  HOT   │──────────▶│  COOL  │──────────▶│  COLD  │           │
│  │$0.018/ │           │ $0.01/ │           │$0.0036/│           │
│  │GB/month│           │GB/month│           │GB/month│           │
│  └────────┘           └────────┘           └────────┘           │
│       │                    │                    │               │
│       │      90 days       │     180 days       │               │
│       ▼                    ▼                    ▼               │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │              ARCHIVE — $0.00099/GB/month                │    │
│  │  Rehydration: Standard (≤15h) | High Priority (<1h)     │    │
│  └─────────────────────────────────────────────────────────┘    │
│       │                                                         │
│       │  365 days                                               │
│       ▼                                                         │
│  ┌──────────┐                                                   │
│  │  DELETE   │  Permanent removal after retention period        │
│  └──────────┘                                                   │
│                                                                 │
│  Policy engine runs once/day (24-48h execution window)          │
└─────────────────────────────────────────────────────────────────┘

Understanding Storage Tiers

Tier Comparison

AttributeHotCoolColdArchive
Storage cost (per GB/month, LRS, East US)$0.018$0.01$0.0036$0.00099
Read access cost (per 10K ops)$0.004$0.01$0.10$5.00
Write access cost (per 10K ops)$0.005$0.10$0.18$0.10
Data retrieval (per GB)Free$0.01$0.03$0.02
Minimum storage durationNone30 days90 days180 days
Access latencyMillisecondsMillisecondsMillisecondsHours
Early deletion penaltyNoneYesYesYes

When to Use Each Tier

Cost Savings Example

For 50 TB of log data with decreasing access frequency:

StrategyMonthly Cost
All Hot$900
Lifecycle (Hot→Cool→Archive→Delete)~$180
Savings~80%

Step-by-Step Implementation

Creating Lifecycle Policies via Azure Portal

  1. Navigate to your Storage Account in the Azure Portal
  2. Under Data management, select Lifecycle management
  3. Click Add a rule
  4. Configure:
    • Rule name (alphanumeric, no spaces)
    • Rule scope: Limit blobs with filters, or apply to all blobs
    • Blob type: Block blobs, Append blobs
  5. Set conditions and actions for base blobs, snapshots, and versions
  6. Review and create

Creating Lifecycle Policies via Azure CLI

# Define the policy in a JSON file
cat > lifecycle-policy.json << 'EOF'
{
  "rules": [
    {
      "name": "logs-tiering-rule",
      "enabled": true,
      "type": "Lifecycle",
      "definition": {
        "filters": {
          "blobTypes": ["blockBlob"],
          "prefixMatch": ["logs/", "diagnostics/"]
        },
        "actions": {
          "baseBlob": {
            "tierToCool": {
              "daysAfterModificationGreaterThan": 30
            },
            "tierToCold": {
              "daysAfterModificationGreaterThan": 90
            },
            "tierToArchive": {
              "daysAfterModificationGreaterThan": 180
            },
            "delete": {
              "daysAfterModificationGreaterThan": 365
            }
          },
          "snapshot": {
            "delete": {
              "daysAfterCreationGreaterThan": 90
            }
          }
        }
      }
    }
  ]
}
EOF

# Apply the policy
az storage account management-policy create \
  --account-name mystorageaccount \
  --resource-group myResourceGroup \
  --policy @lifecycle-policy.json

Creating Lifecycle Policies via Bicep

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' existing = {
  name: 'mystorageaccount'
}

resource lifecyclePolicy 'Microsoft.Storage/storageAccounts/managementPolicies@2023-01-01' = {
  parent: storageAccount
  name: 'default'
  properties: {
    policy: {
      rules: [
        {
          name: 'logs-tiering-rule'
          enabled: true
          type: 'Lifecycle'
          definition: {
            filters: {
              blobTypes: ['blockBlob']
              prefixMatch: ['logs/', 'diagnostics/']
            }
            actions: {
              baseBlob: {
                tierToCool: { daysAfterModificationGreaterThan: 30 }
                tierToCold: { daysAfterModificationGreaterThan: 90 }
                tierToArchive: { daysAfterModificationGreaterThan: 180 }
                delete: { daysAfterModificationGreaterThan: 365 }
              }
              snapshot: {
                delete: { daysAfterCreationGreaterThan: 90 }
              }
            }
          }
        }
      ]
    }
  }
}

ARM Template

The equivalent ARM template uses the same structure. Deploy with:

az deployment group create \
  --resource-group myResourceGroup \
  --template-file lifecycle-policy.json \
  --parameters storageAccountName=mystorageaccount

The resource type is Microsoft.Storage/storageAccounts/managementPolicies with API version 2023-01-01. The policy schema matches the JSON structure shown in the CLI example above — wrap it in the standard ARM template envelope with $schema, contentVersion, and resources array.

Rule Filters

Filters determine which blobs a rule applies to. You can combine multiple filter types:

Prefix Match

Target blobs in specific containers or virtual directories:

"filters": {
  "blobTypes": ["blockBlob"],
  "prefixMatch": ["container1/logs/", "container2/archive/", "backups/daily/"]
}

Blob Types

Supported blob types for lifecycle rules:

Blob Index Tags

Filter blobs using key-value tag pairs for fine-grained control:

"filters": {
  "blobTypes": ["blockBlob"],
  "prefixMatch": ["projects/"],
  "blobIndexMatch": [
    {
      "name": "department",
      "op": "==",
      "value": "finance"
    },
    {
      "name": "classification",
      "op": "==",
      "value": "internal"
    }
  ]
}

Actions

Available actions for base blobs:

ActionDescriptionSupported Blob Types
tierToCoolMove to Cool tierBlock blobs
tierToColdMove to Cold tierBlock blobs
tierToArchiveMove to Archive tierBlock blobs
deleteDelete the blobBlock blobs, Append blobs
enableAutoTierToHotFromCoolAuto-tier back to Hot on accessBlock blobs

Actions for snapshots and versions:

ActionDescription
tierToCoolMove snapshot/version to Cool
tierToColdMove snapshot/version to Cold
tierToArchiveMove snapshot/version to Archive
deleteDelete the snapshot/version

Conditions

Conditions define when an action triggers based on blob age:

ConditionDescriptionUse Case
daysAfterModificationGreaterThanDays since last modifiedGeneral-purpose aging
daysAfterCreationGreaterThanDays since blob creationImmutable data, snapshots
daysAfterLastAccessTimeGreaterThanDays since last read/writeAccess-pattern-based tiering
daysAfterLastTierChangeGreaterThanDays since last tier changePrevent rapid tier oscillation

Important: daysAfterLastAccessTimeGreaterThan requires enabling last access time tracking:

# Enable last access time tracking
az storage account blob-service-properties update \
  --account-name mystorageaccount \
  --resource-group myResourceGroup \
  --enable-last-access-tracking true

Example using access-time-based tiering:

{
  "name": "access-based-tiering",
  "enabled": true,
  "type": "Lifecycle",
  "definition": {
    "filters": {
      "blobTypes": ["blockBlob"],
      "prefixMatch": ["media/"]
    },
    "actions": {
      "baseBlob": {
        "tierToCool": {
          "daysAfterLastAccessTimeGreaterThan": 30
        },
        "tierToArchive": {
          "daysAfterLastAccessTimeGreaterThan": 90
        },
        "enableAutoTierToHotFromCool": {}
      }
    }
  }
}

Advanced Scenarios

Version-Level Policies

When blob versioning is enabled, lifecycle policies can manage previous versions independently:

{
  "name": "manage-versions",
  "enabled": true,
  "type": "Lifecycle",
  "definition": {
    "filters": { "blobTypes": ["blockBlob"] },
    "actions": {
      "baseBlob": {
        "tierToCool": { "daysAfterModificationGreaterThan": 30 }
      },
      "version": {
        "tierToCool": { "daysAfterCreationGreaterThan": 7 },
        "tierToArchive": { "daysAfterCreationGreaterThan": 60 },
        "delete": { "daysAfterCreationGreaterThan": 180 }
      }
    }
  }
}

Snapshot Management

Automatically clean up old snapshots:

{
  "name": "snapshot-cleanup",
  "enabled": true,
  "type": "Lifecycle",
  "definition": {
    "filters": { "blobTypes": ["blockBlob"], "prefixMatch": ["databases/"] },
    "actions": {
      "snapshot": {
        "tierToCool": { "daysAfterCreationGreaterThan": 30 },
        "tierToArchive": { "daysAfterCreationGreaterThan": 90 },
        "delete": { "daysAfterCreationGreaterThan": 365 }
      }
    }
  }
}

Policies for Append Blobs

Append blobs (used for logging) only support the delete action:

{
  "name": "delete-old-append-logs",
  "enabled": true,
  "type": "Lifecycle",
  "definition": {
    "filters": {
      "blobTypes": ["appendBlob"],
      "prefixMatch": ["applogs/"]
    },
    "actions": {
      "baseBlob": {
        "delete": {
          "daysAfterModificationGreaterThan": 90
        }
      }
    }
  }
}

Tag-Based Lifecycle Rules

Use blob index tags for business-logic-driven lifecycle management:

# Set tags on blobs during upload
az storage blob upload \
  --account-name mystorageaccount \
  --container-name documents \
  --name "report-2024-q1.pdf" \
  --file ./report.pdf \
  --tags "retention=7years" "department=legal"

Policy targeting tagged blobs:

{
  "name": "legal-retention-policy",
  "enabled": true,
  "type": "Lifecycle",
  "definition": {
    "filters": {
      "blobTypes": ["blockBlob"],
      "blobIndexMatch": [
        { "name": "retention", "op": "==", "value": "7years" }
      ]
    },
    "actions": {
      "baseBlob": {
        "tierToCool": { "daysAfterModificationGreaterThan": 90 },
        "tierToArchive": { "daysAfterModificationGreaterThan": 365 },
        "delete": { "daysAfterModificationGreaterThan": 2555 }
      }
    }
  }
}

Rehydration from Archive Tier

Blobs in Archive tier are offline and cannot be read directly. You must rehydrate them to an online tier before access.

Standard vs High Priority

AttributeStandardHigh Priority
Rehydration timeUp to 15 hoursUnder 1 hour (for blobs < 10 GB)
Cost (per GB)Same as read cost for target tierHigher rate
Use casePlanned retrieval, batch processingUrgent business needs

Rehydrating via CLI

# Rehydrate to Hot tier with standard priority
az storage blob set-tier \
  --account-name mystorageaccount \
  --container-name archive-data \
  --name "important-report.pdf" \
  --tier Hot \
  --rehydrate-priority Standard

# Rehydrate with high priority
az storage blob set-tier \
  --account-name mystorageaccount \
  --container-name archive-data \
  --name "urgent-file.pdf" \
  --tier Hot \
  --rehydrate-priority High

Rehydrating via C# .NET SDK

using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;

var blobClient = new BlobClient(connectionString, "archive-data", "important-report.pdf");
await blobClient.SetAccessTierAsync(AccessTier.Hot, rehydratePriority: RehydratePriority.Standard);

// Check status — returns "rehydrate-pending-to-hot" while in progress
var properties = await blobClient.GetPropertiesAsync();
Console.WriteLine($"Archive status: {properties.Value.ArchiveStatus}");

Copy-Based Rehydration Pattern

Instead of rehydrating in-place, copy the archived blob to a new blob in an online tier. This preserves the archived copy and avoids early deletion fees:

var sourceBlob = new BlobClient(connectionString, "archive-container", "archived-blob.dat");
var destBlob = new BlobClient(connectionString, "hot-container", "rehydrated-blob.dat");

var copyOperation = await destBlob.StartCopyFromUriAsync(
    sourceBlob.Uri,
    new BlobCopyFromUriOptions { AccessTier = AccessTier.Hot, RehydratePriority = RehydratePriority.High });

await copyOperation.WaitForCompletionAsync();

Integration with Other Services

Event Grid Notifications on Tier Changes

React to tier transitions in real-time using Azure Event Grid:

az eventgrid event-subscription create \
  --name tier-change-notifications \
  --source-resource-id "/subscriptions/{sub-id}/resourceGroups/myRG/providers/Microsoft.Storage/storageAccounts/mystorageaccount" \
  --endpoint "https://myfunctionapp.azurewebsites.net/api/TierChangeHandler" \
  --included-event-types "Microsoft.Storage.BlobTierChanged"

The event payload includes previousTier and currentTier in the data object, allowing downstream systems to react to specific transitions (e.g., notify when blobs move to Archive).

Azure Functions for Custom Lifecycle Logic

Implement complex lifecycle decisions beyond rule-based policies:

using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Microsoft.Azure.Functions.Worker;

public class CustomLifecycleFunction
{
    [Function("EvaluateLifecycle")]
    public async Task Run([TimerTrigger("0 0 2 * * *")] TimerInfo timer, FunctionContext context)
    {
        var logger = context.GetLogger<CustomLifecycleFunction>();
        var client = new BlobServiceClient(Environment.GetEnvironmentVariable("StorageConnection"));
        var container = client.GetBlobContainerClient("projects");

        await foreach (var blob in container.GetBlobsAsync(traits: BlobTraits.Metadata))
        {
            if (blob.Metadata.TryGetValue("projectStatus", out var status) && status == "completed")
            {
                var age = (DateTimeOffset.UtcNow - blob.Properties.LastModified)?.Days ?? 0;
                if (age > 60 && blob.Properties.AccessTier == AccessTier.Hot)
                {
                    await container.GetBlobClient(blob.Name).SetAccessTierAsync(AccessTier.Cool);
                    logger.LogInformation("Moved {Blob} to Cool tier", blob.Name);
                }
            }
        }
    }
}

Logic Apps for Approval Workflows Before Deletion

Create a workflow that requires human approval before deleting high-value blobs. The pattern:

  1. Trigger: Daily recurrence schedule
  2. List blobs: Query blobs approaching their deletion threshold (e.g., within 7 days)
  3. Send approval: Email data governance team with blob details and Approve/Reject options
  4. Conditional delete: Only delete if approval is received within the timeout window

Key Logic Apps actions to use:

This ensures critical data is never deleted without human oversight while still automating the notification and execution workflow.


Monitoring and Auditing

Azure Monitor Metrics for Tier Distribution

Track blob count and capacity by tier using Azure Monitor:

# Query blob capacity by tier
az monitor metrics list \
  --resource "/subscriptions/{sub-id}/resourceGroups/myRG/providers/Microsoft.Storage/storageAccounts/mystorageaccount/blobServices/default" \
  --metric "BlobCapacity" \
  --dimension "BlobType,Tier" \
  --interval PT1H \
  --start-time 2024-04-01T00:00:00Z \
  --end-time 2024-04-02T00:00:00Z

KQL Queries for Tracking Transitions

Query storage logs in Log Analytics to track lifecycle policy executions:

// Track tier transitions over the last 7 days
StorageBlobLogs
| where TimeGenerated > ago(7d)
| where OperationName == "SetBlobTier"
| extend PreviousTier = tostring(parse_json(ResponseBody).previousTier)
| extend NewTier = tostring(parse_json(ResponseBody).currentTier)
| summarize TransitionCount = count() by PreviousTier, NewTier, bin(TimeGenerated, 1d)
| order by TimeGenerated desc
// Identify blobs moved by lifecycle policy (empty CallerIpAddress = system)
StorageBlobLogs
| where TimeGenerated > ago(30d)
| where OperationName == "SetBlobTier" and CallerIpAddress == ""
| summarize BlobsMoved = count() by bin(TimeGenerated, 1d)
| render timechart

Cost Analysis Queries

Track storage cost trends with KQL against Cost Management data exports:

// Storage cost breakdown by tier
AzureCostManagement
| where ServiceName == "Storage" and MeterCategory == "Azure Blob Storage"
| extend Tier = case(
    MeterSubCategory contains "Hot", "Hot",
    MeterSubCategory contains "Cool", "Cool",
    MeterSubCategory contains "Archive", "Archive", "Other")
| summarize MonthlyCost = sum(CostInBillingCurrency) by Tier, bin(Date, 1M)
| render columnchart

Real-World Scenarios

Scenario 1: Application Log Retention

A SaaS platform generates 500 GB of logs daily. Requirements: 7-day hot access for debugging, 30-day cool for incident investigation, 1-year archive for compliance, then delete.

{
  "rules": [
    {
      "name": "app-log-lifecycle",
      "enabled": true,
      "type": "Lifecycle",
      "definition": {
        "filters": {
          "blobTypes": ["blockBlob"],
          "prefixMatch": ["applogs/"]
        },
        "actions": {
          "baseBlob": {
            "tierToCool": { "daysAfterModificationGreaterThan": 7 },
            "tierToArchive": { "daysAfterModificationGreaterThan": 30 },
            "delete": { "daysAfterModificationGreaterThan": 365 }
          }
        }
      }
    }
  ]
}

Cost impact: Reduces monthly storage cost from ~$270/TB to ~$15/TB for data older than 30 days.

Scenario 2: Media Asset Archival

A media company stores video assets. New content is accessed frequently for editing, but after publication, access drops sharply. Assets must be retained indefinitely but can be archived after 6 months.

{
  "rules": [
    {
      "name": "media-archive-policy",
      "enabled": true,
      "type": "Lifecycle",
      "definition": {
        "filters": {
          "blobTypes": ["blockBlob"],
          "prefixMatch": ["media/published/"]
        },
        "actions": {
          "baseBlob": {
            "tierToCool": { "daysAfterLastAccessTimeGreaterThan": 30 },
            "tierToCold": { "daysAfterLastAccessTimeGreaterThan": 90 },
            "tierToArchive": { "daysAfterLastAccessTimeGreaterThan": 180 },
            "enableAutoTierToHotFromCool": {}
          }
        }
      }
    }
  ]
}

Note: Requires last access time tracking enabled. The enableAutoTierToHotFromCool action automatically promotes blobs back to Hot when accessed, handling the case where archived content goes viral again.

Scenario 3: Financial Compliance (SEC 17a-4)

A financial services firm must retain trading records for 7 years in immutable storage, with the first 2 years readily accessible:

{
  "rules": [
    {
      "name": "sec-17a4-compliance",
      "enabled": true,
      "type": "Lifecycle",
      "definition": {
        "filters": {
          "blobTypes": ["blockBlob"],
          "prefixMatch": ["trading-records/"],
          "blobIndexMatch": [
            { "name": "compliance", "op": "==", "value": "sec-17a4" }
          ]
        },
        "actions": {
          "baseBlob": {
            "tierToCool": { "daysAfterCreationGreaterThan": 90 },
            "tierToCold": { "daysAfterCreationGreaterThan": 365 },
            "tierToArchive": { "daysAfterCreationGreaterThan": 730 },
            "delete": { "daysAfterCreationGreaterThan": 2555 }
          }
        }
      }
    }
  ]
}

Important: Combine with immutable storage (WORM policies) to prevent modification or deletion before the retention period expires. The lifecycle delete action respects immutability — blobs won't be deleted if a legal hold or time-based retention policy is active.


Common Pitfalls, Troubleshooting, and Best Practices

Common Pitfalls

  1. Early deletion fees: Moving a blob to Cool and then to Archive before the 30-day minimum duration incurs early deletion charges. The lifecycle engine accounts for this, but manual tier changes do not.

  2. Policy execution delay: Lifecycle policies run once per day. After creating or updating a policy, it may take up to 24-48 hours for the first execution. Don't assume immediate effect.

  3. Archive tier is offline: Blobs in Archive cannot be read without rehydration. Ensure downstream systems handle 409 Conflict errors gracefully when attempting to read archived blobs.

  4. Prefix match is case-sensitive: "prefixMatch": ["Logs/"] will NOT match blobs in logs/. Ensure consistent naming conventions.

  5. Last access time tracking overhead: Enabling last access time tracking adds a small overhead to every read operation. For extremely high-throughput scenarios (millions of reads/second), evaluate the performance impact.

  6. Rule ordering doesn't matter: All rules are evaluated independently. If two rules conflict (one says tier to Cool at 30 days, another says delete at 25 days), the delete wins because it executes first chronologically.

Troubleshooting

Policy not executing:

# Verify policy is applied
az storage account management-policy show \
  --account-name mystorageaccount \
  --resource-group myResourceGroup

Blobs not transitioning — check:

Rehydration stuck:

az storage blob show \
  --account-name mystorageaccount \
  --container-name data \
  --name "archived-blob.dat" \
  --query "properties.archiveStatus"
# Expected: "rehydrate-pending-to-hot" or "rehydrate-pending-to-cool"

Best Practices

  1. Start with monitoring — analyze access patterns using storage analytics before creating policies.
  2. Use access-time-based tiering when possible — more accurate than modification-based for read-heavy workloads.
  3. Combine with blob index tags for business-context-aware lifecycle management.
  4. Test with a limited prefix first before rolling out account-wide.
  5. Set up alerts for lifecycle operation failures via Azure Monitor.
  6. Manage versions explicitly — previous versions accumulate silently and can dominate costs.
  7. Plan for rehydration — document the process and expected wait times for operations teams.
  8. Account for minimum storage duration charges — moving blobs before the minimum incurs the remaining duration's cost.

Summary

AspectRecommendation
Cost optimizationImplement tiered lifecycle: Hot → Cool (30d) → Cold (90d) → Archive (180d) → Delete
ComplianceUse daysAfterCreationGreaterThan with immutable storage for regulatory retention
MonitoringEnable storage analytics logging + KQL queries in Log Analytics
RehydrationDefault to Standard priority; use High only for urgent needs
GovernanceCombine prefix filters with blob index tags for business-context rules
TestingAlways test policies on a limited scope before account-wide rollout

Lifecycle management is the single most impactful cost optimization for Azure Blob Storage. A well-designed policy can reduce storage costs by 60-80% while simultaneously enforcing compliance requirements and improving data governance posture.