Azure BCP/DR with Backup & Restore: Part 1 – Resource Group, Storage, and Network Foundation
This multi-part technical blog series walks you through a practical, cost-effective approach to Business Continuity and Disaster Recovery (BCP/DR) in Azure using backup and restore, rather than Azure Site Recovery. The scenario targets restoring all critical infrastructure from Azure East (primary) to Azure West (DR region). Each step is explained with code and rationale, so you can duplicate this in your own environment.
Part 2: Compute, Firewall, and Final Steps
Why This Approach?
- Cost-Effective: Backup and restore avoids the ongoing costs of Azure Site Recovery (ASR) replication.
- Simplicity: You control what is restored and when, with clear, auditable steps.
- RTO/RPO: Recovery Time Objective (RTO) and Recovery Point Objective (RPO) are acceptable for many workloads, as restore times are predictable and backups are recent.
- Flexibility: You can restore to any region, in this case from East to West US.
Step 1: Create the Target Resource Group (Manual)
This is the only step that must be done manually in the Azure Portal or CLI. It creates the DR resource group in the target region (West US).
- Resource Group Name:
dr-rg-gdep-pwus-deployment
- Region: West US
- Tags: Infrastructure, Disaster Recovery
Command:
az group create --name dr-rg-gdep-pwus-deployment --location westus --tags "Purpose=Infrastructure" "Type=DisasterRecovery"
Step 2: Deploy Resource Groups via Bicep
This step uses a Bicep template to deploy any additional resource groups needed for the DR environment.
Command:
az deployment group create --name gdepdr-rg --template-file ./rg/rg_main.bicep --resource-group dr-rg-gdep-pwus-deployment
Bicep Code: rg_main.bicep
param location string = 'westus'
var environmetag = 'Disaster Recovery'
param resourcegroups2create array = [
'dr-rg-gdep-pwus-infrastructure'
'dr-rg-gdep-pwus-vnets'
'dr-rg-gdep-pwus-fortinet'
'dr-rg-gdep-pwus-meraki-sdwan'
]
module rggdepwus './rg.bicep' = [for rggroupname in resourcegroups2create: {
name: '${deployment().name}-${rggroupname}'
scope:subscription()
params: {
name: rggroupname
location: location
tags: {Application:'Infrastructure',Environment: environmetag}
}
}]
Supporting Bicep: rg.bicep
targetScope='subscription'
param name string
param location string
param tags object
resource rggdepdrwus 'Microsoft.Resources/resourceGroups@2022-09-01' = {
name: name
location: location
tags: tags
}
output resourceGroupName string = rggdepdrwus.name
Explanation:
- rg_main.bicep
loops through a list of DR resource group names and deploys each using the rg.bicep
module.
- rg.bicep
creates a resource group at the subscription level with the specified name, location, and tags.
- This ensures all required DR resource groups are created consistently and tagged for easy management.
Step 3: Create Staging Storage Account
Deploy a storage account for staging backups and other DR artifacts.
Command:
az deployment group create --name gdepdr-sa-staging --resource-group dr-rg-gdep-pwus-deployment --template-file ./storage/sa/sa_staging.bicep
Bicep Code: sa_staging.bicep
param location string = resourceGroup().location
var applicationtag = 'Infrastructure'
var environmetag = 'Disaster Recovery'
var infrastructure_rg_name = 'dr-rg-gdep-pwus-infrastructure'
@description('This Storage Account is used as a Staging account to restore VM(s)')
module sagdepdrwusstaging './sa.bicep' = {
name: '${deployment().name}-sa-gdep-pwus-staging'
scope: resourceGroup(infrastructure_rg_name)
params: {
name: 'storegdeppwusstaging'
location: location
tags: { Application: applicationtag, Environment: environmetag }
properties: {
accessTier: 'Hot'
allowBlobPublicAccess: false
allowCrossTenantReplication: false
allowSharedKeyAccess: true
defaultToOAuthAuthentication: false
dnsEndpointType: 'Standard'
encryption: {
keySource: 'Microsoft.Storage'
requireInfrastructureEncryption: false
services: {
blob: {
enabled: true
keyType: 'Account'
}
file: {
enabled: true
keyType: 'Account'
}
}
}
largeFileSharesState: 'Enabled'
minimumTlsVersion: 'TLS1_2'
networkAcls: {
bypass: 'AzureServices'
defaultAction: 'Allow'
ipRules: []
virtualNetworkRules: []
}
publicNetworkAccess: 'Enabled'
supportsHttpsTrafficOnly: true
}
}
}
Supporting Bicep: sa.bicep
param location string
param name string
param tags object
param properties object
resource sagdepdrwus 'Microsoft.Storage/storageAccounts@2023-04-01' = {
name: name
location: location
tags: tags
kind: 'StorageV2'
sku: {
name: 'Standard_LRS'
}
properties: properties
}
resource sagdepdrwusblobservices 'Microsoft.Storage/storageAccounts/blobServices@2023-04-01' = {
parent: sagdepdrwus
name: 'default'
properties: {
cors: {
corsRules: []
}
deleteRetentionPolicy: {
allowPermanentDelete: false
enabled: false
}
}
}
resource sagdepdrwusfileservices 'Microsoft.Storage/storageAccounts/fileServices@2023-04-01' = {
parent: sagdepdrwus
name: 'default'
properties: {
cors: {
corsRules: []
}
protocolSettings: {
smb: {}
}
}
}
resource sagdepdrwusqueueservices 'Microsoft.Storage/storageAccounts/queueServices@2023-04-01' = {
parent: sagdepdrwus
name: 'default'
properties: {
cors: {
corsRules: []
}
}
}
resource sagdepdrwustableservices 'Microsoft.Storage/storageAccounts/tableServices@2023-04-01' = {
parent: sagdepdrwus
name: 'default'
properties: {
cors: {
corsRules: []
}
}
}
Explanation:
- sa_staging.bicep
deploys a storage account for DR staging in the infrastructure resource group, with secure settings and encryption.
- The sa.bicep
module provisions the storage account and all required services (blob, file, queue, table).
- This storage account is used for storing backup files, scripts, and logs during the DR restore process.
Step 4: Create Network Security Groups (NSGs)
Deploy NSGs to secure your DR environment.
Command:
az deployment group create --name gdepdr-nsg --resource-group dr-rg-gdep-pwus-deployment --template-file ./networking/nsg/nsg_main.bicep
Bicep Code: nsg_main.bicep
param location string = resourceGroup().location
var applicationtag = 'Infrastructure'
var environmetag = 'Disaster Recovery'
var infrastructure_rg_name = 'dr-rg-gdep-pwus-infrastructure'
var fortinet_rg_name = 'dr-rg-gdep-pwus-fortinet'
@description('Used By Most of our Subnets')
module nsggdepdrwusdefault './nsg.bicep' = {
name: '${deployment().name}-nsg-gdep-pwus-default'
scope: resourceGroup(infrastructure_rg_name)
params: {
name: 'nsg-gdep-pwus-default'
location: location
tags: { Application: applicationtag, Environment: environmetag }
properties: {}
}
}
@description('Used By Meraki Public Subnet')
module nsggdepdrwusmeraki './nsg.bicep' = {
name: '${deployment().name}-nsg-gdep-pwus-meraki'
scope: resourceGroup(infrastructure_rg_name)
params: {
name: 'nsg-gdep-pwus-meraki'
location: location
tags: { Application: applicationtag, Environment: environmetag }
properties: {
securityRules: [
{
name: 'AllowAnyCustom443Inbound'
type: 'Microsoft.Network/networkSecurityGroups/securityRules'
properties: {
description: 'Per Abhishek this may be required when (if) we enable Cisco Anyconnect'
protocol: '*'
sourcePortRange: '*'
destinationPortRange: '*'
sourceAddressPrefix: '*'
destinationAddressPrefix: '*'
access: 'Allow'
priority: 100
direction: 'Inbound'
sourcePortRanges: []
destinationPortRanges: []
sourceAddressPrefixes: []
destinationAddressPrefixes: []
}
}
{
name: 'AllowAnyCustom443InboundUDP'
type: 'Microsoft.Network/networkSecurityGroups/securityRules'
properties: {
protocol: 'UDP'
sourcePortRange: '443'
destinationPortRange: '443'
sourceAddressPrefix: '*'
destinationAddressPrefix: '*'
access: 'Allow'
priority: 110
direction: 'Inbound'
sourcePortRanges: []
destinationPortRanges: []
sourceAddressPrefixes: []
destinationAddressPrefixes: []
}
}
{
name: 'AllowAny'
type: 'Microsoft.Network/networkSecurityGroups/securityRules'
properties: {
protocol: '*'
sourcePortRange: '*'
destinationPortRange: '*'
sourceAddressPrefix: '*'
destinationAddressPrefix: '*'
access: 'Allow'
priority: 120
direction: 'Outbound'
sourcePortRanges: []
destinationPortRanges: []
sourceAddressPrefixes: []
destinationAddressPrefixes: []
}
}
]
}
}
}
@description('Used By Fortinet Firewall')
module nsggdepdrwusfortinet './nsg.bicep' = {
name: '${deployment().name}-nsg-gdep-pwus-fortinet'
scope: resourceGroup(fortinet_rg_name)
params: {
name: 'nsg-gdep-pwus-fortinet'
location: location
tags: { Application: applicationtag, Environment: environmetag }
properties: {
securityRules: [
{
name: 'AllowAllOutbound'
properties: {
access: 'Allow'
description: 'Allow all out'
destinationAddressPrefix: '*'
destinationAddressPrefixes: []
destinationPortRange: '*'
destinationPortRanges: []
direction: 'Outbound'
priority: 105
protocol: '*'
sourceAddressPrefix: '*'
sourceAddressPrefixes: []
sourcePortRange: '*'
sourcePortRanges: []
}
type: 'Microsoft.Network/networkSecurityGroups/securityRules'
}
{
name: 'AllowAllInbound'
properties: {
access: 'Allow'
destinationAddressPrefix: '*'
destinationAddressPrefixes: []
destinationPortRange: '*'
destinationPortRanges: []
direction: 'Inbound'
priority: 110
protocol: '*'
sourceAddressPrefix: '*'
sourceAddressPrefixes: []
sourcePortRange: '*'
sourcePortRanges: []
}
type: 'Microsoft.Network/networkSecurityGroups/securityRules'
}
]
}
}
}
Supporting Bicep: nsg.bicep
param location string
param name string
param tags object
param properties object
resource nsggdepdrwus 'Microsoft.Network/networkSecurityGroups@2023-09-01'={
name:name
location:location
tags:tags
properties:properties
}
Explanation:
- nsg_main.bicep
deploys three NSGs: default, Meraki, and Fortinet, each with appropriate rules for their subnet roles.
- The nsg.bicep
module provisions the NSG with the specified rules and tags.
- NSGs are critical for controlling traffic flow and securing your DR network.
Step 5: Create Route Tables
Deploy User Defined Route (UDR) tables for custom routing.
Command:
az deployment group create --name gdepdr-rt --resource-group dr-rg-gdep-pwus-deployment --template-file ./networking/udr/udr_main.bicep
Bicep Code: udr_main.bicep
param location string = resourceGroup().location
var applicationtag = 'Infrastructure'
var environmetag = 'Disaster Recovery'
//var infrastructure_rg_name = 'dr-rg-gdep-pwus-infrastructure'
var fortinet_rg_name = 'dr-rg-gdep-pwus-fortinet'
var meraki_sdwan_rg_name = 'dr-rg-gdep-pwus-meraki-sdwan'
@description('Used to route traffic intended to go to On Premise network from Azure Hub')
module udrgdepdrwushub2onprem './udr.bicep' = {
name: '${deployment().name}-route-gdep-pwus-azurehub-onprem'
scope: resourceGroup(fortinet_rg_name)
params: {
name: 'route-gdep-pwus-azurehub-onprem'
location: location
tags: { Application: applicationtag, Environment: environmetag }
properties: {
disableBgpRoutePropagation: false
routes: [ ... ]
}
}
}
// ...additional modules for all required UDRs (see full code in repo)...
Supporting Bicep: udr.bicep
param location string
param name string
param tags object
param properties object
resource rtgdepdrwus 'Microsoft.Network/routeTables@2023-09-01'={
name:name
location:location
tags:tags
properties:properties
}
Explanation:
- udr_main.bicep
deploys all required route tables for the DR environment, including routes for on-premises, spokes, and SDWAN.
- The udr.bicep
module provisions each route table with the specified routes and settings.
- UDRs are essential for custom traffic flow and integration with firewalls and VPNs.
Step 6: Create Virtual Networks and Subnets
Deploy VNETs, subnets, and associate them with NSGs and UDRs.
Command:
az deployment group create --name gdepdr-vnet --resource-group dr-rg-gdep-pwus-deployment --template-file ./networking/vnet/vnet_main.bicep
Bicep Code: vnet_main.bicep
param location string = resourceGroup().location
var applicationtag = 'Infrastructure'
var environmetag = 'Disaster Recovery'
/*
var infrastructure_rg_name = 'dr-rg-gdep-pwus-infrastructure'
var fortinet_rg_name = 'dr-rg-gdep-pwus-fortinet'
var meraki_sdwan_rg_name = 'dr-rg-gdep-pwus-meraki-sdwan'
*/
var vnet_rg_name = 'dr-rg-gdep-pwus-vnets'
@description('Hub Virtual Network with NVA namely Fortinet Firewall')
module vnetgdepdrwusfortinet './vnet.bicep' = {
name: '${deployment().name}-vnet-gdep-pwus-fortinet'
scope: resourceGroup(vnet_rg_name)
params: {
name: 'vnet-gdep-pwus-fortinet'
location: location
tags: { Application: applicationtag, Environment: environmetag }
properties: {
addressSpace: {
addressPrefixes: [ ... ]
}
dhcpOptions: {
dnsServers: [ ... ]
}
enableDdosProtection: false
subnets: [ ... ]
}
}
}
// ...additional modules for management and production spokes, and VNET peering (see full code in repo)...
Supporting Bicep: vnet.bicep
param location string
param name string
param tags object
param properties object
resource vnetgdepdrwus 'Microsoft.Network/virtualNetworks@2023-09-01'={
name:name
location:location
tags:tags
properties:properties
}
output virtualnetworkname string = vnetgdepdrwus.name
Explanation:
- vnet_main.bicep
provisions all required virtual networks and subnets for the DR environment, including hub, management, and production spokes.
- Subnets are associated with NSGs and UDRs as needed, and VNET peering is configured for connectivity.
- The vnet.bicep
module provisions each VNET with the specified address spaces, subnets, and settings.