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

  1. Navigate to Storage AccountData protection
  2. Under Versioning:
    • Enable Blob versioning
    • Optionally enable Version-level immutability support
  3. Under Blob soft delete:
    • Enable Container soft delete
    • Set retention days (1-365)
  4. 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

PracticeDescription
Enable both featuresUse versioning + soft delete together
Set appropriate retention7-30 days is common for soft delete
Use lifecycle policiesDelete old versions automatically
Test restorationPractice recovery regularly
Monitor costsTrack 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


Azure Integration Hub - Intermediate Level