Project 11: Azure Storage Deep Dive β
Overview β
Master Azure Storage including blob services, file shares, access tiers, lifecycle management, and the new soft delete for blobs and containers (added in April 2025 exam update). This covers critical AZ-104 storage objectives.
Difficulty: Intermediate
Duration: 4-5 hours
Cost: ~$10-30/month (storage costs)
Exam Weight: Storage domain (15-20%)
April 2025 Exam Update
The AZ-104 exam now includes "Configure soft delete for blobs and containers" as a new objective. This project covers this topic in detail.
Architecture Diagram β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AZURE STORAGE ACCOUNT β
β (stdeepdivedemo12345) β
β β
β Account Kind: StorageV2 (General Purpose v2) β
β Performance: Standard / Premium β
β Replication: LRS / ZRS / GRS / GZRS / RA-GRS / RA-GZRS β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β BLOB SERVICE β β
β β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β SOFT DELETE PROTECTION β β β
β β β (NEW in April 2025 Exam) β β β
β β β β β β
β β β ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ β β β
β β β β Blob Soft β β Container Soft β β Blob Versioning β β β β
β β β β Delete β β Delete β β β β β β
β β β β β β β β β β β β
β β β β Retention: 7d β β Retention: 7d β β Automatic β β β β
β β β β Recover deleted β β Recover deleted β β version β β β β
β β β β blobs β β containers β β tracking β β β β
β β β ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β CONTAINERS β β β
β β β β β β
β β β ββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββββββββββ β β β
β β β β hot-data β β cool-data β β archive-data β β β β
β β β β β β β β β β β β
β β β β Access: Hot β β Access: Cool β β Access: Archive β β β β
β β β β Frequent β β Infrequent β β Rare access β β β β
β β β β access β β access β β (hours to rehydrate) β β β β
β β β β β β β β β β β β
β β β β Cost: $$$ β β Cost: $$ β β Cost: $ β β β β
β β β ββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββββββββββ β β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β LIFECYCLE MANAGEMENT β β β
β β β β β β
β β β Day 0 Day 30 Day 90 Day 365 β β β
β β β β β β β β β β
β β β βΌ βΌ βΌ βΌ β β β
β β β [Hot] βββββΆ [Cool] βββββΆ [Archive] βββββΆ [Delete] β β β
β β β β β β
β β β Rules: β β β
β β β - Move to Cool after 30 days without access β β β
β β β - Move to Archive after 90 days β β β
β β β - Delete after 365 days β β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β FILE SERVICE β β
β β β β
β β ββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββββββββ β β
β β β File Share: β β Mounting: β β β
β β β company-files β β β β β
β β β β β Windows: net use Z: \\storage\share β β β
β β β Quota: 100 GB β β Linux: mount -t cifs //storage/share β β β
β β β Protocol: SMB 3.0 β β macOS: mount_smbfs //storage/share β β β
β β β β β β β β
β β β Soft Delete: 14 daysβ β Azure File Sync: Hybrid scenarios β β β
β β ββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β SECURITY β β
β β β β
β β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β β
β β β Access Keys β β SAS Tokens β β Azure AD β β Private β β β
β β β β β β β Auth β β Endpoints β β β
β β β key1, key2 β β Account SAS β β β β β β β
β β β Rotate β β Service SAS β β RBAC roles: β β No public β β β
β β β regularly β β User SAS β β - Blob Data β β access β β β
β β β β β β β Owner β β β β β
β β β Store in β β Time-limitedβ β - Blob Data β β Private β β β
β β β Key Vault β β permissions β β Reader β β DNS zone β β β
β β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Storage Redundancy Options:
βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ
β LRS β β ZRS β β GRS β β RA-GRS β
β 3 copies β β 3 copies β β 6 copies β β 6 copies β
β 1 region β β 3 zones β β 2 regions β β 2 regions β
β 1 DC β β 1 region β β β β Read accessβ
β β β β β β β secondary β
β 99.9999999%β β 99.9999999% β β 99.9999999 β β 99.9999999 β
β (11 9's) β β (12 9's) β β (16 9's) β β (16 9's) β
βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββAccess Tier Comparison β
| Tier | Storage Cost | Access Cost | Min Retention | Use Case |
|---|---|---|---|---|
| Hot | High | Low | None | Frequent access |
| Cool | Medium | Medium | 30 days | Infrequent access |
| Cold | Low | Higher | 90 days | Rare access |
| Archive | Lowest | Highest | 180 days | Long-term backup |
What You'll Learn β
- Configure storage account redundancy
- Enable and test soft delete (NEW)
- Configure blob versioning
- Set up lifecycle management policies
- Create and mount Azure file shares
- Generate and use SAS tokens
- Configure private endpoints
Phase 1: Create Storage Account β
Step 1.1: Create Resource Group β
bash
# Set variables
LOCATION="eastus"
RG_NAME="rg-storage-deepdive"
STORAGE_NAME="stdeepd$(date +%s | tail -c 8)"
# Create resource group
az group create \
--name $RG_NAME \
--location $LOCATION \
--tags Project=StorageDeepDive Environment=Lab
echo "Resource group created"Step 1.2: Create Storage Account with GRS β
bash
# Create storage account with Geo-Redundant Storage
az storage account create \
--resource-group $RG_NAME \
--name $STORAGE_NAME \
--location $LOCATION \
--sku Standard_GRS \
--kind StorageV2 \
--access-tier Hot \
--min-tls-version TLS1_2 \
--allow-blob-public-access false \
--https-only true
# Get storage key
STORAGE_KEY=$(az storage account keys list \
--resource-group $RG_NAME \
--account-name $STORAGE_NAME \
--query "[0].value" -o tsv)
echo "Storage account created: $STORAGE_NAME"Phase 2: Configure Soft Delete (NEW Exam Objective) β
Important for AZ-104
Soft delete for blobs and containers is a new exam objective added in April 2025. Make sure you understand how to configure and test it.
Step 2.1: Enable Blob Soft Delete β
bash
# Enable blob soft delete with 7-day retention
az storage account blob-service-properties update \
--resource-group $RG_NAME \
--account-name $STORAGE_NAME \
--enable-delete-retention true \
--delete-retention-days 7
# Verify soft delete is enabled
az storage account blob-service-properties show \
--resource-group $RG_NAME \
--account-name $STORAGE_NAME \
--query "deleteRetentionPolicy"
echo "Blob soft delete enabled: 7 days retention"Step 2.2: Enable Container Soft Delete β
bash
# Enable container soft delete with 7-day retention
az storage account blob-service-properties update \
--resource-group $RG_NAME \
--account-name $STORAGE_NAME \
--enable-container-delete-retention true \
--container-delete-retention-days 7
# Verify container soft delete
az storage account blob-service-properties show \
--resource-group $RG_NAME \
--account-name $STORAGE_NAME \
--query "containerDeleteRetentionPolicy"
echo "Container soft delete enabled: 7 days retention"Step 2.3: Enable Blob Versioning β
bash
# Enable blob versioning
az storage account blob-service-properties update \
--resource-group $RG_NAME \
--account-name $STORAGE_NAME \
--enable-versioning true
# Verify versioning
az storage account blob-service-properties show \
--resource-group $RG_NAME \
--account-name $STORAGE_NAME \
--query "isVersioningEnabled"
echo "Blob versioning enabled"Step 2.4: Test Blob Soft Delete β
bash
# Create a test container
az storage container create \
--name test-softdelete \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY
# Upload a test file
echo "Important data - version 1" > testfile.txt
az storage blob upload \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--container-name test-softdelete \
--name testfile.txt \
--file testfile.txt
# Verify file exists
az storage blob list \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--container-name test-softdelete \
--output table
# DELETE the blob
az storage blob delete \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--container-name test-softdelete \
--name testfile.txt
echo "Blob deleted"
# List deleted blobs (soft deleted)
az storage blob list \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--container-name test-softdelete \
--include d \
--output table
# RECOVER the deleted blob
az storage blob undelete \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--container-name test-softdelete \
--name testfile.txt
echo "Blob recovered from soft delete!"
# Verify recovery
az storage blob list \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--container-name test-softdelete \
--output tableStep 2.5: Test Container Soft Delete β
bash
# DELETE the container
az storage container delete \
--name test-softdelete \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY
echo "Container deleted"
# List deleted containers
az storage container list \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--include-deleted \
--query "[?deleted==true]" \
--output table
# RECOVER the deleted container
# Note: You need the version ID of the deleted container
DELETED_VERSION=$(az storage container list \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--include-deleted \
--query "[?name=='test-softdelete'].version" -o tsv)
az storage container restore \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--name test-softdelete \
--deleted-version $DELETED_VERSION
echo "Container recovered from soft delete!"
# Verify container and its blobs are restored
az storage container list \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--output tablePhase 3: Configure Access Tiers β
Step 3.1: Create Containers for Different Tiers β
bash
# Create containers
az storage container create \
--name hot-data \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY
az storage container create \
--name cool-data \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY
az storage container create \
--name archive-data \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY
echo "Containers created"Step 3.2: Upload Blobs with Different Tiers β
bash
# Create test files
echo "Hot tier data - accessed frequently" > hot-data.txt
echo "Cool tier data - accessed monthly" > cool-data.txt
echo "Archive tier data - long-term storage" > archive-data.txt
# Upload with Hot tier (default)
az storage blob upload \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--container-name hot-data \
--name hot-data.txt \
--file hot-data.txt \
--tier Hot
# Upload with Cool tier
az storage blob upload \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--container-name cool-data \
--name cool-data.txt \
--file cool-data.txt \
--tier Cool
# Upload with Archive tier
az storage blob upload \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--container-name archive-data \
--name archive-data.txt \
--file archive-data.txt \
--tier Archive
echo "Blobs uploaded with different access tiers"Step 3.3: Change Blob Access Tier β
bash
# Change blob from Hot to Cool
az storage blob set-tier \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--container-name hot-data \
--name hot-data.txt \
--tier Cool
# Verify tier change
az storage blob show \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--container-name hot-data \
--name hot-data.txt \
--query "properties.blobTier" -o tsvStep 3.4: Rehydrate from Archive β
bash
# Rehydrate archive blob to Hot tier
# Note: This takes hours to complete
az storage blob set-tier \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--container-name archive-data \
--name archive-data.txt \
--tier Hot \
--rehydrate-priority High
# Check rehydration status
az storage blob show \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--container-name archive-data \
--name archive-data.txt \
--query "properties.rehydrationStatus" -o tsv
echo "Rehydration initiated - this takes hours to complete"Phase 4: Lifecycle Management β
Step 4.1: Create Lifecycle Policy β
bash
# Create lifecycle policy JSON
cat > lifecycle-policy.json << 'EOF'
{
"rules": [
{
"enabled": true,
"name": "move-to-cool",
"type": "Lifecycle",
"definition": {
"actions": {
"baseBlob": {
"tierToCool": {
"daysAfterModificationGreaterThan": 30
}
}
},
"filters": {
"blobTypes": ["blockBlob"],
"prefixMatch": ["hot-data/"]
}
}
},
{
"enabled": true,
"name": "move-to-archive",
"type": "Lifecycle",
"definition": {
"actions": {
"baseBlob": {
"tierToArchive": {
"daysAfterModificationGreaterThan": 90
}
}
},
"filters": {
"blobTypes": ["blockBlob"],
"prefixMatch": ["cool-data/"]
}
}
},
{
"enabled": true,
"name": "delete-old-data",
"type": "Lifecycle",
"definition": {
"actions": {
"baseBlob": {
"delete": {
"daysAfterModificationGreaterThan": 365
}
}
},
"filters": {
"blobTypes": ["blockBlob"],
"prefixMatch": ["archive-data/"]
}
}
},
{
"enabled": true,
"name": "delete-old-versions",
"type": "Lifecycle",
"definition": {
"actions": {
"version": {
"delete": {
"daysAfterCreationGreaterThan": 90
}
}
},
"filters": {
"blobTypes": ["blockBlob"]
}
}
},
{
"enabled": true,
"name": "delete-old-snapshots",
"type": "Lifecycle",
"definition": {
"actions": {
"snapshot": {
"delete": {
"daysAfterCreationGreaterThan": 30
}
}
},
"filters": {
"blobTypes": ["blockBlob"]
}
}
}
]
}
EOF
# Apply lifecycle policy
az storage account management-policy create \
--resource-group $RG_NAME \
--account-name $STORAGE_NAME \
--policy @lifecycle-policy.json
echo "Lifecycle management policy applied"Step 4.2: View Lifecycle Policy β
bash
# Get current policy
az storage account management-policy show \
--resource-group $RG_NAME \
--account-name $STORAGE_NAME \
--output jsonPhase 5: Azure File Shares β
Step 5.1: Create File Share β
bash
# Create file share with 100 GB quota
az storage share create \
--name company-files \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--quota 100
# Enable soft delete for file shares
az storage account file-service-properties update \
--resource-group $RG_NAME \
--account-name $STORAGE_NAME \
--enable-delete-retention true \
--delete-retention-days 14
echo "File share created with 14-day soft delete"Step 5.2: Upload Files β
bash
# Create directory
az storage directory create \
--share-name company-files \
--name "documents" \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY
# Upload file
echo "Company document content" > company-doc.txt
az storage file upload \
--share-name company-files \
--source company-doc.txt \
--path "documents/company-doc.txt" \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY
echo "File uploaded to Azure Files"Step 5.3: Generate Mount Commands β
bash
# Generate mount script for Windows
echo "=== Windows Mount Command ==="
echo "cmdkey /add:$STORAGE_NAME.file.core.windows.net /user:AZURE\\$STORAGE_NAME /pass:$STORAGE_KEY"
echo "net use Z: \\\\$STORAGE_NAME.file.core.windows.net\\company-files /persistent:yes"
echo ""
echo "=== Linux Mount Command ==="
echo "sudo mkdir /mnt/company-files"
echo "sudo mount -t cifs //$STORAGE_NAME.file.core.windows.net/company-files /mnt/company-files -o vers=3.0,username=$STORAGE_NAME,password=$STORAGE_KEY,dir_mode=0777,file_mode=0777"
echo ""
echo "=== macOS Mount Command ==="
echo "mkdir ~/company-files"
echo "mount_smbfs //AZURE\\;$STORAGE_NAME:$STORAGE_KEY@$STORAGE_NAME.file.core.windows.net/company-files ~/company-files"Phase 6: Security - SAS Tokens β
Step 6.1: Generate Account SAS β
bash
# Generate account SAS with limited permissions
EXPIRY=$(date -u -d "+1 day" +%Y-%m-%dT%H:%MZ)
ACCOUNT_SAS=$(az storage account generate-sas \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--services b \
--resource-types sco \
--permissions rl \
--expiry $EXPIRY \
--https-only \
-o tsv)
echo "Account SAS Token (valid for 1 day, read-only):"
echo "?$ACCOUNT_SAS"Step 6.2: Generate Container SAS β
bash
# Generate container SAS
CONTAINER_SAS=$(az storage container generate-sas \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--name hot-data \
--permissions rl \
--expiry $EXPIRY \
--https-only \
-o tsv)
echo "Container SAS Token:"
echo "https://$STORAGE_NAME.blob.core.windows.net/hot-data?$CONTAINER_SAS"Step 6.3: Generate Blob SAS β
bash
# Generate blob SAS with download permission
BLOB_SAS=$(az storage blob generate-sas \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--container-name hot-data \
--name hot-data.txt \
--permissions r \
--expiry $EXPIRY \
--https-only \
--full-uri \
-o tsv)
echo "Blob SAS URL (direct download link):"
echo "$BLOB_SAS"
# Test SAS token
curl -o downloaded.txt "$BLOB_SAS"
cat downloaded.txtStep 6.4: Create Stored Access Policy β
bash
# Create stored access policy (allows revoking SAS tokens)
az storage container policy create \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--container-name hot-data \
--name read-policy \
--permissions r \
--expiry $EXPIRY
# Generate SAS from policy (can be revoked by deleting policy)
POLICY_SAS=$(az storage blob generate-sas \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY \
--container-name hot-data \
--name hot-data.txt \
--policy-name read-policy \
--full-uri \
-o tsv)
echo "Policy-based SAS URL:"
echo "$POLICY_SAS"Phase 7: Private Endpoint for Storage β
Step 7.1: Create VNet β
bash
# Create VNet
az network vnet create \
--resource-group $RG_NAME \
--name vnet-storage \
--address-prefix 10.0.0.0/16 \
--subnet-name snet-private \
--subnet-prefix 10.0.1.0/24 \
--location $LOCATION
# Disable private endpoint network policies
az network vnet subnet update \
--resource-group $RG_NAME \
--vnet-name vnet-storage \
--name snet-private \
--disable-private-endpoint-network-policies trueStep 7.2: Create Private Endpoint β
bash
# Get storage account ID
STORAGE_ID=$(az storage account show \
--resource-group $RG_NAME \
--name $STORAGE_NAME \
--query id -o tsv)
# Create private endpoint for blob service
az network private-endpoint create \
--resource-group $RG_NAME \
--name pe-storage-blob \
--vnet-name vnet-storage \
--subnet snet-private \
--private-connection-resource-id $STORAGE_ID \
--group-id blob \
--connection-name StorageBlobConnection \
--location $LOCATION
echo "Private endpoint created for blob storage"Step 7.3: Disable Public Access β
bash
# Disable public blob access
az storage account update \
--resource-group $RG_NAME \
--name $STORAGE_NAME \
--default-action Deny
echo "Public access disabled - storage only accessible via private endpoint"Exam Summary: Key Concepts β
| Topic | Key Points |
|---|---|
| Soft Delete | Blob: 1-365 days, Container: 1-365 days, recoverable |
| Versioning | Automatic version tracking, restore previous versions |
| Access Tiers | Hot > Cool > Cold > Archive (cost vs access) |
| Lifecycle | Automated tier transitions and deletion |
| SAS Types | Account, Service, User Delegation |
| Redundancy | LRS < ZRS < GRS < GZRS (durability) |
Cleanup β
bash
# Cleanup local files
rm -f testfile.txt hot-data.txt cool-data.txt archive-data.txt
rm -f company-doc.txt downloaded.txt lifecycle-policy.json
# Delete resource group
az group delete --name $RG_NAME --yes --no-wait
echo "Cleanup initiated"Key Takeaways β
- Soft Delete: Essential for data protection, now on exam
- Versioning: Complements soft delete for full protection
- Lifecycle Management: Automate tier transitions
- SAS Tokens: Time-limited, scoped access
- Private Endpoints: Secure, private access to storage
- File Shares: SMB-compatible, mountable storage