Azure Blob Versioning & Soft Delete
Data Protection and Recovery
Introduction
Azure Blob Storage provides built-in data protection features that help prevent data loss and enable recovery from accidental deletions or modifications. Blob versioning automatically maintains previous versions of blobs when they're overwritten, allowing you to access or restore any previous state. Soft delete provides an additional safety net by retaining deleted blobs for a configurable period before permanent removal. Together, these features form a comprehensive data protection strategy for enterprise storage needs.
This comprehensive guide covers:
- Blob versioning — Automatic version management
- Version behavior — How versions are created and stored
- Soft delete — Recovering deleted blobs
- Restoration techniques — Restoring versions and deleted blobs
- Cost considerations — Managing version storage costs
- Best practices — Implementing comprehensive data protection
Understanding Versioning
How Blob Versioning Works
┌─────────────────────────────────────────────────────────────────────┐
│ BLOB VERSIONING FLOW │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ INITIAL UPLOAD │
│ ─────────────── │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Blob: "data.txt" │ │
│ │ Version: "2024-01-15T10:00:00.0000000Z" │ │
│ │ Content: "Version 1" │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ AFTER MODIFICATION │
│ ─────────────────── │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Blob: "data.txt" (current) │ │
│ │ Version: "2024-01-16T14:30:00.0000000Z" (newest) │ │
│ │ Content: "Version 2" │ │
│ │ │ │
│ │ Version: "2024-01-15T10:00:00.0000000Z" (previous) │ │
│ │ Content: "Version 1" │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ KEY CHARACTERISTICS: │
│ ───────────────────────── │
│ ✓ Each modification creates new version │
│ ✓ Previous versions remain accessible │
│ ✓ Versions are immutable once created │
│ ✓ Version ID is unique timestamp-based │
│ ✓ Current blob is just the latest version │
│ │
└─────────────────────────────────────────────────────────────────────┘
Version Access Patterns
┌─────────────────────────────────────────────────────────────────────┐
│ ACCESSING BLOB VERSIONS │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Current Version (latest) │
│ ───────────────────────── │
│ GET https://mystorage.blob.core.windows.net/container/data.txt │
│ → Returns: "Version 3 content" │
│ │
│ Specific Version │
│ ──────────────── │
│ GET https://mystorage.blob.core.windows.net/container/data.txt │
│ ?versionid=2024-01-16T10:00:00.0000000Z │
│ → Returns: "Version 2 content" │
│ │
│ List All Versions │
│ ──────────────── │
│ GET https://mystorage.blob.core.windows.net/container │
│ ?comp=versions&restype=container │
│ → Returns: List of all versions with IDs │
│ │
│ Version from SDK │
│ ──────────────── │
│ var blob = container.GetBlobClient("data.txt"); │
│ var version = blob.WithVersion("2024-01-16T10:00:00.0000000Z"); │
│ var content = await version.DownloadContentAsync(); │
│ │
└─────────────────────────────────────────────────────────────────────┘
Enable Versioning
Via Azure Portal
- Navigate to Storage Account → Data protection
- Under Versioning:
- Enable Blob versioning
- Optionally enable Version-level immutability support
- Under Blob soft delete:
- Enable Container soft delete
- Set retention days (1-365)
- Save changes
Via Azure CLI
# Enable blob versioning
az storage account update \
--name mystorage \
--resource-group my-rg \
--blob-service-properties '{
"versioning": {
"enabled": true
}
}'
# Enable soft delete with 7-day retention
az storage account update \
--name mystorage \
--resource-group my-rg \
--blob-service-properties '{
"deleteRetentionPolicy": {
"enabled": true,
"days": 7
}
}'
# Enable both together
az storage account update \
--name mystorage \
--resource-group my-rg \
--blob-service-properties '{
"versioning": { "enabled": true },
"deleteRetentionPolicy": { "enabled": true, "days": 14 }
}'
Via ARM Template
{
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-01-01",
"name": "mystorage",
"location": "eastus",
"properties": {
"blobServiceProperties": {
"versioning": {
"enabled": true
},
"deleteRetentionPolicy": {
"enabled": true,
"days": 14
},
"containerDeleteRetentionPolicy": {
"enabled": true,
"days": 7
}
}
}
}
]
}
Work with Versions in Code
List Blob Versions
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
public async Task ListVersionsAsync()
{
var blobServiceClient = new BlobServiceClient(
"https://mystorage.blob.core.windows.net",
new DefaultAzureCredential());
var containerClient = blobServiceClient.GetBlobContainerClient("mycontainer");
var blobClient = containerClient.GetBlobClient("data.txt");
// List all versions
await foreach (var versionItem in blobClient.GetVersionsAsync())
{
Console.WriteLine($"Version ID: {versionItem.VersionId}");
Console.WriteLine($"Created: {versionItem.Properties.CreatedOn}");
Console.WriteLine($"Size: {versionItem.Properties.ContentLength}");
// Access specific version
var specificVersion = blobClient.WithVersion(versionItem.VersionId);
var content = await specificVersion.DownloadContentAsync();
Console.WriteLine($"Content: {content.Value.Content}");
}
}
Access Specific Version
public async Task AccessVersionAsync(string versionId)
{
var blobServiceClient = new BlobServiceClient(
"https://mystorage.blob.core.windows.net",
new DefaultAzureCredential());
var containerClient = blobServiceClient.GetBlobContainerClient("mycontainer");
var blobClient = containerClient.GetBlobClient("data.txt");
// Access specific version by ID
var versionedBlob = blobClient.WithVersion(versionId);
// Download content
var response = await versionedBlob.DownloadContentAsync();
var content = response.Value.Content.ToString();
// Get properties
var properties = await versionedBlob.GetPropertiesAsync();
Console.WriteLine($"Created: {properties.Value.CreatedOn}");
}
Restore Previous Version
public async Task RestoreVersionAsync(string sourceVersionId)
{
var blobServiceClient = new BlobServiceClient(
"https://mystorage.blob.core.windows.net",
new DefaultAzureCredential());
var containerClient = blobServiceClient.GetBlobContainerClient("mycontainer");
var blobClient = containerClient.GetBlobClient("data.txt");
// Get the version to restore
var versionedBlob = blobClient.WithVersion(sourceVersionId);
// Copy version content to current blob
await blobClient.StartCopyFromUriAsync(versionedBlob.Uri);
// Or overwrite current with version content
var versionContent = await versionedBlob.DownloadContentAsync();
await blobClient.UploadAsync(BinaryData.FromString(versionContent.Value.Content));
}
Soft Delete Operations
Enable and Configure
# Configure soft delete
az storage account update \
--name mystorage \
--resource-group my-rg \
--blob-service-properties '{
"deleteRetentionPolicy": {
"enabled": true,
"days": 30
},
"containerDeleteRetentionPolicy": {
"enabled": true,
"days": 7
}
}'
Restore Deleted Blob
public async Task RestoreDeletedBlobAsync()
{
var blobServiceClient = new BlobServiceClient(
"https://mystorage.blob.core.windows.net",
new DefaultAzureCredential());
var containerClient = blobServiceClient.GetBlobContainerClient("mycontainer");
var blobClient = containerClient.GetBlobClient("deleted-file.txt");
// Undelete restores the most recently deleted version
await blobClient.UndeleteAsync();
// Or specify which deleted blob to restore
await blobClient.UndeleteAsync(
new BlobUriBuilder(blobClient.Uri)
{
VersionId = "2024-01-16T12:00:00.0000000Z"
});
}
List Deleted Blobs
public async Task ListDeletedBlobsAsync()
{
var blobServiceClient = new BlobServiceClient(
"https://mystorage.blob.core.windows.net",
new DefaultAzureCredential());
var containerClient = blobServiceClient.GetBlobContainerClient("mycontainer");
// List blobs including deleted
await foreach (var blobItem in containerClient.GetBlobsAsync(
traits: BlobListingDetails.Deleted))
{
Console.WriteLine($"Deleted blob: {blobItem.Name}");
Console.WriteLine($"Deleted time: {blobItem.DeletedOn}");
Console.WriteLine($"Remaining retention: {blobItem.Properties.DeletedRetentionRemaining}");
}
}
Use Cases and Patterns
Point-in-Time Recovery
┌─────────────────────────────────────────────────────────────────────┐
│ POINT-IN-TIME RECOVERY WORKFLOW │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Scenario: Accidentally modified file and need to restore │
│ ────────────────────────────────────────────────────────────── │
│ │
│ Step 1: List versions │
│ ├─ Version 3: Today 2PM (current - corrupted) │
│ ├─ Version 2: Yesterday 10AM │
│ └─ Version 1: Last week │
│ │
│ Step 2: Identify good version (Version 1) │
│ │
│ Step 3: Restore │
│ └─ Copy Version 1 content to current │
│ │
│ Result: File restored to good state │
│ │
└─────────────────────────────────────────────────────────────────────┘
Prevent Accidental Overwrites
public async Task PreventOverwriteWithVersioning()
{
var blobServiceClient = new BlobServiceClient(
"https://mystorage.blob.core.windows.net",
new DefaultAzureCredential());
var containerClient = blobServiceClient.GetBlobContainerClient("protected");
var blobClient = containerClient.GetBlobClient("important.txt");
// Check if blob exists and has previous versions
var exists = await blobClient.ExistsAsync();
if (exists)
{
// Download current content
var currentContent = await blobClient.DownloadContentAsync();
// Backup current before modification
await blobClient.SetMetadataAsync(new Dictionary<string, string>
{
{ "backup-date", DateTime.UtcNow.ToString("o") }
});
// Now safe to modify - version will be created automatically
}
// Upload new content - creates new version automatically
await blobClient.UploadAsync(BinaryData.FromString("New content"));
}
Cost Considerations
Version Storage Costs
┌─────────────────────────────────────────────────────────────────────┐
│ VERSION COST IMPACT │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Without Versioning: │
│ ───────────────────── │
│ 1 blob × $0.0184/GB = ~$0.02/month │
│ │
│ With Versioning (5 versions over 6 months): │
│ ────────────────────────────────────────── │
│ Version 1: $0.0184/GB × 6 months = $0.11 │
│ Version 2: $0.0184/GB × 5 months = $0.09 │
│ Version 3: $0.0184/GB × 4 months = $0.07 │
│ Version 4: $0.0184/GB × 3 months = $0.06 │
│ Version 5: $0.0184/GB × 2 months = $0.04 │
│ Current: $0.0184/GB × 1 month = $0.02 │
│ ───────────────────────────────────── │
│ Total: ~$0.39/month (20x increase) │
│ │
│ Mitigation Strategies: │
│ ───────────────────────── │
│ ✓ Use lifecycle policies to delete old versions │
│ ✓ Set reasonable version retention limits │
│ ✓ Archive old versions to cold storage │
│ ✓ Monitor version counts per blob │
│ │
└─────────────────────────────────────────────────────────────────────┘
Best Practices
Data Protection Checklist
| Practice | Description |
|---|---|
| Enable both features | Use versioning + soft delete together |
| Set appropriate retention | 7-30 days is common for soft delete |
| Use lifecycle policies | Delete old versions automatically |
| Test restoration | Practice recovery regularly |
| Monitor costs | Track version storage usage |
Implementation Pattern
public class ProtectedBlobStorage
{
public async Task InitializeProtectionAsync(string storageAccount)
{
// Ensure versioning and soft delete are enabled
// This is typically done at account level, not per-blob
var blobServiceClient = new BlobServiceClient(
$"https://{storageAccount}.blob.core.windows.net",
new DefaultAzureCredential());
var properties = await blobServiceClient.GetPropertiesAsync();
if (!properties.Value.Value.BlobServiceProperties.VersioningEnabled)
{
throw new InvalidOperationException("Enable blob versioning first");
}
if (!properties.Value.Value.BlobServiceProperties.DeleteRetentionPolicy.Enabled)
{
throw new InvalidOperationException("Enable soft delete first");
}
}
}
Related Topics
- Lifecycle Policies — Automating cleanup
- Blob Event Grid — Event-driven processing
- Blob Managed Identity — Secure access
Azure Integration Hub - Intermediate Level