Provisions one Azure NetApp Files backup vault (
azurerm_netapp_backup_vault) — the destination volume backups are written to. Targetshashicorp/azurerm ~> 4.0.
- 🗃️ Provisions one backup vault — the destination NetApp Files volume backups land in — as a keystone resource named
this. - 🧭 Documents the resource's absent surface as carefully as its present one: there is no redundancy, retention, or immutability setting here.
- 🔗 Retention lives on the backup policy; a vault with no policy pointed at it holds nothing. The two are separate resources on purpose.
⚠️ Destroying the vault FAILS while it still holds backups — unless the caller opts in withfeatures { netapp { delete_backups_on_backup_vault_destroy = true } }, in which case every backup is deleted irreversibly. There is no soft-delete and no recycle bin either way.- 🔄 A replacement is not a migration: rename or move the vault and existing backups do not follow.
- 📤 Emits
id(volumes consume the vault by ID, unlike the account and pool which go by name) andlocation, so a composition can check the same-region requirement nothing else enforces.
💡 Why it matters: This is a deliberately small module because the resource is small — and knowing that is the point. People come to a "backup vault" looking for redundancy options, a retention lock, or an immutability window, and none of those exist on this resource. Retention is the policy's job; everything else is service-managed. The most useful thing this README does is stop you hunting for a control that isn't there.
If this module saves you time, please consider supporting its continued development:
- ⭐ Star the repository on GitHub.
- 🤝 Connect on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
flowchart LR
rg["terraform-azurerm-resource-group"]
kv["terraform-azurerm-key-vault"]
vnet["terraform-azurerm-virtual-network: subnet delegated to Microsoft.NetApp/volumes"]
acct["terraform-azurerm-netapp-account"]
enc["terraform-azurerm-netapp-account-encryption: who holds the key"]
pool["terraform-azurerm-netapp-pool: the BILLED capacity"]
vol["terraform-azurerm-netapp-volume: what clients mount"]
snap["terraform-azurerm-netapp-snapshot: one on-demand, in the pool"]
spol["terraform-azurerm-netapp-snapshot-policy: the schedule"]
vault["terraform-azurerm-netapp-backup-vault: the destination"]
bpol["terraform-azurerm-netapp-backup-policy: the retention"]
vghana["terraform-azurerm-netapp-volume-group-sap-hana"]
vgora["terraform-azurerm-netapp-volume-group-oracle"]
rg -->|"resource_group_name, location"| acct
kv -->|"encryption_key URI"| enc
acct -->|"id, BY ID"| enc
acct -->|"account_name, BY NAME"| pool
acct -->|"account_name, BY NAME"| vault
acct -->|"account_name, BY NAME"| bpol
acct -->|"account_name, BY NAME"| spol
acct -->|"account_name, BY NAME"| vghana
acct -->|"account_name, BY NAME"| vgora
pool -->|"pool_name, BY NAME"| vol
pool -->|"capacity_pool_id, BY ID"| vghana
pool -->|"capacity_pool_id, BY ID"| vgora
vnet -->|"subnet_id, must be delegated"| vol
vol -->|"name plus pool_name, BY NAME"| snap
spol -->|"snapshot_policy_id, BY ID"| vol
vault -->|"backup_vault_id, BY ID"| vol
bpol -->|"backup_policy_id, BY ID"| vol
classDef me fill:#0078D4,stroke:#004578,color:#fff;
classDef keystone fill:#004578,stroke:#001f3f,color:#fff;
classDef sib fill:#eef2f7,stroke:#b8c4d0,color:#1b1b1b;
class acct keystone;
class enc,pool,vol,snap,spol,vault,bpol,vghana,vgora me;
class rg,kv,vnet sib;
flowchart TB
addr["name, resource_group_name, account_name, location: force-new, i.e. everything except tags"]
norepl["a replacement is NOT a migration: existing backups do not follow"]
nosurface["NO redundancy, retention, or immutability configuration exists on this resource"]
retention["retention lives on the backup POLICY, not here"]
members["the vault does not know its members: a VOLUME opts in by naming vault id plus policy id"]
del["destroy FAILS while backups remain, unless the caller enables the provider opt-in"]
this["terraform-azurerm-netapp-backup-vault"]
vault["azurerm_netapp_backup_vault.this"]
out["outputs: id consumed BY ID, name, location for the same-region check"]
addr -->|"identity"| this
norepl -->|"cost of a rename"| addr
nosurface -->|"the useful absence"| this
retention -->|"separate module"| nosurface
members -->|"membership is one-way"| this
del -->|"write access is destroy access"| this
this -->|"creates"| vault
vault -->|"emits"| out
classDef me fill:#0078D4,stroke:#004578,color:#fff;
classDef keystone fill:#004578,stroke:#001f3f,color:#fff;
classDef sib fill:#eef2f7,stroke:#b8c4d0,color:#1b1b1b;
class this me;
class vault keystone;
class addr,norepl,nosurface,retention,members,del,out sib;
Resource inventory
| Resource | Count | Role |
|---|---|---|
azurerm_netapp_backup_vault.this |
1 | The keystone backup vault, with its optional timeouts block. |
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/azurerm |
~> 4.0 |
| Provider block | None in this module — the caller configures provider "azurerm" { features {} }, auth, and subscription. |
Schema notes that bite (verified against the live provider schema):
- Force-new:
name,resource_group_name,account_name, andlocation— every field excepttags. - A replacement is not a migration. Renaming or moving the vault creates a new one; existing backups do not follow, and the old vault's contents go with it.
- Destroying the vault fails while it still holds backups. The provider refuses with "cannot delete backups from backup vault due to missing DeleteBackupsOnBackupVaultDestroy feature set as true". The caller can opt in with
features { netapp { delete_backups_on_backup_vault_destroy = true } }, which then deletes every backup irreversibly. There is no soft-delete or retention lock either way. This is a caller-sidefeatures {}setting, so it is not — and cannot be — a module variable. - The resource has no redundancy, retention, or immutability configuration. Retention belongs to the backup policy; everything else is service-managed.
- A vault and the volumes backing up to it should be in the same region, but the relationship is expressed only inside each volume's configuration — nothing validates it.
- The vault does not know its members. A volume opts in by naming this vault's
idalongside a policyid, so there is no way to enumerate a vault's volumes from the vault. - The account is addressed by name, but volumes consume this vault by Resource ID — the family mixes both conventions.
Contributoron the resource group holding the NetApp account, or a custom role coveringMicrosoft.NetApp/netAppAccounts/backupVaults/*.- Read access on the NetApp account, so the vault can be created within it.
- No data-plane or Key Vault permission is needed here.
⚠️ Note the operational consequence: a caller who enablesfeatures { netapp { delete_backups_on_backup_vault_destroy = true } }turns destroying this vault into irreversible deletion of every backup in it, so write access here plus that provider flag is destroy access over the recovery copies. Without the flag the destroy simply fails. Scope the role accordingly.
- An existing NetApp account, in the same region as this vault and as the volumes whose backups land here.
- The caller configures the
provider "azurerm" { features {} }block, auth, and subscription.
terraform-azurerm-netapp-backup-vault/
├── providers.tf # required_version >= 1.12.0; azurerm ~> 4.0; no provider block
├── variables.tf # four force-new addressing fields; tags/timeouts tail; nothing else exists
├── main.tf # keystone azurerm_netapp_backup_vault.this
├── outputs.tf # id (consumed by ID), name, account/rg, and location for the region check
├── README.md # this document
├── SCOPE.md # cross-module contract
├── LICENSE # MIT
└── .gitignore # canonical library ignore set
provider "azurerm" {
features {}
}
module "anf_vault" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-backup-vault.git?ref=v1.0.0"
name = "bv-prod-01"
resource_group_name = module.anf_rg.name
account_name = module.anf_account.name # the NAME, not the id
location = module.anf_rg.location
tags = { workload = "records", retention_owner = "platform-storage" }
}ℹ️ That is the whole resource. A vault on its own backs nothing up — pair it with a backup policy and attach both to a volume (example 3).
ℹ️ The caller owns the provider, its authentication, and the mandatory
features {}block. This module never declares them.
Consumes
| Input | Type | Source module |
|---|---|---|
resource_group_name |
string |
terraform-azurerm-resource-group (name) |
account_name |
string |
terraform-azurerm-netapp-account (name) |
location |
string |
caller / terraform-azurerm-netapp-account (location) |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
Backup vault Resource ID (first) | terraform-azurerm-netapp-volume (data_protection_backup_policy.backup_vault_id) — by ID, not by name |
name |
Backup vault name | operational review |
account_name / resource_group_name |
Addressing | composition wiring |
location |
The vault's region | composition check — a volume and its vault should share a region, and nothing enforces that |
The examples below reference existing resources by ID or name rather than creating them; this module owns only its own resource. Those references are declared inputs:
variable "anf_vnet_subnet_ids" {
description = "subnet ids of an existing anf vnet that these examples reference but do not create."
type = map(string)
}1 · The whole module
module "anf_vault" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-backup-vault.git?ref=v1.0.0"
name = "bv-prod-01"
resource_group_name = module.anf_rg.name
account_name = module.anf_account.name
location = module.anf_rg.location
}ℹ️ Four addressing fields and
tags. There is genuinely nothing else — see example 2 for what people expect to find and don't.
2 · The controls that do not exist here
# ❌ None of these are arguments on this resource.
# redundancy = "GeoRedundant"
# retention_days = 30
# immutability = "Locked"
# soft_delete = true
# public_access = false
⚠️ Worth stating plainly: there is no redundancy setting, no retention, no immutability lock, and no soft-delete on a NetApp Files backup vault. Retention is the backup policy's job (example 3); the rest is service-managed. If you are hardening a backup destination and looking for a lock, it is not on this resource.
3 · A vault only does something when paired with a policy
module "anf_backup_policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-backup-policy.git?ref=v1.0.0"
name = "bp-prod-01"
resource_group_name = module.anf_rg.name
account_name = module.anf_account.name
location = module.anf_rg.location
daily_backups_to_keep = 7
weekly_backups_to_keep = 5
monthly_backups_to_keep = 12
}# The VOLUME is what joins them:
module "anf_volume" {
# ...
data_protection_backup_policy = {
backup_vault_id = module.anf_vault.id
backup_policy_id = module.anf_backup_policy.id
}
}💡 The vault is the destination, the policy is the retention, and the volume is what opts into both. A vault with no policy pointed at it holds nothing, and neither resource knows about the other — the pairing exists only inside each volume's configuration.
4 · Volumes consume the vault by ID, not by name
backup_vault_id = module.anf_vault.id # ✅ by Resource ID
backup_policy_id = module.anf_backup_policy.idℹ️ Note the inconsistency in this family and plan for it: the account and pool are addressed by name, while the vault and policy are consumed by Resource ID. The ID form creates a real Terraform dependency, which is the better of the two — a rename shows up as a plan diff rather than a run-time failure.
5 · A rename is not a migration
name = "bv-prod-02" # was bv-prod-01 — forces replacement
⚠️ Every field excepttagsis force-new, so this creates a new, empty vault — and the old one is destroyed along with the backups it held. There is no move operation and no way to re-point existing backups. Treat the vault's name as permanent for the life of the data in it.
6 · Destroying a vault that still holds backups
# Removing the module destroys the vault AND its contents. No soft-delete,
# no recycle bin, no grace period.
⚠️ This is the single most consequential fact about the resource. Aterraform destroyover a NetApp environment takes the recovery copies with it, and there is nothing to recover them from. Where that matters, keep the vault in a separate state from the volumes it protects.
7 · The same-region requirement nothing enforces
output "region_check" {
description = "A volume and its vault should share a region; nothing validates it."
value = {
vault = module.anf_vault.location
volume = module.anf_volume.location
}
}
⚠️ locationis emitted specifically so a composition can compare it. The vault and the volumes backing up to it should be in the same region, but the relationship lives inside each volume's configuration — neither Terraform nor this module can check it.
8 · One vault, many policies, many volumes
module "anf_vault" { source = "...netapp-backup-vault..." } # one destination
module "policy_short" {
source = "...netapp-backup-policy..."
daily_backups_to_keep = 7
}
module "policy_long" {
source = "...netapp-backup-policy..."
daily_backups_to_keep = 7
monthly_backups_to_keep = 84
}💡 The relationship is genuinely many-to-many, which is why the vault and policy are separate modules. One vault serves every retention scheme; you do not need a vault per policy, and creating one would just fragment the destination for no benefit.
9 · Tags carry what the resource cannot
tags = {
workload = "records"
retention_owner = "platform-storage"
data_class = "member-records"
destroy_review = "required"
}💡
tagsis the only descriptive surface on this resource, and the only field that is not force-new. Since the vault cannot tell you which volumes back up to it, recording the workload here is the sole trace a later reader gets.
10 · Several vaults from a keyed map
locals {
vaults = { "bv-prod-01" = "records", "bv-nonprod-01" = "sandbox" }
}
module "vaults" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-backup-vault.git?ref=v1.0.0"
for_each = local.vaults
name = each.key
resource_group_name = module.anf_rg.name
account_name = module.anf_account.name
location = module.anf_rg.location
tags = { workload = each.value }
}💡 Separate vaults per environment is a reasonable blast-radius decision — since deleting a vault destroys its contents, keeping production's recovery copies out of a sandbox teardown's path is worth the extra resource.
11 · Generous delete timeouts, used carefully
timeouts = {
create = "30m"
delete = "2h" # a vault holding many backups takes time to remove
}
⚠️ Raisedeletewhen tearing down an environment with substantial backup history — but read that as a warning, not just a tuning tip: a long delete is the service removing recovery copies. Be certain they are expendable first.
12 · Reviewing the backup estate
output "backup_destinations" {
description = "Vaults and their regions. Membership is not readable from the vault side."
value = {
for k, m in module.vaults : k => { id = m.id, region = m.location }
}
}ℹ️ Note what this table cannot show: which volumes actually back up to each vault. That knowledge lives only in each volume's
data_protection_backup_policyblock, so a complete picture has to be assembled from the volume side.
13 · Separating the vault's lifecycle from the volumes'
# Vault + policy in their own root module / state:
module "anf_vault" { source = "...netapp-backup-vault..." }
module "anf_policy" { source = "...netapp-backup-policy..." }# Volumes elsewhere, consuming the IDs via remote state or variables:
data_protection_backup_policy = {
backup_vault_id = var.backup_vault_id
backup_policy_id = var.backup_policy_id
}🔒 Because destroying the vault destroys the recovery copies, keeping it out of the same state as the volumes it protects means a volume teardown cannot take the backups with it. A deliberate split, not an accident of layout.
14 · Write access is destroy access
Microsoft.NetApp/netAppAccounts/backupVaults/write
Microsoft.NetApp/netAppAccounts/backupVaults/delete
🔒 A role that can create a vault through this module can delete one, and deleting it destroys the backups inside. There is no immutability lock on this resource to fall back on, so the boundary is the RBAC scope — grant it on the resource group holding the NetApp account rather than broadly.
15 · 🏗️ End-to-end composition
provider "azurerm" {
features {}
}
# 1 · The resource group.
module "anf_rg" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
name = "rg-anf-eastus2"
location = "eastus2"
}
# 2 · The NetApp account — addressed by NAME by everything below.
module "anf_account" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-account.git?ref=v1.0.0"
name = "anf-prod-eastus2"
resource_group_name = module.anf_rg.name
location = module.anf_rg.location
}
# 3 · The capacity pool.
module "anf_pool" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-pool.git?ref=v1.0.0"
name = "pool-prod-01"
resource_group_name = module.anf_rg.name
account_name = module.anf_account.name
location = module.anf_rg.location
service_level = "Premium"
size_in_tb = 4
}
# 4 · The backup vault — this module. The DESTINATION only; no retention here.
module "anf_vault" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-backup-vault.git?ref=v1.0.0"
name = "bv-prod-01"
resource_group_name = module.anf_rg.name
account_name = module.anf_account.name
location = module.anf_rg.location # must match the volumes' region
tags = {
workload = "records"
retention_owner = "platform-storage"
destroy_review = "required" # deleting this destroys the recovery copies
}
}
# 5 · The retention policy — a separate resource, deliberately.
module "anf_backup_policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-backup-policy.git?ref=v1.0.0"
name = "bp-prod-01"
resource_group_name = module.anf_rg.name
account_name = module.anf_account.name
location = module.anf_rg.location
daily_backups_to_keep = 7
weekly_backups_to_keep = 5
monthly_backups_to_keep = 12
}
# 6 · The VOLUME is what joins the vault and the policy — both BY RESOURCE ID.
module "anf_volume" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-volume.git?ref=v1.0.0"
name = "vol-records"
resource_group_name = module.anf_rg.name
account_name = module.anf_account.name
pool_name = module.anf_pool.name
location = module.anf_pool.location
service_level = module.anf_pool.service_level
storage_quota_in_gb = 1024
volume_path = "records"
subnet_id = var.anf_vnet_subnet_ids["anf"]
export_policy_rule = {
app_tier = {
rule_index = 1
allowed_clients = ["10.40.2.0/24"]
protocol = ["NFSv3"]
unix_read_write = true
}
}
data_protection_backup_policy = {
backup_vault_id = module.anf_vault.id
backup_policy_id = module.anf_backup_policy.id
}
}
output "backup_wiring" {
description = "The region check nothing enforces, plus the two IDs the volume joined."
value = {
vault_region = module.anf_vault.location
volume_region = module.anf_volume.location
vault_id = module.anf_vault.id
policy_id = module.anf_backup_policy.id
}
}💡 The shape of this composition is the design: three independent resources (vault, policy, volume) and the volume is the only one that knows about the other two. That is why the vault cannot list its members, why the same-region requirement is unenforceable, and why the vault and policy are separate modules rather than one. Output names on sibling modules are illustrative; match them to the versions you pin.
Required (all four force-new): name, resource_group_name, account_name, location.
Universal tail: tags — the only field that is not force-new — and timeouts.
Full object() schemas
variable "name" {
# force-new. A volume references the vault by RESOURCE ID rather than by this name, but a
# rename still replaces the vault — and a replacement is NOT a migration: existing backups do
# not follow.
type = string
}
variable "resource_group_name" { type = string } # force-new
variable "account_name" { type = string } # force-new; the NAME, not the Resource ID
variable "location" { type = string } # force-new; match the account and the volumes
variable "tags" {
# The ONLY non-force-new field, and the only descriptive surface on this resource. Since the
# vault cannot tell you which volumes back up to it, recording the workload here is the sole
# trace a later reader gets.
type = map(string)
default = {}
}
variable "timeouts" {
# ⚠️ Destroying a backup vault FAILS while backups remain unless the caller enables the provider
# opt-in, which then deletes them all irreversibly. Allow a generous `delete` when
# tearing down an environment — and be certain the backups are expendable first.
type = object({
create = optional(string)
read = optional(string)
update = optional(string)
delete = optional(string)
})
default = null
}
# NOTE what does not exist here: no redundancy setting, no retention, no immutability lock, no
# soft-delete. Retention belongs to terraform-azurerm-netapp-backup-policy; the rest is
# service-managed.| Output | Description | Kind |
|---|---|---|
id |
The Azure Resource ID of the backup vault | Passthrough |
name |
The backup vault name | Passthrough |
account_name |
The NetApp account this backup vault belongs to | Passthrough |
resource_group_name |
The resource group holding the NetApp account | Passthrough |
location |
The region the backup vault resides in, normalized by the provider (an input of "East US" is emitted as "eastus") | Passthrough |
account_id |
The Azure Resource ID of the parent NetApp account, derived by trimming the vault segment from this vault's ID | Passthrough |
arm_resource_type |
The ARM resource type this module creates | Derived |
tags |
The tags applied to the vault | Passthrough |
tag_count |
The number of tag pairs on the vault, against an Azure ceiling of 50 per resource | Passthrough |
destroy_fails_while_backups_remain |
Always true under the provider's DEFAULT configuration | Constant |
provider_feature_that_forces_destroy |
The name of the provider feature a caller must set to true, inside provider azurerm { features { netapp { | Derived |
destroy_deletes_every_backup_when_that_feature_is_enabled |
Always true, and it is irreversible | Constant |
destroy_vault_retry_attempts |
How many times the provider retries the whole vault delete when a backup appears in the vault mid-teardown - a real race, because a backup started just before its volume was deleted can take time to show up | Passthrough |
destroy_backup_delete_retry_attempts |
How many times the provider retries deleting each individual backup, at 30-second intervals, while Azure reports that a backup transfer is still in progress | Passthrough |
only_tags_are_updatable |
Always true | Constant |
timeout_defaults |
The provider's built-in timeouts for this resource, which appear nowhere in the schema | Derived |
effective_timeouts |
The timeouts actually in force: the caller's values where supplied, the provider's defaults otherwise | Derived |
is_prerequisite_for_any_backup |
Always true | Constant |
backups_survive_deletion_of_their_volume |
Always true, and it is the opposite of the intuition most teardown scripts encode | Constant |
restore_is_same_region_only |
Always true | Constant |
backup_data_placement_is_service_managed |
Always true, and it explains why this resource has no configuration | Constant |
backup_data_encryption_at_rest |
How the backup data is encrypted at rest, for a control questionnaire | Derived |
backup_data_encryption_in_transit |
How backup data reaches the vault, for a control questionnaire | Derived |
one_vault_per_account_is_recommended |
Always true | Constant |
max_manual_backups_per_volume_per_day |
The Azure ceiling on on-demand backups of a single volume within a day, which is NOT adjustable by support request | Passthrough |
max_combined_backup_retention_count |
The Azure ceiling on backups retained per volume, counting the hourly, daily, weekly and monthly retention settings of a backup policy TOGETHER | Passthrough |
max_protected_volume_size_tib |
The largest volume, in TiB, that Azure NetApp Files backup can protect | Passthrough |
max_backup_enabled_volumes_per_subscription |
The Azure ceiling on how many volumes in a subscription can have backup enabled, which is NOT adjustable by support request | Passthrough |
No secret is emitted; this resource carries none.
- The module is small because the resource is, and documenting the absence is the main contribution. There is no redundancy setting, no retention, no immutability lock, and no soft-delete on a NetApp Files backup vault. Retention belongs to the policy; everything else is service-managed. A reader hardening a backup destination will go looking for a lock, and the fastest help is to say it isn't here.
- Deleting the vault destroys the backups it holds, with no grace period and no immutability to fall back on. That single fact drives two pieces of guidance: tag the vault so a destroy review is prompted, and consider keeping it in a different state from the volumes it protects so a volume teardown cannot take the recovery copies with it.
- A replacement is not a migration. Every field except
tagsis force-new, so a rename creates a new empty vault and destroys the old one's contents. The vault's name is effectively permanent for the life of the data in it — which is a stronger constraint than "force-new" usually implies. - The vault does not know its members. A volume opts in by naming this vault's
idalongside a policyid, so membership is readable only from the volume side. Any inventory of "what backs up where" has to be assembled from volumes, not vaults. - This family mixes two addressing conventions, and this module sits on the better side of it: the account and pool are consumed by name (plain strings, no Terraform dependency), while the vault and policy are consumed by Resource ID (real references, so a rename surfaces as a plan diff).
- The same-region requirement is unenforceable from here. The vault and its volumes should share a region, but the relationship lives inside each volume's configuration — so
locationis emitted specifically to let a composition compare the two. - The vault and the policy are separate modules on the ownership test. They have independent lifecycles and a genuinely many-to-many relationship with volumes: one vault serves every retention scheme. Combining them would force a redundant vault per policy.
- No
validation {}blocks, because there is no closed value set and no cross-field rule — every input is a free-form addressing string. Where a resource has nothing checkable, this suite says so rather than inventing a check. features {}dependence. The module carries noprovider {}block. If it appears not to initialize in isolation, the cause is a missing caller-sideprovider "azurerm" { features {} }.
| Concern | Secure default (empty call) | Opt-out (caller must type it) |
|---|---|---|
| Absent controls | documented: no redundancy, retention, immutability, or soft-delete exists here | — (structural) |
| Destroy blast radius | destroy FAILS while backups remain; irreversible deletion only when the caller sets the provider opt-in | — (mitigate with state separation and RBAC scope) |
| Rename safety | documented: a replacement is not a migration | rename anyway (destroys the contents) |
| Retention | delegated to the backup policy module | — (structural) |
| Region consistency | location emitted for the check nothing enforces |
— (no opt-out) |
| Referencing | id emitted; volumes consume by ID, creating a real dependency |
— (no opt-out) |
| Provenance | tags documented as the only descriptive surface |
leave it empty |
terraform init -backend=false
terraform validate
terraform fmt -check- Pin the module with
?ref=v1.0.0— never a branch. - This library is plan-only during authoring; a human runs
terraform plan/applyfrom CI against real credentials. - Treat any plan that proposes replacing or destroying this vault as data loss — the backups go with it, and nothing recovers them.
- Confirm the vault's region matches the volumes that will back up to it; nothing checks this.
- Consider keeping the vault in a separate state from the volumes it protects.
terraform validateproves the configuration is internally consistent and type-correct against the pinned provider schema. This module has novalidation {}blocks — every input is a free-form addressing string with no closed value set.terraform fmt -checkenforces canonical formatting.- Neither command calls Azure. Only
terraform plan(run by a human, from CI) exercises the ARM API — the module ships without any cloud apply. - What only apply exercises: whether the named account exists in the stated region.
- What nothing exercises: whether any volume actually backs up to this vault, whether a policy is paired with it, and whether the vault's region matches its volumes'. All three live in the volume's configuration, so a vault that is correct in isolation can still be part of a backup arrangement that does nothing.
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
id = "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg-anf-eastus2/providers/Microsoft.NetApp/netAppAccounts/anf-prod-eastus2/backupVaults/bv-prod-01"
name = "bv-prod-01"
account_name = "anf-prod-eastus2"
resource_group_name = "rg-anf-eastus2"
location = "eastus2"
| Symptom | Cause | Fix |
|---|---|---|
Provider configuration not present / features error |
No caller-side provider "azurerm" { features {} }. |
Add the provider block with features {} in the root module. |
| Looking for a redundancy or immutability setting | It does not exist on this resource. | Retention is the backup policy; the rest is service-managed. |
| No backups appear in the vault | No policy is paired with it, or no volume opted in. | Create a backup policy and attach both IDs in the volume's data_protection_backup_policy. |
| Backups vanished after a rename | Every field except tags is force-new; a replacement is not a migration. |
Restore from elsewhere if you can, and treat the vault name as permanent. |
A terraform destroy of the vault fails |
The provider refuses while backups remain, unless the caller has enabled the delete_backups_on_backup_vault_destroy provider feature. |
Delete the backups deliberately first, or enable the flag knowing it deletes every backup irreversibly. |
Backups vanished after a terraform destroy |
The caller had that provider feature enabled, so the destroy cascaded. There is no soft-delete. | Keep the vault in a separate state from the volumes it protects, and leave the flag off unless you mean it. |
| Backups fail with a region error | The vault and the volume are in different regions. | Align them; nothing validates this. |
| Cannot determine which volumes use this vault | The vault does not know its members. | Enumerate from the volume side — the relationship lives there. |
| Delete times out | The vault holds substantial backup history. | Raise the delete timeout — and confirm the backups are expendable. |
| Apply fails: account not found | account_name does not resolve, or the region differs. |
Wire it from the account module's name output. |
- azurerm provider —
azurerm_netapp_backup_vault - Understand Azure NetApp Files backup
- Configure backups for Azure NetApp Files
- Manage backup vaults
- Sibling modules:
terraform-azurerm-netapp-account,terraform-azurerm-netapp-pool,terraform-azurerm-netapp-volume,terraform-azurerm-netapp-snapshot,terraform-azurerm-netapp-backup-policy. - This module's
SCOPE.md.
💙 "Infrastructure as Code should be standardized, consistent, and secure."