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
| Attribute | Hot | Cool | Cold | Archive |
|---|---|---|---|---|
| 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 duration | None | 30 days | 90 days | 180 days |
| Access latency | Milliseconds | Milliseconds | Milliseconds | Hours |
| Early deletion penalty | None | Yes | Yes | Yes |
When to Use Each Tier
- Hot: Active data accessed or modified frequently. Web assets, current application data, streaming ingestion buffers.
- Cool: Infrequently accessed data stored for at least 30 days. Short-term backups, older analytics data, content not currently displayed.
- Cold: Rarely accessed data stored for at least 90 days. Quarterly reports, aging backups, disaster recovery copies.
- Archive: Offline data stored for at least 180 days. Compliance archives, raw historical data, long-term backups that are rarely if ever read.
Cost Savings Example
For 50 TB of log data with decreasing access frequency:
| Strategy | Monthly Cost |
|---|---|
| All Hot | $900 |
| Lifecycle (Hot→Cool→Archive→Delete) | ~$180 |
| Savings | ~80% |
Step-by-Step Implementation
Creating Lifecycle Policies via Azure Portal
- Navigate to your Storage Account in the Azure Portal
- Under Data management, select Lifecycle management
- Click Add a rule
- Configure:
- Rule name (alphanumeric, no spaces)
- Rule scope: Limit blobs with filters, or apply to all blobs
- Blob type: Block blobs, Append blobs
- Set conditions and actions for base blobs, snapshots, and versions
- 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:
blockBlob— supports all actions (tier transitions and delete)appendBlob— supports only delete action
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:
| Action | Description | Supported Blob Types |
|---|---|---|
tierToCool | Move to Cool tier | Block blobs |
tierToCold | Move to Cold tier | Block blobs |
tierToArchive | Move to Archive tier | Block blobs |
delete | Delete the blob | Block blobs, Append blobs |
enableAutoTierToHotFromCool | Auto-tier back to Hot on access | Block blobs |
Actions for snapshots and versions:
| Action | Description |
|---|---|
tierToCool | Move snapshot/version to Cool |
tierToCold | Move snapshot/version to Cold |
tierToArchive | Move snapshot/version to Archive |
delete | Delete the snapshot/version |
Conditions
Conditions define when an action triggers based on blob age:
| Condition | Description | Use Case |
|---|---|---|
daysAfterModificationGreaterThan | Days since last modified | General-purpose aging |
daysAfterCreationGreaterThan | Days since blob creation | Immutable data, snapshots |
daysAfterLastAccessTimeGreaterThan | Days since last read/write | Access-pattern-based tiering |
daysAfterLastTierChangeGreaterThan | Days since last tier change | Prevent 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
| Attribute | Standard | High Priority |
|---|---|---|
| Rehydration time | Up to 15 hours | Under 1 hour (for blobs < 10 GB) |
| Cost (per GB) | Same as read cost for target tier | Higher rate |
| Use case | Planned retrieval, batch processing | Urgent 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:
- Trigger: Daily recurrence schedule
- List blobs: Query blobs approaching their deletion threshold (e.g., within 7 days)
- Send approval: Email data governance team with blob details and Approve/Reject options
- Conditional delete: Only delete if approval is received within the timeout window
Key Logic Apps actions to use:
Azure Blob Storage — List blobswith filter for last modified dateOffice 365 Outlook — Send approval emailwith optionsAzure Blob Storage — Delete blobin the approved branchConditionaction to branch on the approval response
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
-
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.
-
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.
-
Archive tier is offline: Blobs in Archive cannot be read without rehydration. Ensure downstream systems handle
409 Conflicterrors gracefully when attempting to read archived blobs. -
Prefix match is case-sensitive:
"prefixMatch": ["Logs/"]will NOT match blobs inlogs/. Ensure consistent naming conventions. -
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.
-
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:
- Blob's last modified date actually exceeds the threshold
- Prefix filter matches (case-sensitive, including container name)
- Storage account is GPv2 or Blob Storage (not GPv1)
- Rule is enabled (
"enabled": true)
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
- Start with monitoring — analyze access patterns using storage analytics before creating policies.
- Use access-time-based tiering when possible — more accurate than modification-based for read-heavy workloads.
- Combine with blob index tags for business-context-aware lifecycle management.
- Test with a limited prefix first before rolling out account-wide.
- Set up alerts for lifecycle operation failures via Azure Monitor.
- Manage versions explicitly — previous versions accumulate silently and can dominate costs.
- Plan for rehydration — document the process and expected wait times for operations teams.
- Account for minimum storage duration charges — moving blobs before the minimum incurs the remaining duration's cost.
Summary
| Aspect | Recommendation |
|---|---|
| Cost optimization | Implement tiered lifecycle: Hot → Cool (30d) → Cold (90d) → Archive (180d) → Delete |
| Compliance | Use daysAfterCreationGreaterThan with immutable storage for regulatory retention |
| Monitoring | Enable storage analytics logging + KQL queries in Log Analytics |
| Rehydration | Default to Standard priority; use High only for urgent needs |
| Governance | Combine prefix filters with blob index tags for business-context rules |
| Testing | Always 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.