Infrastructure
Azure Storage: validate lifecycle policy impact before deletion
A production runbook for qualifying an Azure Storage lifecycle policy with Blob inventory, versions, snapshots, delete or tiering rules, evidence, validation and rollback before removing data.
An Azure Storage lifecycle policy often looks harmless: move old blobs to Cool or Archive, delete stale snapshots, clean up temporary exports. In production, that is a real operational change. A broad rule can hit a shared prefix, delete a version that was still useful, archive an object the application reads every night, or make rollback much slower than the team expected.
The use case is a Storage account that receives application exports, attachments and diagnostic files. The team wants to enable cleanup to reduce noise and cost. The hard part is not writing the policy. The hard part is proving what it would touch, which paths must be excluded, how to validate after activation, and what to do if a useful object is deleted or moved.
The goal of this runbook is to make a clear decision: enable the policy, narrow it, postpone it, or roll it back before evidence is lost.
Define the data scope
Start by naming the containers, prefixes and object types in scope. A lifecycle policy acts on blobs, versions and snapshots based on age, prefix, blob type and sometimes index tags. It does not understand the business intent behind a folder name.
storage_account: stprodappdata
subscription: production
resource_group: rg-data-prod
containers:
exports:
intended_action: delete_after_45_days
owners: product-operations
restore_need: last_successful_export_for_replay
attachments:
intended_action: no_delete_without_business_owner
owners: application-team
restore_need: user_visible_content
diagnostics:
intended_action: move_to_cool_after_30_days_delete_after_180_days
owners: platform-operations
restore_need: incident_evidence
policy_change:
decision_needed: enable_limit_or_postpone
rollback_owner: platform-operations
validation_window: 48h
excluded_prefixes:
- attachments/
- exports/manual-replay/ Without this contract, the policy will be treated as storage optimization while it actually changes restore capability, evidence retention and sometimes compliance posture.
Read the existing policy and history
Before adding a rule, capture the current state. Confirm whether a lifecycle policy already exists, who changed it recently, and whether the account uses soft delete, versioning, snapshots or immutability.
RG="rg-data-prod"
ACCOUNT="stprodappdata"
az storage account management-policy show --resource-group "$RG" --account-name "$ACCOUNT" --output json
az storage account blob-service-properties show --resource-group "$RG" --account-name "$ACCOUNT" --query "{deleteRetention:deleteRetentionPolicy,containerDeleteRetention:containerDeleteRetentionPolicy,versioning:isVersioningEnabled,changeFeed:changeFeed.enabled,restorePolicy:restorePolicy}" --output json
az monitor activity-log list --resource-group "$RG" --offset 14d --query "[?contains(operationName.value, 'managementPolicies') || contains(operationName.value, 'blobServices')].{time:eventTimestamp,operation:operationName.value,caller:caller,status:status.value}" --output table This avoids two common mistakes: enabling deletion without a suitable recovery window, or changing a policy another team already relies on.
Rebuild impact before activation
Azure Storage Lifecycle Management should not be treated as a button with a magic dry run. Before activation, build an inventory of blobs that would match the rule criteria. The target output is a reviewable list of paths, ages, sizes and likely owners.
ACCOUNT="stprodappdata"
CONTAINER="exports"
CUTOFF="2026-06-10T00:00:00Z"
az storage blob list --account-name "$ACCOUNT" --container-name "$CONTAINER" --auth-mode login --include m t v --query "[?properties.lastModified < '$CUTOFF'].{name:name,lastModified:properties.lastModified,accessTier:properties.blobTier,size:properties.contentLength,versionId:versionId,tags:tags}" --output json For large accounts, use Blob Inventory or a controlled export instead of a fragile CLI loop. The operating point is the same: produce a verifiable sample and a prefix-level estimate before writing the rule.
Classify deletion, tiering and restore risk
Not all actions carry the same risk. Moving to Cool is not the same as archiving, deleting a version or deleting a snapshot.
Move to Cool
Main risk: different read cost and latency
Validation: does the application read these objects during normal operation?
Move to Archive
Main risk: slow rehydration and broken application path if immediate reads are expected
Validation: no production job depends on direct reads
Delete base blob
Main risk: object disappears from the application view
Validation: business owner, retention, soft delete and proof of non-use
Delete version or snapshot
Main risk: rollback or incident evidence path is lost
Validation: useful versions or snapshots, restore test, accepted retention window
Filter by prefix only
Main risk: shared prefix or unstable naming convention
Validation: prefix inventory and explicit exclusions This classification helps keep the first policy small. A good first change may tier a well-understood diagnostics prefix, not delete everything older than a global age.
Check real access before deciding
An old blob can still be useful. Before deletion, look for recent reads, application errors and batch dependencies. Storage logs do not replace business ownership, but they prevent the team from deciding only from last modified time.
let Window = 30d;
StorageBlobLogs
| where TimeGenerated > ago(Window)
| where AccountName == "stprodappdata"
| where ContainerName in ("exports", "diagnostics")
| summarize reads=countif(OperationName in ("GetBlob", "GetBlobProperties")),
writes=countif(OperationName in ("PutBlob", "PutBlockList")),
deletes=countif(OperationName has "Delete"),
failures=countif(StatusCode >= 400),
callers=make_set(AuthenticationType, 5),
sampleUris=make_set(Uri, 5)
by ContainerName, bin(TimeGenerated, 1d)
| order by TimeGenerated desc If logs are not available, that is part of the decision. The safer path may be enabling diagnostics first, narrowing the scope, or requiring owner sign-off before any deletion rule.
Write a bounded policy
The policy should be readable. Name rules, limit prefixes, separate deletion from tiering, and avoid mixing business objects with technical artifacts.
{
"rules": [
{
"enabled": true,
"name": "diagnostics-cool-after-30-days",
"type": "Lifecycle",
"definition": {
"filters": {
"blobTypes": ["blockBlob"],
"prefixMatch": ["diagnostics/platform/"]
},
"actions": {
"baseBlob": {
"tierToCool": { "daysAfterModificationGreaterThan": 30 },
"delete": { "daysAfterModificationGreaterThan": 180 }
},
"snapshot": {
"delete": { "daysAfterCreationGreaterThan": 90 }
},
"version": {
"delete": { "daysAfterCreationGreaterThan": 90 }
}
}
}
}
]
} Do not start with a high-level prefix such as exports/ if that directory contains replayable exports, user files and temporary artifacts. Create stable operational prefixes before automating deletion.
Enable with a short validation window
After applying the policy, validation should prove three things: the policy is in place, expected operations appear, and excluded paths are untouched.
RG="rg-data-prod"
ACCOUNT="stprodappdata"
az storage account management-policy create --resource-group "$RG" --account-name "$ACCOUNT" --policy @04-bounded-lifecycle-policy.json
az storage account management-policy show --resource-group "$RG" --account-name "$ACCOUNT" --query "policy.rules[].{name:name,enabled:enabled,filters:definition.filters,actions:definition.actions}" --output json Add monitoring for deletes and tier changes.
let Window = 48h;
StorageBlobLogs
| where TimeGenerated > ago(Window)
| where AccountName == "stprodappdata"
| where OperationName has_any ("Delete", "SetBlobTier")
| summarize operations=count(), statuses=make_set(StatusText, 10), sampleUris=make_set(Uri, 10)
by OperationName, ContainerName, bin(TimeGenerated, 1h)
| order by TimeGenerated desc If deletion appears in an unexpected container or prefix, disable the rule immediately and preserve logs before editing the policy again.
Prepare rollback
Rolling back a lifecycle policy does not automatically restore objects. It stops future actions. Recovery then depends on soft delete, versions, snapshots, backup or rehydration from Archive.
rollback:
stop_policy:
action: disable_or_remove_rule
validation: management_policy_read_back_without_rule
restore_deleted_blob:
prerequisites:
- soft_delete_enabled
- retention_window_not_expired
- exact_container_and_blob_name_known
validation: blob_readable_from_application_path
restore_version_or_snapshot:
prerequisites:
- version_id_or_snapshot_timestamp_known
- owner_approval_for_restore
validation: restored_object_hash_or_size_matches_evidence
rehydrate_archive:
prerequisites:
- archive_tier_was_action_taken
- business_accepts_rehydration_delay
validation: object_available_before_batch_resume
evidence_to_keep:
- policy_before_after
- impacted_prefix_inventory
- StorageBlobLogs_extract
- business_owner_decision The rollback decision must therefore be quick. The shorter the retention window, the stricter the initial validation should be.
Make an explicit decision
The final decision should fit on an operations card.
Enable
Candidate inventory reviewed by owner
Bounded prefixes and explicit exclusions
Soft delete, versioning or another restore path matches the risk
Logs available to watch Delete and SetBlobTier
Rollback tested or documented
Narrow
Prefix is too broad
Batch reads or replayable exports remain uncertain
Versions or snapshots are still useful
Restore path has not been tested
Postpone
No clear owner
Insufficient logs
Business or compliance retention not confirmed
Existing policy is not understood
Roll back
Deletion or tiering outside the approved scope
Application errors correlated with touched objects
Useful object recoverable only during a short window Conclusion
An Azure Storage lifecycle policy is not only a cost rule. It is a production action on data availability, evidence and recovery. The runbook should therefore start with inventory, not JSON syntax.
The right decision is to bound prefixes, separate tiering from deletion, verify real access, enable with a short watch window, then keep a rollback path that stops the policy and restores impacted objects while recovery is still possible. Reliable automatic deletion is deletion proven before it is automated.