Theoretical Foundation: Configure Blob Versioning
1. Initial Intuitionβ
Imagine you edit an important document and save over the original file. An hour later, you realize the previous version was correct. Without version history, the data is lost.
Now imagine that every time you save the file, the system automatically keeps a numbered copy of the previous version. You can go back to any previous version at any time. This is exactly how tools like Git work for code.
Blob Versioning applies this same concept to Azure Blob Storage: every time a blob is created, modified, or replaced, Azure automatically preserves the previous version. The complete modification history is available, and any previous version can be recovered with a single operation.
The fundamental difference compared to snapshots (which we saw in Azure Files) is that versioning is automatic: you don't need to remember to create a copy before modifying. The system does it for you on every write operation.
2. Contextβ
2.1 Blob Versioning within the protection strategyβ
2.2 What Blob Versioning is and what it is notβ
Is: An automatic content versioning system for Block Blobs in Azure Blob Storage.
Is not:
- Available for Append Blobs or Page Blobs
- A complete replacement for backups
- Free (each version takes space and is charged)
- Available in Premium accounts (only Standard GPv2)
2.3 Critical dependenciesβ
Blob Versioning is a prerequisite for other functionalities:
- Object Replication: Requires versioning enabled on both accounts (source and destination)
- Point-in-time Restore: Requires versioning + soft delete + change feed simultaneously
- Lifecycle Management of versions: Requires versioning to have
versionsection in rules
3. Building the Conceptsβ
3.1 Current version and previous versionsβ
When Blob Versioning is enabled, each blob has:
Current version: The most recent state of the blob. This is what you normally access through the blob's default URL.
Previous versions: Read-only copies of previous states of the blob. Each version has a unique Version ID, which is a timestamp in the format 2025-01-15T10:30:00.0000000Z.
3.2 What generates a new versionβ
Not every operation creates a new version. It's important to understand exactly which operations preserve the previous version:
| Operation | Creates previous version? | Result |
|---|---|---|
| Upload new blob (POST/PUT) | No (blob didn't exist) | Creates current version |
| Replace existing blob (PUT) | Yes | Current version becomes previous version; new content becomes current version |
| Modify metadata | Yes | Version with old metadata becomes previous version |
| Modify index tags | No | Tags don't create new version |
| Set Blob Tier (tier change) | No | Tier changes on current version |
| Delete Blob | Yes (special) | Current version becomes previous version; blob no longer has current version |
Non-obvious behavior when deleting with versioning: When you delete a blob with active versioning, the current version is not destroyed. It becomes a previous version without a corresponding current version. The blob appears deleted (default URL returns 404), but all previous versions continue to exist and are charged.
3.3 Version ID: the unique identifierβ
Each version has a Version ID that is automatically generated at creation time. The format is an ISO 8601 timestamp with 100-nanosecond precision:
2025-01-15T10:30:45.1234567Z
To access a specific version, add versionid as a parameter to the URL:
https://myaccount.blob.core.windows.net/container/report.xlsx?versionid=2025-01-15T10:30:45.1234567Z
3.4 Interaction between Versioning and Soft Deleteβ
When both are enabled, the behavior changes significantly:
Without Versioning, with Soft Delete:
- Delete blob β blob goes to soft-deleted β can be restored
With Versioning, with Soft Delete:
- Delete blob β current version becomes previous version β Soft Delete is not triggered for the blob itself
- Soft Delete now protects previous versions when they are explicitly deleted
This is one of the most important interactions and most tested in AZ-104.
3.5 Point-in-time Restore and Versioningβ
Point-in-time Restore (PITR) is a functionality that allows restoring the complete state of all blobs in a container to a specific moment in the past. It depends on:
- Blob Versioning enabled
- Soft Delete enabled
- Change Feed enabled
With PITR, you can say: "restore all blobs in this container to how they were at 2:00 PM yesterday". Azure uses the Change Feed and versions to reconstruct that state.
4. Structural Viewβ
5. Practical Operationβ
5.1 Restoring a blob to a previous versionβ
The restoration process is a copy operation: you copy a previous version over the current version. This creates a new current version with the content from the version you want to restore, and the version that was current becomes another previous version.
The copy operation is done with the Copy Blob command or az storage blob copy start specifying the source Version ID.
5.2 Promoting previous version to current versionβ
The official way to "restore" a version is using Copy Blob from URL with the Version ID:
# Copy previous version to make it the current version
az storage blob copy start \
--account-name myaccount \
--destination-container mycontainer \
--destination-blob report.xlsx \
--source-uri "https://myaccount.blob.core.windows.net/mycontainer/report.xlsx?versionid=2025-01-14T08:00:00.0000000Z&<SAS-TOKEN>" \
--auth-mode login
5.3 Versioning behavior with containers and blobsβ
Versioning is configured at the Storage Account level and applies to all blobs in the account. It's not possible to enable versioning only for specific containers or blobs.
6. Implementation Methodsβ
6.1 Azure Portalβ
Enabling Blob Versioning:
Storage Account > Data Protection > Enable versioning for blobs
The option is on the same screen as Soft Delete and Change Feed, allowing you to configure all data protections in one place.
Listing versions in the portal:
Storage Account > Containers > [container] > [blob] > Versions
The portal displays a list of all blob versions with Version ID, date, and size. You can download any version or promote it to current version.
6.2 Azure CLIβ
Enabling Blob Versioning:
az storage account blob-service-properties update \
--account-name myaccount \
--resource-group myRG \
--enable-versioning true
Checking if versioning is enabled:
az storage account blob-service-properties show \
--account-name myaccount \
--resource-group myRG \
--query "isVersioningEnabled"
Listing all versions of a blob:
az storage blob list \
--account-name myaccount \
--container-name mycontainer \
--prefix "report.xlsx" \
--include v \
--auth-mode login \
--output table
The --include v parameter instructs to include versions in the listing. The output shows the versionId of each version.
Downloading a specific version:
az storage blob download \
--account-name myaccount \
--container-name mycontainer \
--name "report.xlsx" \
--version-id "2025-01-14T08:00:00.0000000Z" \
--file "/local/path/report-v1.xlsx" \
--auth-mode login
Deleting a specific version:
az storage blob delete \
--account-name myaccount \
--container-name mycontainer \
--name "report.xlsx" \
--version-id "2025-01-14T08:00:00.0000000Z" \
--auth-mode login
Deleting all versions of a blob:
# Delete current version (main blob)
az storage blob delete \
--account-name myaccount \
--container-name mycontainer \
--name "report.xlsx" \
--auth-mode login
# List and delete each previous version
az storage blob list \
--account-name myaccount \
--container-name mycontainer \
--prefix "report.xlsx" \
--include v \
--auth-mode login \
--query "[].versionId" \
--output tsv | while read VID; do
az storage blob delete \
--account-name myaccount \
--container-name mycontainer \
--name "report.xlsx" \
--version-id "$VID" \
--auth-mode login
done
6.3 Azure PowerShellβ
$ctx = New-AzStorageContext `
-StorageAccountName "myaccount" `
-UseConnectedAccount
# List versions of a blob
Get-AzStorageBlob `
-Container "mycontainer" `
-Prefix "report.xlsx" `
-Context $ctx `
-IncludeVersion
# Get specific version
$version = Get-AzStorageBlob `
-Container "mycontainer" `
-Blob "report.xlsx" `
-VersionId "2025-01-14T08:00:00.0000000Z" `
-Context $ctx
# Promote previous version to current version
# (copies the specific version over the current blob)
Start-AzStorageBlobCopy `
-SrcContainer "mycontainer" `
-SrcBlob "report.xlsx" `
-SrcBlobSnapshotTime "2025-01-14T08:00:00.0000000Z" `
-DestContainer "mycontainer" `
-DestBlob "report.xlsx" `
-Context $ctx
6.4 Bicepβ
resource blobServiceProperties 'Microsoft.Storage/storageAccounts/blobServices@2023-01-01' = {
name: '${storageAccount.name}/default'
properties: {
isVersioningEnabled: true
// Enable together with other protections
deleteRetentionPolicy: {
enabled: true
days: 7
}
containerDeleteRetentionPolicy: {
enabled: true
days: 30
}
changeFeed: {
enabled: true
retentionInDays: 7
}
}
}
6.5 Lifecycle Management for versionsβ
This is one of the most critical points: old versions accumulate cost indefinitely without a lifecycle policy to manage them.
{
"rules": [
{
"name": "version-management",
"enabled": true,
"type": "Lifecycle",
"definition": {
"filters": {
"blobTypes": ["blockBlob"]
},
"actions": {
"version": {
"tierToCool": {
"daysAfterCreationGreaterThan": 30
},
"tierToArchive": {
"daysAfterCreationGreaterThan": 90
},
"delete": {
"daysAfterCreationGreaterThan": 365
}
}
}
}
}
]
}
Attention:
daysAfterCreationGreaterThanin the context ofversionrefers to the date that specific version was created (the moment it became a previous version), not the original blob creation date.
7. Control and Securityβ
7.1 Permissions to access versionsβ
Access to specific blob versions requires the same permissions as accessing the current blob:
| Operation | Minimum role |
|---|---|
| List versions | Storage Blob Data Reader |
| Read version content | Storage Blob Data Reader |
| Delete specific version | Storage Blob Data Contributor |
| Promote version to current (copy) | Storage Blob Data Contributor |
| Enable/disable versioning | Storage Account Contributor |
7.2 Versioning and immutabilityβ
When Immutability Policy is active along with Versioning:
- Versions protected by the policy cannot be deleted during the immutability period
- This creates very robust protection: each modification creates a new version, and versions cannot be deleted by attackers
- Even an administrator cannot delete immutable versions before the deadline
This combination is highly recommended for regulatory compliance that requires auditable modification history.
7.3 Versioning and SAS tokensβ
To access a specific version via SAS token, the SAS needs to include the versionid in the URL or the user needs permission to specify it. Service SAS and Account SAS tokens support access to versions.
User Delegation SAS (generated with Azure AD credentials) is the most secure option and also supports access to specific versions.
7.4 The cost problem with versioningβ
Enabling versioning without a lifecycle policy for version control is a recipe for increasing costs. Each blob modification creates a previous version that:
- Takes space proportional to the blob size
- Is charged in the tier it's in (usually Hot, since versions inherit the blob's tier)
- Is never automatically deleted without lifecycle policy
For a 1 GB blob modified 10 times per day, after 30 days you could have 300 previous versions potentially occupying 300 GB in Hot.
8. Decision Makingβ
8.1 Versioning vs Snapshotsβ
| Aspect | Blob Versioning | Manual snapshots |
|---|---|---|
| Automation | Automatic on every write | Manual (you decide when to create) |
| Granularity | Per write operation | Only when you create |
| Management | Requires lifecycle policy | Requires manual cleanup process |
| Cost | Potentially high without lifecycle | Controllable (you control when to create) |
| Recovery | By specific Version ID | By snapshot timestamp |
| Recommended use | Continuous protection against overwrite | Backup before specific operation |
8.2 When to enable Versioningβ
| Situation | Enable Versioning? | Reason |
|---|---|---|
| Critical data modified frequently | Yes | Automatic protection against overwrite |
| Write-once blobs (logs, backups) | Not necessarily | Data is never overwritten; Soft Delete is sufficient |
| Object Replication requirement | Yes (mandatory) | Prerequisite |
| Point-in-time Restore requirement | Yes (mandatory) | Prerequisite |
| Premium Block Blob account | Not available | Premium doesn't support versioning |
| Account with high volume and cost | Evaluate | Lifecycle policy is mandatory to control cost |
8.3 Recommended complementary configurationβ
| Functionality | Enable with Versioning? | Reason |
|---|---|---|
| Blob Soft Delete | Yes | Protects individually deleted versions |
| Container Soft Delete | Yes | Protects container and all versions |
| Change Feed | Yes for PITR | Necessary for Point-in-time Restore |
| Lifecycle policy for versions | Always | Mandatory cost control |
| Immutability Policy | For compliance | Makes versions auditable and immutable |
9. Best Practicesβ
- Always configure lifecycle policy for versions when enabling versioning. Never enable versioning without simultaneously defining expiration rules for
version. - Move old versions to cheaper tiers before deleting them. Use
tierToCoolafter 30 days anddeleteafter 365 days as a starting point. - Combine with Soft Delete for layered protection: versioning protects against overwrites, soft delete protects versions against explicit deletion.
- Enable versioning along with Change Feed if Point-in-time Restore is needed, as PITR requires both.
- Use Version ID when referencing versions in restoration scripts, never assume which version is the "second most recent" by position.
- Restrict who can delete specific versions using granular RBAC. Only designated administrators should have permission to delete versions.
- Monitor BlobCapacity by blob type separating versions from active content to understand the real cost of versioning.
- Document the restoration process for the team: how to list versions, how to identify the correct version and how to promote it.
10. Common Errorsβ
| Error | Why it happens | How to avoid |
|---|---|---|
| Explosive cost after enabling versioning | No lifecycle policy for versions; frequently modified blobs | Always create lifecycle policy for version simultaneously |
| "Deleted" blob still appears in costs | Delete with versioning doesn't remove previous versions | Explicitly delete each version after deleting current blob |
| Restoration creates unwanted extra version | Copy from previous version creates new current version | Expected behavior; document and include in lifecycle policy |
| Versioning doesn't work on Premium account | Premium Block Blob doesn't support versioning | Use Standard GPv2 for data that needs versioning |
| Object Replication fails | Versioning not enabled on one of the accounts | Enable versioning on both accounts before configuring replication |
| PITR not available | Versioning, Soft Delete or Change Feed not enabled | Enable all three simultaneously |
| Versions in Hot not included in lifecycle | Lifecycle policy without version section | Always include version section in lifecycle rules |
| Version ID hard to find | Not documented after upload operation | Capture Version ID from HTTP response and store in inventory system |
11. Operation and Maintenanceβ
11.1 Monitoring versions and costβ
# See total capacity of versions (in Azure Monitor)
az monitor metrics list \
--resource <storage-account-id> \
--metric BlobCapacity \
--dimension BlobType \
--interval P1D
Filter by BlobType=BlockBlobPreviousVersions to see only space occupied by previous versions.
Log Analytics query to track created versions:
StorageBlobLogs
| where OperationName == "PutBlob" and isnotempty(ResponseStatus)
| where ResponseStatus == "Success"
| extend VersionId = extract("versionid=([^&]+)", 1, RequestUri)
| where isnotempty(VersionId)
| summarize count() by bin(TimeGenerated, 1d)
11.2 Version inventory with Azure Storage Blob Inventoryβ
For accounts with large data volumes, use Blob Inventory to generate a CSV report of all blobs and versions:
az storage account blob-inventory-policy create \
--account-name myaccount \
--resource-group myRG \
--policy '{
"enabled": true,
"rules": [{
"name": "version-inventory",
"enabled": true,
"destination": "inventory-container",
"definition": {
"format": "Csv",
"schedule": "Weekly",
"objectType": "Blob",
"schemaFields": ["Name", "VersionId", "IsCurrentVersion", "ContentLength", "BlobType", "AccessTier", "LastModified"],
"filters": {
"includeSnapshots": false,
"includeBlobVersions": true
}
}
}]
}'
The report is generated weekly in the specified container and can be analyzed with Azure Synapse or Data Factory to identify versions candidates for cleanup.
11.3 Important limitsβ
| Aspect | Limit |
|---|---|
| Supported blob types | Block Blobs only |
| Supported account types | Standard GPv2 |
| Maximum number of versions per blob | No documented limit (practical: control via lifecycle) |
| Version ID | Timestamp with 100ns precision; unique per blob |
| Versioning disabling | Possible, but existing versions remain |
Important about disabling: If you disable versioning on an account that already had accumulated versions, existing versions are not deleted. They continue to exist, continue to be charged, but new versions are no longer created. To clean up existing versions, you need to delete them explicitly or use lifecycle policy that will continue to apply even after disabling versioning.
12. Integration and Automationβ
12.1 Point-in-time Restore: complete enablementβ
PITR requires three simultaneous functionalities. Enable all via CLI:
# Enable versioning, soft delete and change feed (PITR prerequisites)
az storage account blob-service-properties update \
--account-name myaccount \
--resource-group myRG \
--enable-versioning true \
--enable-delete-retention true \
--delete-retention-days 7 \
--enable-change-feed true \
--change-feed-retention-days 7
# Enable Point-in-time Restore with 6-day window
az storage account blob-service-properties update \
--account-name myaccount \
--resource-group myRG \
--enable-restore-policy true \
--restore-days 6
The PITR period must be shorter than the soft delete period. If soft delete is 7 days, PITR can be at most 6 days.
Executing a restore:
az storage blob restore \
--account-name myaccount \
--resource-group myRG \
--time-to-restore "2025-01-14T10:00:00Z" \
--blob-ranges '[{"startRange": "", "endRange": ""}]'
The blob-ranges parameter with empty startRange and endRange means "restore all blobs". You can specify prefixes to restore only part of the container.
12.2 Version inventory automation with Azure Functionβ
import azure.functions as func
from azure.storage.blob import BlobServiceClient
from datetime import datetime, timedelta
def main(timer: func.TimerRequest):
"""
Runs weekly and identifies versions
older than 90 days in Hot tier,
generating report of candidates for Archive.
"""
client = BlobServiceClient.from_connection_string(conn_str)
cutoff = datetime.utcnow() - timedelta(days=90)
candidates = []
for container in client.list_containers():
container_client = client.get_container_client(container['name'])
for blob in container_client.list_blobs(include=['versions']):
if (blob.get('version_id') and
not blob.get('is_current_version') and
blob.get('last_modified', datetime.utcnow()) < cutoff and
blob.get('blob_tier') == 'Hot'):
candidates.append({
'container': container['name'],
'blob': blob['name'],
'version_id': blob['version_id'],
'size_gb': blob['size'] / (1024**3),
'tier': blob['blob_tier']
})
# Save report as blob for analysis
report_client = client.get_blob_client("reports", f"version-audit-{datetime.utcnow().date()}.json")
report_client.upload_blob(str(candidates), overwrite=True)
13. Final Summaryβ
Essential concepts:
- Blob Versioning automatically preserves previous copies of a blob on each write, overwrite or delete operation.
- Each version has a unique Version ID (ISO 8601 timestamp) used to access or restore the specific version.
- The current version is accessed through the default URL. Previous versions require
?versionid=in the URL. - Versioning is a Storage Account setting and applies to all Block Blobs in the account.
Critical differences:
- Versioning vs Snapshots: Versioning is automatic on each write. Snapshots are manual (you decide when to create).
- Delete with Versioning: Deleting a blob with active versioning turns the current version into a previous version, but doesn't destroy it. The blob appears deleted but versions exist and are charged.
- Soft Delete with Versioning: Soft Delete now protects individually deleted versions, not the blob itself (since the "deleted" blob already becomes a previous version with versioning).
- Versioning and cost: Without lifecycle policy for
version, cost grows indefinitely. This is the most critical operational consequence.
What needs to be remembered:
- Versioning is only available on Standard GPv2. Premium accounts don't support it.
- Versioning only affects Block Blobs. Append and Page Blobs are not supported.
- Versioning is a prerequisite for Object Replication and Point-in-time Restore.
- Always configure lifecycle policy for the
versionsection when enabling versioning. - Disabling versioning doesn't delete existing versions; they continue to exist and be charged.
- Restoring a previous version is a copy operation, which creates a new current version and adds one more entry to the history.
- Versions in Hot tier are charged as normal blobs in Hot. Use lifecycle to move them to Cool or Archive as they age.