Defines an Azure Recovery Services VM workload backup policy β the schedule and multi-tier retention that a Recovery Services vault applies to SQL-in-a-VM and SAP HANA database workloads. Targets
hashicorp/azurerm ~> 4.0.
- ποΈ Protects database workloads running inside a VM β Azure SQL Server (
SQLDataBase) or SAP HANA (SAPHanaDatabase). - π§± Carries one or more protection sub-policies, one per
policy_type(Full,Differential,Incremental,Log). - β±οΈ Expresses scheduled backups (
Daily/Weekly) and log backups (frequency_in_minutes) in a single policy. - π Layers multi-tier retention β daily, weekly, monthly, and yearly for full backups; simple retention for logs.
- π Interprets every schedule time against a required
time_zone, with optional backup compression.
π‘ Why it matters: A workload backup policy is the contract between a Recovery Services vault and the databases it protects. Getting the schedule and retention tiers right at authoring time β and validating the enum surface before any API call β keeps recovery-point objectives predictable and avoids apply-time rejections against the live vault.
If this module saves you time, please consider supporting it:
- β Star the repository on GitHub.
- πΌ Connect on LinkedIn: linkedin.com/in/microsoftexpert
- β Buy me a coffee: buymeacoffee.com/microsoftexpert
graph TD
me["terraform-azurerm-backup-policy-vm-workload"]
target["azurerm_backup_policy_vm_workload"]
vault["azurerm_recovery_services_vault"]
item["protected workload item"]
me -->|"defines"| target
vault -->|"holds policy"| target
target -->|"applied to"| item
classDef me fill:#0078D4,color:#ffffff,stroke:#004578;
classDef target fill:#004578,color:#ffffff,stroke:#002a44;
classDef ext fill:#f2f2f2,color:#000000,stroke:#cccccc;
class me me;
class target target;
class vault,item ext;
This module owns the policy definition only. The Recovery Services vault that holds the policy is created by a sibling module and referenced by name. The protected workload items (databases enrolled into backup) are wired by sibling backup-protection resources that reference this policy's id.
graph LR
in_wl["workload_type"]
in_set["settings (time_zone, compression)"]
in_pp["protection_policy[] (backup + retention)"]
res["azurerm_backup_policy_vm_workload.this"]
out_id["id"]
out_rpo["has_log_backups + log_backup_interval_minutes"]
out_disc["retention and schedule discard reporters"]
in_wl -->|"input"| res
in_set -->|"input"| res
in_pp -->|"input"| res
res -->|"output"| out_id
res -->|"output"| out_rpo
res -->|"output"| out_disc
classDef me fill:#0078D4,color:#ffffff,stroke:#004578;
class res me;
Resource inventory
| Resource | Role | Cardinality |
|---|---|---|
azurerm_backup_policy_vm_workload.this |
The keystone workload backup policy | single |
The keystone renders one settings block and a dynamic "protection_policy" block over the protection_policy list; each sub-policy renders its own backup block and only the retention blocks it supplies (retention_daily, retention_weekly, retention_monthly, retention_yearly, simple_retention). An optional timeouts block is rendered when supplied.
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/azurerm |
~> 4.0 |
| Provider block | None in this module β the caller configures provider "azurerm", including the mandatory features {} block, plus auth. |
Schema notes that bite
- π
name,resource_group_name,recovery_vault_name, andworkload_typeare force-new β changing any of them replaces the policy. - π΄ Each
policy_typereads a specific set of retention blocks, and SILENTLY DISCARDS the rest. The long-term blocks βretention_daily,retention_weekly,retention_monthly,retention_yearlyβ are read only whenpolicy_typeisFull;simple_retentionis read only when it is not. Supplying the wrong one is not rejected: it is accepted, stored in state, and dropped when the request is built, so recovery points expire on a schedule nobody chose. Theretention_discarded_for_keysoutput reports exactly this. - π΄ The same silent discard applies inside
backup.frequency_in_minutesis read only for aLogsub-policy;frequency,timeandweekdaysare read only for the others. ALogsub-policy givenfrequency = "Weekly"runs on its log interval and ignores the word. Reported byschedule_fields_discarded_for_keys. - π΄ Twelve cross-field rules are enforced by the provider only at apply, inside the function that builds the request β including
retention_dailymust be set whenfrequencyisDaily(which applies to everypolicy_type, while the block is only used forFull), and Incremental is unsupported whenworkload_typeisSQLDataBase. This module mirrors all twelve asvalidation {}blocks, so they fail atterraform validateinstead: offline, with no credentials and no half-created policy. - β±οΈ
backup.frequencyisDailyorWeeklyfor scheduled backups; log-style sub-policies instead setbackup.frequency_in_minutes, one of15,30,60,120,240,480,720,1440. - π
settings.time_zoneis required and governs how every scheduletimeis interpreted (e.g."UTC"). - ποΈ
settings.compression_enableddefaults tofalsewhen omitted.
Least-privilege at the Recovery Services vault scope:
Microsoft.RecoveryServices/vaults/backupPolicies/writeMicrosoft.RecoveryServices/vaults/backupPolicies/readMicrosoft.RecoveryServices/vaults/backupPolicies/delete
The built-in Backup Contributor role on the vault covers these.
- An existing Recovery Services vault.
- The
Microsoft.RecoveryServicesresource provider registered on the subscription. - The caller's
provider "azurerm"block configured withfeatures {}and a valid authentication method.
terraform-azurerm-backup-policy-vm-workload/
βββ providers.tf # terraform{} block: required_version + pinned azurerm (no provider block)
βββ variables.tf # Typed inputs: name, vault refs, workload_type, settings, protection_policy, timeouts
βββ main.tf # Keystone azurerm_backup_policy_vm_workload.this + dynamic protection_policy
βββ outputs.tf # id first, then the regime (policy types, RPO, retention), the discard reporters, and the constant-fact outputs
βββ README.md # This document
βββ SCOPE.md # Cross-module contract
βββ LICENSE # MIT
βββ .gitignore # Canonical library ignore set
βΉοΈ The caller configures the provider, authentication, and the mandatory
features {}block. This module never declares aproviderblock.
provider "azurerm" {
features {}
}
module "sql_vm_backup_policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-backup-policy-vm-workload.git?ref=v1.0.0"
name = "sqlvm-daily-full"
resource_group_name = "rg-backup"
recovery_vault_name = "rsv-prod-eastus"
workload_type = "SQLDataBase"
settings = {
time_zone = "UTC"
}
protection_policy = [
{
policy_type = "Full"
backup = { frequency = "Daily", time = "23:00" }
retention_daily = { count = 30 }
},
]
}Consumes
| Input | Type | Source module |
|---|---|---|
resource_group_name |
string |
terraform-azurerm-resource-group |
recovery_vault_name |
string |
terraform-azurerm-recovery-services-vault |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
Resource ID of the workload backup policy | backup-protection items that reference this policy |
name |
Policy name | references |
recovery_vault_name / resource_group_name / workload_type |
Placement and workload | prerequisite review |
policy_types |
Which sub-policy types are defined | change review |
has_log_backups |
Whether point-in-time recovery exists | recovery-capability review |
log_backup_interval_minutes |
The effective RPO in minutes | data-loss tolerance review |
full_backup_schedule |
The Full sub-policy's frequency, time and weekdays | change windows |
longest_retention_days |
Longest retention, approximate days | records-retention review |
retention_discarded_for_keys |
Retention blocks the provider will DISCARD | silent-no-op review |
schedule_fields_discarded_for_keys |
Schedule fields the provider will DISCARD | silent-no-op review |
duplicate_policy_types |
Reported, not refused | authoring review |
compression_enabled / time_zone |
Settings in effect | capacity and scheduling review |
protects_nothing_until_assigned |
Constant: inert until a protected item references it | composition design |
changing_retention_applies_to_existing_recovery_points |
Constant: shortening retention EXPIRES stored points | data-destruction gates |
policy_cannot_be_destroyed_while_items_are_assigned |
Constant: destroy fails rather than unprotecting | change planning |
backups_survive_policy_deletion |
Constant: destroying this reclaims no storage | cost expectations |
this_module_verifies_no_prerequisite |
Constant: names are not checked against reality | prerequisite review |
1 Β· Minimal SQL Full policy (daily retention)
module "sql_full_daily" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-backup-policy-vm-workload.git?ref=v1.0.0"
name = "sqlvm-full-daily"
resource_group_name = "rg-backup"
recovery_vault_name = "rsv-prod-eastus"
workload_type = "SQLDataBase"
settings = { time_zone = "UTC" }
protection_policy = [
{
policy_type = "Full"
backup = { frequency = "Daily", time = "22:00" }
retention_daily = { count = 30 }
},
]
}π‘ The smallest useful policy: a nightly full backup kept for 30 days.
2 Β· SAP HANA Full policy
module "hana_full_daily" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-backup-policy-vm-workload.git?ref=v1.0.0"
name = "hana-full-daily"
resource_group_name = "rg-backup"
recovery_vault_name = "rsv-prod-eastus"
workload_type = "SAPHanaDatabase"
settings = { time_zone = "UTC" }
protection_policy = [
{
policy_type = "Full"
backup = { frequency = "Daily", time = "01:30" }
retention_daily = { count = 14 }
},
]
}βΉοΈ Only
workload_typechanges between a SQL and a SAP HANA policy shape; both accept the same protection-policy grammar.
3 Β· Weekly Full with weekly + monthly + yearly retention
module "sql_weekly_tiered" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-backup-policy-vm-workload.git?ref=v1.0.0"
name = "sqlvm-weekly-tiered"
resource_group_name = "rg-backup"
recovery_vault_name = "rsv-prod-eastus"
workload_type = "SQLDataBase"
settings = { time_zone = "UTC" }
protection_policy = [
{
policy_type = "Full"
backup = { frequency = "Weekly", time = "23:00", weekdays = ["Sunday"] }
retention_weekly = { count = 12, weekdays = ["Sunday"] }
retention_monthly = {
count = 12
format_type = "Weekly"
weeks = ["First"]
weekdays = ["Sunday"]
}
retention_yearly = {
count = 5
format_type = "Weekly"
months = ["January"]
weeks = ["First"]
weekdays = ["Sunday"]
}
},
]
}π‘ A weekly full backup with three long-term retention tiers layered on top β a common regulated-data retention shape.
4 Β· Full + Log combined in one policy
module "sql_full_plus_log" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-backup-policy-vm-workload.git?ref=v1.0.0"
name = "sqlvm-full-log"
resource_group_name = "rg-backup"
recovery_vault_name = "rsv-prod-eastus"
workload_type = "SQLDataBase"
settings = { time_zone = "UTC" }
protection_policy = [
{
policy_type = "Full"
backup = { frequency = "Daily", time = "23:00" }
retention_daily = { count = 30 }
},
{
policy_type = "Log"
backup = { frequency_in_minutes = 120 }
simple_retention = { count = 15 }
},
]
}π‘ Full backups anchor the recovery points; log backups every 120 minutes tighten the recovery-point objective for point-in-time restore.
5 Β· Differential sub-policy
module "sql_differential" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-backup-policy-vm-workload.git?ref=v1.0.0"
name = "sqlvm-full-diff"
resource_group_name = "rg-backup"
recovery_vault_name = "rsv-prod-eastus"
workload_type = "SQLDataBase"
settings = { time_zone = "UTC" }
protection_policy = [
{
policy_type = "Full"
backup = { frequency = "Weekly", time = "23:00", weekdays = ["Sunday"] }
retention_weekly = { count = 8, weekdays = ["Sunday"] }
},
{
policy_type = "Differential"
backup = { frequency = "Weekly", time = "22:00", weekdays = ["Wednesday"] }
simple_retention = { count = 14 }
},
]
}βΉοΈ A differential sub-policy pairs a mid-week backup with a
simple_retentionwindow between full backups.
6 Β· Incremental sub-policy (SAP HANA)
module "hana_incremental" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-backup-policy-vm-workload.git?ref=v1.0.0"
name = "hana-full-incr"
resource_group_name = "rg-backup"
recovery_vault_name = "rsv-prod-eastus"
workload_type = "SAPHanaDatabase"
settings = { time_zone = "UTC" }
protection_policy = [
{
policy_type = "Full"
backup = { frequency = "Weekly", time = "01:00", weekdays = ["Saturday"] }
retention_weekly = { count = 6, weekdays = ["Saturday"] }
},
{
policy_type = "Incremental"
backup = { frequency = "Daily", time = "01:00" }
retention_daily = { count = 7 } # required by the Daily rule β and then discarded
simple_retention = { count = 7 } # this is the retention that actually applies
},
]
}π‘ Incremental backups reduce data movement between full backups; here they run nightly with a one-week simple retention. Incremental is available for
SAPHanaDatabaseonly β the provider rejects it outright whenworkload_typeisSQLDataBase, and this module refuses that combination atterraform validate.π΄ Note the
retention_dailyblock that does nothing. The provider requiresretention_dailywheneverbackup.frequencyisDaily, for everypolicy_typeβ but it only reads the long-term retention blocks whenpolicy_typeisFull. So on this Incremental sub-policy the block is mandatory and then thrown away, andsimple_retentionis what actually governs expiry. Omitting it fails at apply with "retention_dailymust be set whenbackup.0.frequencyisDaily"; theretention_discarded_for_keysoutput reports the discard.
7 Β· Log backups every 15 minutes
module "sql_tight_rpo" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-backup-policy-vm-workload.git?ref=v1.0.0"
name = "sqlvm-tight-rpo"
resource_group_name = "rg-backup"
recovery_vault_name = "rsv-prod-eastus"
workload_type = "SQLDataBase"
settings = { time_zone = "UTC" }
protection_policy = [
{
policy_type = "Full"
backup = { frequency = "Daily", time = "23:30" }
retention_daily = { count = 35 }
},
{
policy_type = "Log"
backup = { frequency_in_minutes = 15 }
simple_retention = { count = 30 }
},
]
}
β οΈ frequency_in_minutesmust be one of15, 30, 60, 120, 240, 480, 720, 1440. Any other value fails validation at parse time.
8 Β· Backup compression enabled
module "sql_compressed" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-backup-policy-vm-workload.git?ref=v1.0.0"
name = "sqlvm-compressed"
resource_group_name = "rg-backup"
recovery_vault_name = "rsv-prod-eastus"
workload_type = "SQLDataBase"
settings = {
time_zone = "UTC"
compression_enabled = true
}
protection_policy = [
{
policy_type = "Full"
backup = { frequency = "Daily", time = "23:00" }
retention_daily = { count = 30 }
},
]
}π
compression_enableddefaults tofalse; set it totrueexplicitly to opt in to backup compression.
9 Β· Custom time zone
module "sql_pacific" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-backup-policy-vm-workload.git?ref=v1.0.0"
name = "sqlvm-pacific"
resource_group_name = "rg-backup"
recovery_vault_name = "rsv-prod-westus2"
workload_type = "SQLDataBase"
settings = { time_zone = "Pacific Standard Time" }
protection_policy = [
{
policy_type = "Full"
backup = { frequency = "Daily", time = "02:00" }
retention_daily = { count = 30 }
},
]
}βΉοΈ Every schedule
timeis interpreted insettings.time_zone. A02:00full backup here runs at 02:00 Pacific, not UTC.
10 Β· Yearly retention, format_type Weekly
module "sql_yearly_weekly" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-backup-policy-vm-workload.git?ref=v1.0.0"
name = "sqlvm-yearly-weekly"
resource_group_name = "rg-backup"
recovery_vault_name = "rsv-prod-eastus"
workload_type = "SQLDataBase"
settings = { time_zone = "UTC" }
protection_policy = [
{
policy_type = "Full"
backup = { frequency = "Weekly", time = "23:00", weekdays = ["Sunday"] }
retention_weekly = { count = 12, weekdays = ["Sunday"] }
retention_yearly = {
count = 7
format_type = "Weekly"
months = ["January"]
weeks = ["First"]
weekdays = ["Sunday"]
}
},
]
}π‘ With
format_type = "Weekly", yearly retention pins to aweeks+weekdayscombination (e.g. the first Sunday of January).
11 Β· Yearly retention, format_type Daily
module "sql_yearly_daily" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-backup-policy-vm-workload.git?ref=v1.0.0"
name = "sqlvm-yearly-daily"
resource_group_name = "rg-backup"
recovery_vault_name = "rsv-prod-eastus"
workload_type = "SQLDataBase"
settings = { time_zone = "UTC" }
protection_policy = [
{
policy_type = "Full"
backup = { frequency = "Daily", time = "23:00" }
retention_daily = { count = 30 }
retention_yearly = {
count = 10
format_type = "Daily"
months = ["January"]
monthdays = [1]
}
},
]
}βΉοΈ With
format_type = "Daily", yearly retention pins to specificmonthdays(e.g. January 1) rather than a weeks/weekdays pattern.
12 Β· Monthly retention, format_type Daily
module "sql_monthly_daily" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-backup-policy-vm-workload.git?ref=v1.0.0"
name = "sqlvm-monthly-daily"
resource_group_name = "rg-backup"
recovery_vault_name = "rsv-prod-eastus"
workload_type = "SQLDataBase"
settings = { time_zone = "UTC" }
protection_policy = [
{
policy_type = "Full"
backup = { frequency = "Daily", time = "23:00" }
retention_daily = { count = 30 }
retention_monthly = {
count = 12
format_type = "Daily"
monthdays = [1, 15]
}
},
]
}π‘ A
Dailymonthly format retains backups from the 1st and 15th of each month for a year.
13 Β· Full four-tier retention (daily + weekly + monthly + yearly)
module "sql_all_tiers" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-backup-policy-vm-workload.git?ref=v1.0.0"
name = "sqlvm-all-tiers"
resource_group_name = "rg-backup"
recovery_vault_name = "rsv-prod-eastus"
workload_type = "SQLDataBase"
settings = { time_zone = "UTC" }
protection_policy = [
{
policy_type = "Full"
backup = { frequency = "Daily", time = "23:00" }
retention_daily = { count = 30 }
retention_weekly = {
count = 12
weekdays = ["Sunday"]
}
retention_monthly = {
count = 12
format_type = "Weekly"
weeks = ["First"]
weekdays = ["Sunday"]
}
retention_yearly = {
count = 7
format_type = "Weekly"
months = ["January"]
weeks = ["First"]
weekdays = ["Sunday"]
}
},
]
}π The full grandfather-father-son shape in one sub-policy: 30 dailies, 12 weeklies, 12 monthlies, and 7 yearlies.
14 Β· Custom timeouts
module "sql_with_timeouts" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-backup-policy-vm-workload.git?ref=v1.0.0"
name = "sqlvm-timeouts"
resource_group_name = "rg-backup"
recovery_vault_name = "rsv-prod-eastus"
workload_type = "SQLDataBase"
settings = { time_zone = "UTC" }
protection_policy = [
{
policy_type = "Full"
backup = { frequency = "Daily", time = "23:00" }
retention_daily = { count = 30 }
},
]
timeouts = {
create = "30m"
delete = "30m"
}
}βΉοΈ
timeoutsaccepts Go duration strings. Leave itnull(the default) for provider defaults.
15 Β· ποΈ End-to-end composition (resource group + vault β policy)
provider "azurerm" {
features {}
}
module "backup_rg" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
name = "rg-backup-prod"
location = "eastus"
}
module "recovery_vault" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-recovery-services-vault.git?ref=v1.0.0"
name = "rsv-prod-eastus"
resource_group_name = module.backup_rg.name
location = "eastus"
}
module "sql_vm_backup_policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-backup-policy-vm-workload.git?ref=v1.0.0"
name = "sqlvm-prod-policy"
resource_group_name = module.backup_rg.name
recovery_vault_name = module.recovery_vault.name
workload_type = "SQLDataBase"
settings = { time_zone = "UTC" }
protection_policy = [
{
policy_type = "Full"
backup = { frequency = "Weekly", time = "23:00", weekdays = ["Sunday"] }
retention_weekly = { count = 12, weekdays = ["Sunday"] }
retention_monthly = {
count = 12
format_type = "Weekly"
weeks = ["First"]
weekdays = ["Sunday"]
}
},
{
policy_type = "Log"
backup = { frequency_in_minutes = 60 }
simple_retention = { count = 15 }
},
]
}
output "backup_policy_id" {
value = module.sql_vm_backup_policy.id
}π‘ The resource-group and recovery-services-vault modules feed their
nameoutputs straight into this policy'sresource_group_nameandrecovery_vault_nameinputs, keeping ownership boundaries clean.
Required
| Name | Type | Description |
|---|---|---|
name |
string |
Policy name. Force-new. |
resource_group_name |
string |
Resource group holding the vault. Force-new. |
recovery_vault_name |
string |
Recovery Services vault name. Force-new. |
workload_type |
string |
SQLDataBase or SAPHanaDatabase. Force-new. |
settings |
object |
Policy-wide settings (time_zone required). |
protection_policy |
list(object) |
One or more protection sub-policies. |
Optional
| Name | Type | Default | Description |
|---|---|---|---|
timeouts |
object |
null |
Per-operation timeouts (Go duration strings). |
Full object() schemas
variable "settings" {
type = object({
time_zone = string
compression_enabled = optional(bool) # defaults to false in main.tf
})
}
variable "protection_policy" {
type = list(object({
policy_type = string # Full | Differential | Incremental | Log
backup = object({
frequency = optional(string) # Daily | Weekly
frequency_in_minutes = optional(number) # 15|30|60|120|240|480|720|1440
time = optional(string)
weekdays = optional(list(string))
})
retention_daily = optional(object({
count = number
}))
retention_weekly = optional(object({
count = number
weekdays = list(string)
}))
retention_monthly = optional(object({
count = number
format_type = string
monthdays = optional(list(number))
weekdays = optional(list(string))
weeks = optional(list(string))
}))
retention_yearly = optional(object({
count = number
format_type = string
months = list(string)
monthdays = optional(list(number))
weekdays = optional(list(string))
weeks = optional(list(string))
}))
simple_retention = optional(object({
count = number
}))
}))
# Validations: >= 1 sub-policy; policy_type in {Full,Differential,Incremental,Log};
# backup.frequency in {Daily,Weekly}; frequency_in_minutes in {15,30,60,120,240,480,720,1440}.
}
variable "timeouts" {
type = object({
create = optional(string)
read = optional(string)
update = optional(string)
delete = optional(string)
})
default = null
}| Output | Description | Notes |
|---|---|---|
id |
Resource ID of the VM workload backup policy | Emitted first |
name |
Policy name | β |
recovery_vault_name |
The vault holding the policy | Echoed - the provider exposes no vault attribute |
resource_group_name |
The vault's group | Not the group of what is backed up |
workload_type |
SQLDataBase or SAPHanaDatabase |
Constrains which sub-policy types are legal |
policy_types |
Sorted sub-policy types | Sorted so reordering shows no change |
has_log_backups |
A Log sub-policy exists |
π΄ False means no point-in-time recovery between full backups |
log_backup_interval_minutes |
The Log interval, or null |
π΄ This is the effective RPO. A closed set, not a range |
full_backup_schedule |
Frequency, time, weekdays of the Full sub-policy | null if no Full sub-policy β worth a second look |
longest_retention_days |
Longest retention, approximate days | |
retention_discarded_for_keys |
Retention that will be discarded | π΄ Long-term retention is read only for Full; simple_retention only for the rest |
schedule_fields_discarded_for_keys |
Schedule fields that will be discarded | π΄ frequency_in_minutes only for Log; frequency/time/weekdays only for the rest |
duplicate_policy_types |
A policy_type declared twice |
|
compression_enabled |
SQL backup compression | Cost lands on the protected server, not the vault |
time_zone |
The schedule's time zone | Windows name (Pacific Standard Time), not IANA |
protects_nothing_until_assigned |
Always true |
Inert until a protected item references id |
changing_retention_applies_to_existing_recovery_points |
Always true |
π΄ Shortening retention expires recovery points already stored |
policy_cannot_be_destroyed_while_items_are_assigned |
Always true |
Destroy fails rather than silently unprotecting β including on a rename |
backups_survive_policy_deletion |
Always true |
Destroying this reclaims no storage and is not a cost control |
this_module_verifies_no_prerequisite |
Always true |
Vault name, resource group and time zone are unchecked strings |
- Force-new identity.
name,resource_group_name,recovery_vault_name, andworkload_typeall force replacement. Rename or re-target a policy only with the understanding that the old policy is destroyed and a new one created β detach protected items first. - Retention grammar is per
policy_type. The keystone renders each retention block only when the sub-policy supplies it (dynamicblocks guarded withtry(..., null) != null). AFullsub-policy typically carriesretention_daily/retention_weekly/retention_monthly/retention_yearly; aLogsub-policy carriessimple_retention. Attaching a retention block apolicy_typedoes not accept passes the type checker but is rejected at apply by the provider. frequencyvsfrequency_in_minutes. Scheduled full/differential/incremental backups setbackup.frequency(Daily/Weekly) with atimeand, for weekly,weekdays. Log backups instead setbackup.frequency_in_minutesfrom the fixed enum. Both fields are optional in the type so each sub-policy uses only the one it needs.time_zonesemantics.settings.time_zoneis required and governs interpretation of every scheduletimeacross all sub-policies. Choosing a local zone (for examplePacific Standard Time) means the printedtimeis local, not UTC.compression_enableddefaults off. When omitted fromsettings,main.tfrenderscompression_enabled = false.features {}dependence. The module declares no provider block. Initialization depends entirely on the caller'sprovider "azurerm" { features {} }; a missingfeatures {}block in the root module is the usual cause of an init failure in isolation.
- Fail at parse time, not apply time. The value sets for
workload_type(SQLDataBase/SAPHanaDatabase),policy_type(Full/Differential/Incremental/Log),backup.frequency(Daily/Weekly), andbackup.frequency_in_minutes(15β¦1440) are enforced withvalidation {}blocks, so a malformed policy is rejected before any Azure API call. - At least one sub-policy.
protection_policyrequires a minimum of one entry. - Compression is opt-in.
compression_enableddefaults tofalse; the caller typestrueto enable it. - No tags. This resource type does not support
tags, so the universal tail istimeoutsonly.
terraform init -backend=false
terraform validate
terraform fmt -check- Pin the module with
?ref=v1.0.0β never a branch. - This is plan-only in authoring:
terraform applyis run by a human from CI, never as part of module development.
The offline proof gate exercises everything that does not require a live subscription:
| Check | Command | What it proves |
|---|---|---|
| Type + schema | terraform validate |
The object() schemas, optional(...) defaults, and every validation {} (enums, minimum sub-policy count) hold. |
| Formatting | terraform fmt -check |
Canonical HCL formatting. |
| Init (no backend) | terraform init -backend=false |
Provider constraints resolve against ~> 4.0. |
Only terraform plan / apply against a real vault exercises the provider-side rule that a given policy_type accepts only certain retention blocks β that surface is validated in CI, not at author time.
$ terraform output
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-backup/providers/Microsoft.RecoveryServices/vaults/rsv-prod-eastus/backupPolicies/sqlvm-prod-policy"
name = "sqlvm-prod-policy"| Symptom | Cause | Fix |
|---|---|---|
| Apply rejects a retention block | A retention block was supplied that the policy_type does not accept (e.g. simple_retention on a Full policy, or retention_monthly on a Log policy). |
Match retention blocks to the policy_type: full-style retention on Full; simple_retention on Log. |
frequency_in_minutes must be one of... at plan |
backup.frequency_in_minutes set to a value outside the enum. |
Use one of 15, 30, 60, 120, 240, 480, 720, 1440. |
time_zone error / times off by hours |
settings.time_zone omitted or set to an unexpected zone. |
Set settings.time_zone explicitly (e.g. "UTC"); confirm each time is meant in that zone. |
workload_type must be... at plan |
workload_type not one of the two legal values. |
Use SQLDataBase or SAPHanaDatabase. |
| Plan wants to replace the policy | A force-new field (name, resource_group_name, recovery_vault_name, workload_type) changed. |
Revert the change, or accept replacement and detach protected items first. |
| Provider fails to initialize in isolation | The caller's root module is missing provider "azurerm" { features {} }. |
Add the features {} block to the root module. |
At least one protection_policy is required. |
protection_policy passed as an empty list. |
Supply at least one sub-policy. |
- Terraform Registry β
azurerm_backup_policy_vm_workload - Terraform Registry β
azurerm_recovery_services_vault - Microsoft Learn β Azure Backup for SQL Server in Azure VMs
- Microsoft Learn β Azure Backup for SAP HANA databases
- Sibling modules β
terraform-azurerm-resource-group,terraform-azurerm-recovery-services-vault - This module's
SCOPE.md
π "Infrastructure as Code should be standardized, consistent, and secure."