Skip to content

Latest commit

Β 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

☁️ Azure Disk Backup Policy Terraform Module

Manages one azurerm_data_protection_backup_policy_disk β€” the schedule and retention rules governing managed-disk backups in a Backup Vault. Targets hashicorp/azurerm ~> 4.0.

Terraform azurerm Module Type Resources Caveat

🧩 Overview

  • 🎯 Creates one disk backup policy inside an existing Backup Vault.
  • πŸ•’ Takes the schedule as ISO 8601 repeating intervals (R/<start>/P1D) and the fallback retention as a plain ISO 8601 duration (P30D) β€” two different grammars, easily swapped.
  • πŸ—‚οΈ Accepts one or more retention rules, keyed by rule name.
  • 🧊 Every argument is force-new and there is no update function β€” this policy can never be edited.
  • 🚫 Carries no tags β€” the resource exposes none.

πŸ’‘ Why it matters: you do not edit a backup policy. All six arguments are force-new and the provider declares no update function, so any change destroys and recreates it β€” taking with it every backup instance that still references the old ID. The intended way to change retention is to create a new policy and repoint the instances, because backup_policy_id on azurerm_data_protection_backup_instance_disk is deliberately the one argument in this pair that updates in place.

❀️ Support this project

If this module saves you time:

πŸ—ΊοΈ Where this fits in the family

flowchart TB
  RG["azurerm_resource_group"]
  VAULT["azurerm_data_protection_backup_vault"]
  DISK["azurerm_managed_disk"]
  SNAPRG["snapshot resource group"]
  ROLE["role assignments for the vault identity"]
  POL["backup_policy_disk"]
  INS["backup_instance_disk"]

  RG -->|"resource group"| VAULT
  VAULT -->|"vault_id, FORCE-NEW"| POL
  VAULT -->|"vault_id, FORCE-NEW"| INS
  POL -->|"backup_policy_id, the ONLY updatable argument"| INS
  DISK -->|"disk_id, FORCE-NEW"| INS
  INS -->|"snapshots written to"| SNAPRG
  ROLE -.->|"required BEFORE protection can start, not created here"| INS

  classDef me fill:#0078D4,stroke:#004578,color:#ffffff
  classDef key fill:#004578,stroke:#002b47,color:#ffffff
  classDef ext fill:#F0F3F6,stroke:#9AA5B1,color:#1F2933
  class POL,INS me
  class VAULT key
  class RG,DISK,SNAPRG,ROLE ext
Loading

The policy is referenced by the instance, never the other way round β€” and that single edge is the one thing in this pair that can change without replacing anything.

🧬 What this module builds

flowchart TB
  VN["name, vault_id, intervals, retention_duration, retention_rule, time_zone"]
  ALLFN["ALL SIX are FORCE-NEW, and there is NO update function"]
  R["azurerm_data_protection_backup_policy_disk.this"]
  NEWPOL["To change retention: create a NEW policy, repoint the instance, drop the old one"]
  OID["id, name, retention_rule_names"]
  OFLAG["retention_rule_count, rules_without_criteria"]
  OWARN["a_repeating_interval_is_not_a_duration"]

  VN --> ALLFN
  ALLFN --> R
  R -->|"never edited, only replaced"| NEWPOL
  R --> OID
  R --> OFLAG
  R --> OWARN

  classDef me fill:#0078D4,stroke:#004578,color:#ffffff
  classDef key fill:#004578,stroke:#002b47,color:#ffffff
  classDef ext fill:#F0F3F6,stroke:#9AA5B1,color:#1F2933
  class R key
  class ALLFN,NEWPOL me
  class VN,OID,OFLAG,OWARN ext
Loading
Resource Count Role
azurerm_data_protection_backup_policy_disk.this 1 The keystone. Retention rules are rendered inline.

βœ… Provider / Versions

Item Value
Terraform >= 1.12.0
hashicorp/azurerm ~> 4.0
Provider block None in this module β€” the caller configures the provider, its authentication, and the mandatory features {} block
Module type standalone

Schema notes that bite β€” each verified against the provider source and cross-checked against the provider's own documentation:

  • πŸ”΄ No update function, and all six arguments force-new. The docs agree on every one: "Changing this forces a new Backup Policy Disk to be created."
  • πŸ”΄ The nested retention fields are individually force-new too β€” eleven ForceNew markers for six arguments.
  • πŸ”΄ retention_rule is NOT capped at one. It is a list with no MaxItems; the MaxItems: 1 belongs to the nested criteria block. The docs say "One or more retention_rule blocks".
  • βœ… The Timeouts block correctly declares no update, so Terraform rejects the key outright. Not universal in this service β€” azurerm_data_protection_resource_guard declares one its code never reads.
  • The two duration fields are genuinely parsed; backup_repeating_time_intervals has no validator at all.
  • absolute_criteria is a closed, case-sensitive set from the SDK's own PossibleValuesForAbsoluteMarker().
  • time_zone is checked only for non-emptiness and takes a Windows identifier, not IANA.
  • The name pattern ^[-a-zA-Z0-9]{3,150}$ permits a name that is entirely hyphens.
  • No CustomizeDiff, no version gate, no tags.

πŸ”‘ Required Azure RBAC Roles / Permissions

Role Scope Why
Backup Contributor (or a custom role with Microsoft.DataProtection/backupVaults/backupPolicies/*) the Backup Vault Create, read and delete policies. There is no update permission to need β€” the resource has no update path.

⚠️ Creating a policy grants nothing and protects nothing. The permissions that matter at backup time belong to the vault's managed identity, on the disk and the snapshot resource group, and are granted alongside the backup instance β€” not here.

Azure Prerequisites

  • Microsoft.DataProtection registered in the subscription.
  • An existing Backup Vault.
  • Nothing else. A policy is inert until a backup instance references it.

πŸ“ Module Structure

terraform-azurerm-data-protection-backup-policy-disk/
β”œβ”€β”€ providers.tf    # required_version + the pinned azurerm; no provider block
β”œβ”€β”€ variables.tf    # 7 typed inputs, 13 validations, a THREE-key timeouts object
β”œβ”€β”€ main.tf         # locals + the single keystone resource
β”œβ”€β”€ outputs.tf      # 31 outputs; id first, then name, then the derived facts
β”œβ”€β”€ README.md       # this file
β”œβ”€β”€ SCOPE.md        # the cross-module contract
β”œβ”€β”€ LICENSE         # MIT
└── .gitignore

βš™οΈ Quick Start

provider "azurerm" {
  features {}
}

module "disk_policy" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-disk.git?ref=v1.0.0"

  name     = "bp-disk-daily"
  vault_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-backup-eastus/providers/Microsoft.DataProtection/backupVaults/bv-corp"

  backup_repeating_time_intervals = ["R/2026-09-01T23:00:00+00:00/P1D"]
  default_retention_duration      = "P30D"
}

⚠️ Note the two grammars. The schedule is a repeating interval beginning R/; the retention is a bare duration. Swapping them puts an unvalidated value in the field the provider does not check.

πŸ”Œ Cross-Module Contract

Consumes

Input Type Source
vault_id Resource ID terraform-azurerm-data-protection-backup-vault β†’ id

Emits

Output Note
id What a backup instance references
retention_rule_names, retention_rule_count Several rules are legal
rules_without_criteria Rules that select nothing
the_absolute_criteria_set_is_closed_and_case_sensitive The five legal values
force_new_fields All six

πŸ“š Example Library

1 Β· Minimal β€” daily backups kept 30 days
module "disk_policy" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-disk.git?ref=v1.0.0"

  name     = "bp-disk-daily"
  vault_id = var.backup_vault_id

  backup_repeating_time_intervals = ["R/2026-09-01T23:00:00+00:00/P1D"]
  default_retention_duration      = "P30D"
}

πŸ’‘ Without a retention rule, every backup is kept for default_retention_duration and no longer.

2 Β· Weekly backups
module "weekly" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-disk.git?ref=v1.0.0"

  name     = "bp-disk-weekly"
  vault_id = var.backup_vault_id

  backup_repeating_time_intervals = ["R/2026-09-06T02:00:00+00:00/P1W"]
  default_retention_duration      = "P90D"
}

ℹ️ The start instant sets both the first run and the day of week. P1W from a Sunday means Sundays.

3 Β· Twice a day
module "twice_daily" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-disk.git?ref=v1.0.0"

  name     = "bp-disk-twice-daily"
  vault_id = var.backup_vault_id

  backup_repeating_time_intervals = [
    "R/2026-09-01T11:00:00+00:00/P1D",
    "R/2026-09-01T23:00:00+00:00/P1D",
  ]

  default_retention_duration = "P7D"
}

πŸ’‘ Several intervals are legal and are how sub-daily schedules are expressed. schedule_count reports how many.

4 Β· A long-term retention rule
module "with_yearly" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-disk.git?ref=v1.0.0"

  name     = "bp-disk-daily-plus-yearly"
  vault_id = var.backup_vault_id

  backup_repeating_time_intervals = ["R/2026-09-01T23:00:00+00:00/P1D"]
  default_retention_duration      = "P30D"

  retention_rule = {
    "yearly" = {
      duration          = "P7Y"
      priority          = 20
      absolute_criteria = "FirstOfYear"
    }
  }
}

πŸ’‘ The map key becomes the rule's name. Everything else is kept for the 30-day default; the first backup of each year is kept for seven years.

5 Β· Several rules β€” the shape the provider actually allows
module "tiered" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-disk.git?ref=v1.0.0"

  name     = "bp-disk-tiered"
  vault_id = var.backup_vault_id

  backup_repeating_time_intervals = ["R/2026-09-01T23:00:00+00:00/P1D"]
  default_retention_duration      = "P30D"

  retention_rule = {
    "weekly" = { duration = "P1Y", priority = 10, absolute_criteria = "FirstOfWeek" }
    "yearly" = { duration = "P7Y", priority = 20, absolute_criteria = "FirstOfYear" }
  }
}

⚠️ retention_rule is a list with no MaxItems β€” several rules are legal. The MaxItems: 1 in the provider belongs to the nested criteria block, which is why each rule takes a single absolute_criteria. Reading that cap as belonging to the outer block would silently limit you to one rule.

πŸ’‘ priority orders the rules, and this module refuses duplicate priorities β€” two rules sharing one have no defined order and the provider does not check it.

6 Β· A rule with no criteria β€” legal and rarely intended
module "no_criteria" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-disk.git?ref=v1.0.0"

  name     = "bp-disk-loose"
  vault_id = var.backup_vault_id

  backup_repeating_time_intervals = ["R/2026-09-01T23:00:00+00:00/P1D"]
  default_retention_duration      = "P30D"

  retention_rule = {
    "long" = { duration = "P7Y", priority = 10 }
  }
}

⚠️ Omitting absolute_criteria means nothing selects which backups the rule applies to. The provider permits it; this module reports it through rules_without_criteria rather than refusing it, because refusing would reject a configuration the provider accepts β€” and a failed validation blocks terraform destroy as well as apply.

7 Β· The five criteria values
# The complete, CASE-SENSITIVE set, read from the SDK's own PossibleValuesForAbsoluteMarker():
#
#   AllBackup      FirstOfDay      FirstOfWeek      FirstOfMonth      FirstOfYear
#
# "firstofday" is refused. The module emits the list so a composition can assert on it:

output "legal_criteria" {
  value = module.disk_policy.the_absolute_criteria_set_is_closed_and_case_sensitive
}

ℹ️ Each value means the first successful backup of that period β€” not the first attempt.

8 Β· Time zone, and why it interacts with the schedule
module "with_tz" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-disk.git?ref=v1.0.0"

  name     = "bp-disk-local"
  vault_id = var.backup_vault_id

  backup_repeating_time_intervals = ["R/2026-09-01T23:00:00+00:00/P1D"]
  default_retention_duration      = "P30D"
  time_zone                       = "Eastern Standard Time"
}

⚠️ A Windows identifier, not IANA β€” America/New_York is refused by this module because the provider checks only that the string is non-empty, and would otherwise accept it.

⚠️ The interval already carries its own UTC offset in the start instant. Setting a time zone as well is how a schedule ends up firing at an hour nobody intended. Prefer UTC, or omit the time zone.

9 Β· Why there is no "edit the policy" example
# There is none, because it is not possible. All six arguments are force-new and
# the provider has no update function -- changing a retention duration, a
# schedule or a time zone destroys and recreates the policy.
#
# The intended sequence for changing retention is:
#
#   1. create a NEW policy alongside the old one
#   2. update backup_policy_id on the instances -- an IN-PLACE change
#   3. remove the old policy
#
# Doing it by editing this module's inputs instead destroys the policy and
# leaves every instance still referencing the old ID pointing at nothing.

module "disk_policy_v2" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-disk.git?ref=v1.0.0"

  name     = "bp-disk-daily-v2" # a NEW name, alongside the old policy
  vault_id = var.backup_vault_id

  backup_repeating_time_intervals = ["R/2026-09-01T23:00:00+00:00/P1D"]
  default_retention_duration      = "P90D" # the change
}

πŸ”΄ this_policy_can_never_be_updated and changing_retention_means_a_new_policy_not_an_edit state this as constants, because it is the single most consequential fact about the resource and nothing in a plan explains it.

10 Β· Many policies from one map
locals {
  tiers = {
    "gold"   = { interval = "R/2026-09-01T23:00:00+00:00/P1D", retain = "P365D" }
    "silver" = { interval = "R/2026-09-06T02:00:00+00:00/P1W", retain = "P90D" }
    "bronze" = { interval = "R/2026-09-06T02:00:00+00:00/P1W", retain = "P30D" }
  }
}

module "tiers" {
  source   = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-disk.git?ref=v1.0.0"
  for_each = local.tiers

  name     = "bp-disk-${each.key}"
  vault_id = var.backup_vault_id

  backup_repeating_time_intervals = [each.value.interval]
  default_retention_duration      = each.value.retain
}

πŸ’‘ Because policies cannot be edited, having several named tiers up front β€” and moving instances between them β€” is a more workable pattern than tuning one policy.

11 Β· Reviewing the policy before an apply
output "policy_review" {
  value = {
    schedules      = module.disk_policy.backup_repeating_time_intervals
    schedule_count = module.disk_policy.schedule_count
    default_retain = module.disk_policy.default_retention_duration
    rules          = module.disk_policy.retention_rule_names
    rule_count     = module.disk_policy.retention_rule_count
    loose_rules    = module.disk_policy.rules_without_criteria
    replace_on     = module.disk_policy.force_new_fields
  }
}

check "every_rule_selects_something" {
  assert {
    condition     = length(module.disk_policy.rules_without_criteria) == 0
    error_message = "A retention rule has no absolute_criteria and selects nothing in particular."
  }
}

πŸ’‘ Every value is known at plan. force_new_fields lists all six arguments, which on this resource is the same as saying "everything".

12 Β· πŸ—οΈ End-to-end composition
provider "azurerm" {
  features {}
}

module "resource_group" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"

  name     = "rg-backup-eastus"
  location = "eastus"
}

module "backup_vault" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-vault.git?ref=v1.0.0"

  name                = "bv-corp"
  resource_group_name = module.resource_group.name
  location            = module.resource_group.location

  datastore_type = "VaultStore"
  redundancy     = "LocallyRedundant"

  identity = {
    type = "SystemAssigned"
  }
}

module "disk_policy" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-disk.git?ref=v1.0.0"

  name     = "bp-disk-daily"
  vault_id = module.backup_vault.id

  backup_repeating_time_intervals = ["R/2026-09-01T23:00:00+00:00/P1D"]
  default_retention_duration      = "P30D"

  retention_rule = {
    "yearly" = { duration = "P7Y", priority = 20, absolute_criteria = "FirstOfYear" }
  }
}

module "disk_backup" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-instance-disk.git?ref=v1.0.0"

  name     = "bi-disk-data-01"
  location = module.resource_group.location
  vault_id = module.backup_vault.id

  disk_id                      = var.managed_disk_id
  snapshot_resource_group_name = "rg-snapshots-eastus"
  backup_policy_id             = module.disk_policy.id
}

output "policy_id" {
  value = module.disk_policy.id
}

πŸ”΄ This composition is not finished when the apply is. The backup vault's managed identity needs Disk Backup Reader on the disk and Disk Snapshot Contributor on rg-snapshots-eastus. Neither is created here, and without them the apply is green while protection silently fails β€” see the instance module's protection_state.

⚠️ The policy and the instance must live in the same vault. Both take vault_id and the policy's ID embeds its own vault; the provider compares them nowhere, so the instance module reports the comparison instead.

πŸ“₯ Inputs

Required: name, vault_id, backup_repeating_time_intervals, default_retention_duration.

Optional: retention_rule, time_zone, timeouts.

Full input schemas
Name Type Default Notes
name string β€” Force-new. ^[-a-zA-Z0-9]{3,150}$, reproduced exactly including its looseness.
vault_id string β€” Force-new. Anchored to backupVaults/<vault> with $.
backup_repeating_time_intervals list(string) β€” Force-new. MinItems: 1. ISO 8601 repeating intervals. No provider validator.
default_retention_duration string β€” Force-new. ISO 8601 duration, genuinely parsed by the provider.
retention_rule map(object({ duration, priority, absolute_criteria })) {} Force-new. Keyed by rule name. Several are legal.
time_zone string null Force-new. A Windows identifier. Provider checks non-emptiness only.
timeouts object({create, read, delete}) null THREE keys β€” there is no update.

🧾 Outputs

Output Type Notes
id string What a backup instance references
name / vault_id / vault_name / resource_group_name / subscription_id string
backup_repeating_time_intervals / schedule_count
default_retention_duration string
has_retention_rule / retention_rule_names / retention_rule_count
rules_without_criteria list(string) Rules that select nothing
the_absolute_criteria_set_is_closed_and_case_sensitive list(string) The five legal values
force_new_fields list(string) All six arguments
import_address string For terraform import
the CONSTANT outputs bool Facts that are consequential, invisible in state, and inferable from nothing else

Nothing is sensitive: this resource carries no credential.

🧠 Architecture Notes

This policy can never be updated. All six arguments are force-new and the provider declares no update function at all β€” the Resource struct has Create, Read and Delete and nothing else. The nested retention fields are marked force-new individually as well, which is why the file carries eleven ForceNew markers for six arguments. Consequently there is no counterpart list of updatable fields in the outputs: there are none.

So retention changes are a create-and-repoint, never an edit. Create the new policy alongside the old, update backup_policy_id on each instance β€” an in-place change that disturbs no data β€” then remove the old policy. Editing this module's inputs instead destroys the policy, and any instance still referencing the old ID is left pointing at nothing.

retention_rule is not capped, and that is easy to get backwards. The provider models it as a list with no MaxItems; the MaxItems: 1 belongs to the nested criteria block. So several rules are legal β€” a common shape is one keeping the first backup of each week for a year and another keeping the first of each year for seven β€” while each individual rule carries exactly one absolute_criteria. This module takes a map keyed by rule name so the names are unique by construction, and refuses duplicate priority values, which the provider does not check and which leave two rules with no defined order.

Two grammars, both called ISO 8601. backup_repeating_time_intervals takes R/<start instant>/<period>; default_retention_duration and each rule's duration take a bare P… duration. The durations are genuinely parsed by the provider and a malformed one is refused. The repeating intervals carry no validator at all, so a malformed schedule is accepted by Terraform and rejected by Azure β€” which is why this module adds shape checks of its own for exactly that field.

time_zone is barely validated and interacts with the schedule. It takes a Windows identifier, the provider checks only non-emptiness, and the repeating interval already carries its own UTC offset. Setting both is how a schedule fires at an unintended hour.

features {} dependence. As with every module in this suite, the caller supplies provider "azurerm" { features {} }.

🧱 Design Principles

Concern Default in the empty call Opt-out
Retention No rule β€” every backup kept for default_retention_duration add retention_rule entries
Number of rules Unbounded by the provider; the module reflects that none
Duplicate rule priorities Refused β€” no defined order, and the provider does not check none
Schedule validity Shape-checked here because the provider checks nothing none
Name rules The provider's exact pattern, including permitting --- none
Time zone Left to the service; only the IANA-style mistake is refused set a Windows identifier
A rule with no criteria Reported, not refused none needed

Two rules govern the validations. Never invent a constraint that could reject legal input β€” a validation {} failure blocks terraform destroy as well as apply, which is why the loose name pattern is reproduced rather than tightened. And enforce a configuration rule, report a service one β€” the interval's shape and the criteria set are enforced; a criteria-less rule is reported.

πŸš€ Runbook

terraform init -backend=false
terraform validate
terraform fmt -check

Pin the module with ?ref=v1.0.0 β€” never a branch. This module is plan-only; a human applies from CI.

πŸ§ͺ Testing

Know which command reaches which check. terraform validate on a configuration that CALLS this module checks types and syntax only: it evaluates none of the module's variable values. The 13 validation {} blocks are reached at terraform plan, and none of them needs credentials, because variable validation runs before the provider is configured. To exercise them without a plan, run the module as the root module and drive it through terraform console -var-file=....

Because this resource has no CustomizeDiff and no version gate, nothing is deferred to plan by the provider.

What no Terraform stage exercises: whether a repeating interval is one Azure will accept, whether the time zone identifier is real, and whether any backup instance uses this policy. The first two fail at apply; the third never fails at all β€” an unused policy is simply inert.

πŸ’¬ Example Output

$ terraform output
id                              = "/subscriptions/.../backupVaults/bv-corp/backupPolicies/bp-disk-daily"
name                            = "bp-disk-daily"
vault_name                      = "bv-corp"
backup_repeating_time_intervals = ["R/2026-09-01T23:00:00+00:00/P1D"]
schedule_count                  = 1
default_retention_duration      = "P30D"
retention_rule_names            = ["yearly"]
retention_rule_count            = 1
rules_without_criteria          = []
force_new_fields                = ["name", "vault_id", "backup_repeating_time_intervals", "default_retention_duration", "retention_rule", "time_zone"]
this_policy_can_never_be_updated = true

πŸ” Troubleshooting

Symptom Cause Fix
A plan shows the policy being destroyed and recreated after a small edit Every argument is force-new and there is no update function. Expected. Create a new policy and repoint the instances instead β€” see Example 9.
Recreating the policy broke the backup instances The instances referenced the old policy ID, which no longer exists. Sequence it: new policy β†’ update backup_policy_id β†’ remove the old policy.
every backup_repeating_time_intervals entry must be an ISO 8601 REPEATING interval beginning "R/" A bare duration such as P1D was put in the schedule field. Use R/<start>/P1D. A bare duration belongs in default_retention_duration.
The schedule was accepted and Azure rejected it at apply The provider applies no validator to the intervals. This module checks the shape only. Check the interval against Azure's expected format.
every retention_rule absolute_criteria must be one of "AllBackup", "FirstOfDay", "FirstOfWeek", "FirstOfMonth" or "FirstOfYear", case-sensitively. A misspelled or lowercased criteria value. Match the casing exactly.
retention_rule priorities must be distinct Two rules share a priority. Give them different priorities; the provider does not check this and the order would be undefined.
I can only configure one retention rule A misreading of the provider's MaxItems: 1, which belongs to the nested criteria block. Several rules are legal β€” see Example 5.
time_zone looks like an IANA name such as "America/New_York". An IANA zone was supplied. Use a Windows identifier such as Eastern Standard Time, or UTC.
Backups run at an unexpected hour The interval's start instant carries a UTC offset and a time zone was set. Prefer UTC, or omit time_zone.

πŸ”— Related Docs

πŸ’™ "Infrastructure as Code should be standardized, consistent, and secure."