NetApp Backup and Recovery™ for Kubernetes protects containerized applications and virtual machines on Kubernetes, including Red Hat OpenShift and OpenShift Virtualization. Three earlier posts showed how to add Kubernetes clusters and backup destination credentials, and how to manage applications without the NetApp Console™ UI. This post closes the last gap: you can now create protection policies through the API with ready-made scripts from the Verda GitHub repository, so the complete setup runs from a pipeline or a GitOps workflow.
Introduction
A protection policy defines how Backup and Recovery protects an application: which snapshot schedules run, how long copies are kept, and where they go. Until now, this was the one step in the setup chain that still needed the Console UI. Clusters, credentials, and backup target credentials could be automated, but the policy that ties them together could not.
The new scripts in the protection policies folder of the Verda repository remove that last manual step. They cover four policy types and four backup targets:
- local-snapshot: Snapshots on the primary storage of the cluster only.
- disk-to-object: Snapshots plus backups to object storage.
- disk-to-disk: Snapshots plus replication to a secondary ONTAP® system.
- 3-2-1-fanout: Snapshots, replication to a secondary ONTAP system, and backups to object storage.
The supported backup targets are Azure Blob, Amazon Web Services (AWS) S3, ONTAP S3, and NetApp StorageGRID® (sgws). Together with the two earlier posts, you can now automate the whole chain: discover the cluster, add the credentials, create the policy, and assign it to your applications.
Prerequisites
Before you start, make sure you have the following:
- The authentication and account discovery steps from the first two sections of the Verda repository, which give you valid values for
ACCOUNT_ID, AGENT_ID, and ACCESS_TOKEN. Use the organization ID (a UUID) as ACCOUNT_ID. - An existing backup target for the policy types that use object storage: the credentials from the previous post, plus the bucket or storage account.
- For
disk-to-disk and 3-2-1-fanout, a secondary ONTAP system that you already added to the Console. - A Linux or macOS shell with
curl and jq installed.
Clone the repository and change to the scripts folder:
git clone https://github.com/NetApp/Verda.git
cd Verda/BnR-API/04-protection-policies/scripts
Set the base URL of the Console API. All scripts and API calls in this post use it:
export CONSOLE_API="https://cloudmanager.cloud.netapp.com"
How the scripts are organized
The folder contains one script for each combination of backup target and policy type, named <target>-<n>-<type>.sh, for example azure-4-3-2-1-fanout.sh. Each script runs on its own with --policy-name and documents its variables in its header.
For most cases, use the wrapper script create-k8s-policy.sh. It picks the right script for your target and policy type, validates your options, and stops with a list of any missing variables. A companion script, delete-k8s-policy.sh, removes a policy by its policyId.
Create a policy
We start with the most complete case: a 3-2-1 fanout policy that backs up to Azure Blob. First, ask the wrapper which variables this combination needs:
./create-k8s-policy.sh azure 3-2-1-fanout --vars
azure / 3-2-1-fanout needs this command-line option:
--policy-name <name> name of the protection policy to create
...and these environment variables:
ACCOUNT_ID NetApp Console organization/account ID used in the API URL and X-Account-Id header
AGENT_ID Console agent ID (agentID in the request body)
ACCESS_TOKEN Bearer access token generated from your refresh token
AZURE_SUBSCRIPTION_ID Azure subscription ID of the backup target
AZURE_RG Azure resource group of the backup target
AZURE_STORAGE_ACCOUNT Azure storage account of the backup target
TARGET_SYSTEM_NAME Name of the secondary ONTAP system as shown in the Console (not needed if TARGET_SYSTEM_ID or SECONDARY_TARGET is set)
Optional (defaults in parentheses; or use the wrapper's command-line options):
SECONDARY_TARGET Secondary system as shown in the UI drop-down: "system | svm | aggregate"; replaces the TARGET_* name/SVM/aggregate
TARGET_SYSTEM_ID Secondary ONTAP system ID, e.g. VsaWorkingEnvironment-xxxxxxxx; selects the system instead of its name
TARGET_SVM SVM on the secondary system; only needed if it has more than one
TARGET_AGGREGATE Aggregate on that SVM; only needed if it has more than one
TARGET_CLUSTER_ID Cluster ID of the secondary system; looked up unless set
TARGET_STORAGE_SYSTEM_UUID SVM UUID on the secondary system; looked up unless set
HOURLY_EVERY_HOURS Hourly schedule: run every N hours (1)
HOURLY_RETENTION Hourly schedule: local snapshots to keep (3)
DAILY_AT Daily schedule: time of day HH:MM (00:00)
DAILY_RETENTION Daily schedule: local snapshots to keep (3)
WEEKLY_ENABLED Add a weekly schedule: true or false (false)
WEEKLY_DAY Weekly schedule: day of week, e.g. Sunday (Sunday)
WEEKLY_AT Weekly schedule: time of day HH:MM (00:00)
WEEKLY_RETENTION Weekly schedule: local snapshots to keep (12)
MONTHLY_ENABLED Add a monthly schedule: true or false (false)
MONTHLY_DAY Monthly schedule: day of month 1-31 (2)
MONTHLY_AT Monthly schedule: time of day HH:MM (01:00)
MONTHLY_RETENTION Monthly schedule: local snapshots to keep (3)
SECONDARY_HOURLY_RETENTION Copies to keep on the secondary ONTAP system, hourly (5)
SECONDARY_DAILY_RETENTION ... daily (4)
SECONDARY_WEEKLY_RETENTION ... weekly, only if weekly is enabled (3)
SECONDARY_MONTHLY_RETENTION ... monthly, only if monthly is enabled (2)
OBJECT_HOURLY_RETENTION Copies to keep in the object store, hourly (5)
OBJECT_DAILY_RETENTION ... daily (4)
OBJECT_WEEKLY_RETENTION ... weekly, only if weekly is enabled (4)
OBJECT_MONTHLY_RETENTION ... monthly, only if monthly is enabled (3)
RETRY_COUNT Backup retries: number of retry attempts (3)
RETRY_AFTER Backup retries: wait before retrying, e.g. 5m (5m)
PARTIAL_BACKUP Allow partial backups: true or false (false)
Choosing the secondary ONTAP system (the replication target). Two ways, same result:
TARGET_SYSTEM_NAME Names only the system, e.g. BlueXPWEPerfStage2. If it has exactly one SVM
and one aggregate, they are picked automatically; if it has several, the
script stops and lists them (then set TARGET_SVM / TARGET_AGGREGATE).
SECONDARY_TARGET Names the whole choice as the UI drop-down shows it, e.g.
"BlueXPWEPerfStage2 | svm_BlueXPWEPerfStage2 | aggr1". When set, it replaces
TARGET_SYSTEM_NAME, TARGET_SVM, TARGET_AGGREGATE and TARGET_SYSTEM_ID.
List the available entries with: create-k8s-policy.sh --list-secondary-targets
Either way, the cluster ID and SVM UUID are looked up, unless you set TARGET_CLUSTER_ID /
TARGET_STORAGE_SYSTEM_UUID yourself.
The output lists TARGET_SYSTEM_NAME as required, but setting SECONDARY_TARGET replaces it, as we show below. Besides the common variables ACCOUNT_ID, AGENT_ID, and ACCESS_TOKEN, the Azure target needs the subscription, resource group, and storage account:
The output lists TARGET_SYSTEM_NAME as required, but setting SECONDARY_TARGET replaces it, as we show below. Besides the common variables ACCOUNT_ID, AGENT_ID, and ACCESS_TOKEN, the Azure target needs the subscription, resource group, and storage account:
export AZURE_SUBSCRIPTION_ID="<subscription-id>"
export AZURE_RG="<resource-group>"
export AZURE_STORAGE_ACCOUNT="<storage-account>"
Because the policy replicates to a secondary ONTAP system, we also need to select the replication target, which consists of the system, the SVM, and the aggregate. List the available choices:
./create-k8s-policy.sh azure 3-2-1-fanout --list-secondary-targets
rtp-a800-c01 | patricu | rtp_a800_02_NVME_SSD
rtp-a800-c01 | patricu | rtp_a800_01_NVME_SSD
rtp-a800-c01 | VEEAM | rtp_a800_02_NVME_SSD
rtp-a800-c01 | VEEAM | rtp_a800_01_NVME_SSD
rtp-a800-c01 | INFRASTRUCTURE | rtp_a800_02_NVME_SSD
rtp-a800-c01 | INFRASTRUCTURE | rtp_a800_01_NVME_SSD
Copy one entry exactly as the Console drop-down or the above command output shows it and store it in SECONDARY_TARGET. In our example, we replicate to the SVM patricu on the first aggregate:
export SECONDARY_TARGET="rtp-a800-c01 | patricu | rtp_a800_02_NVME_SSD"
Now create the policy. Only --policy-name is mandatory on the command line:
./create-k8s-policy.sh azure 3-2-1-fanout --policy-name "prod-3-2-1-azure" | jq '{policyId, policyName, backupArchitectureType, secondarySettings}'
{
"policyId": "1adbdcac-1015-4b55-a33a-1577e237a832",
"policyName": "prod-3-2-1-azure",
"backupArchitectureType": "ThreeTwoOneFanout",
"secondarySettings": {
"transferSchedule": "inline",
"replicationRetentions": [
{
"schedulerType": "Hourly",
"backupType": "DATA",
"retentionCount": 5,
"snapMirrorLabel": "Hourly"
},
{
"schedulerType": "Daily",
"backupType": "DATA",
"retentionCount": 4,
"snapMirrorLabel": "Daily"
}
],
"backupTargets": [
{
"clusterID": "5e6e935e-bd09-11ed-9718-00a098b92a73",
"systemID": "OnPremWorkingEnvironment-VszMCwtJ",
"systemName": "rtp-a800-c01",
"storageSystemUuid": "4b2ff066-fe11-11f0-a931-00a098b92a73",
"storageAggregateName": "rtp_a800_02_NVME_SSD",
"svm": "patricu"
}
]
}
}
The script returns the complete policy definition, and we filter it with jq here. The output includes the policyId of the new policy and the replication target we selected. Other targets work the same way and need different variables:
Target | Variables |
|---|
------ | --------- |
azure
| AZURE_SUBSCRIPTION_ID, AZURE_RG, AZURE_STORAGE_ACCOUNT
|
aws
| AWS_ACCOUNT_ID, AWS_BACKUP_TARGET (bucket name)
|
ontap-s3
| ONTAP_CRED_NAME, ONTAP_BUCKET_NAME
|
sgws
| SGWS_CRED_NAME, SGWS_BUCKET_NAME
|
For ONTAP S3 and StorageGRID, the scripts look up the credential ID and server address for you.
Verify the policy
List all policies with a single API call:
curl -sS "${CONSOLE_API}/backup-recovery/organizations/${ACCOUNT_ID}/v1/workloads/k8s/kubernetes/policies" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "X-Account-Id: ${ACCOUNT_ID}" \
| jq '.[] | {policyId, policyName, backupArchitectureType}'
{
"policyId": "861f8520-4e20-4189-85fa-45c9ceeab86c",
"policyName": "pu-disk-object-ontap-1d",
"backupArchitectureType": "DiskToObjectStore"
}
{
"policyId": "51716790-8f52-4cbe-9673-5a193cfd6026",
"policyName": "pu-disk-object-azure-1d",
"backupArchitectureType": "DiskToObjectStore"
}
{
"policyId": "68f666e6-e192-4afc-a1b1-faba49003a40",
"policyName": "pu-disk-object-azure2-1d",
"backupArchitectureType": "DiskToObjectStore"
}
{
"policyId": "84836c2d-2cae-46e7-9098-de02937eb385",
"policyName": "pu-disk-object-azure3-1d",
"backupArchitectureType": "DiskToObjectStore"
}
{
"policyId": "1adbdcac-1015-4b55-a33a-1577e237a832",
"policyName": "prod-3-2-1-azure",
"backupArchitectureType": "ThreeTwoOneFanout"
}
The policy also appears in the Console under Backup and Recovery > Kubernetes > Policies.
Adjust schedules and retention
By default, the scripts match the Console UI: an hourly schedule that keeps three copies, and a daily schedule at 00:00 that keeps three copies. Replicas and object store copies have their own defaults: five hourly and four daily copies. Weekly and monthly schedules are optional. Use flags to change the defaults. This example creates an AWS S3 policy that keeps six hourly copies, runs the daily schedule at 02:00, and adds a weekly schedule with eight copies:
./create-k8s-policy.sh aws disk-to-object --policy-name "prod-aws-8w" \
--hourly-retention 6 --daily-at 02:00 --weekly --weekly-retention 8
You can set the retention of replicas and object store copies separately with the --secondary-*-retention and --object-*-retention flags, or together with the --replica-*-retention flags. Run ./create-k8s-policy.sh -h for all options.
Choose the secondary ONTAP system
For disk-to-disk and 3-2-1-fanout, we recommend setting SECONDARY_TARGET as shown above, because it names the system, SVM, and aggregate explicitly. The scripts also accept TARGET_SYSTEM_NAME and look up the SVM and aggregate for you, but this works without additional input only if the system has exactly one SVM and one aggregate. With more than one of either, the lookup cannot decide for you.
Delete a policy
To remove a policy, pass its policyId to the delete script. The script asks for confirmation unless you add --yes:
./delete-k8s-policy.sh <policyId>
The server can refuse to delete a policy that is still assigned to protected applications.
Put it into your pipeline
Because the scripts read their input from environment variables and flags, they fit into any pipeline step. A typical GitOps flow for a new cluster looks like this:
- Add the cluster to Backup and Recovery (cluster discovery post).
- Add the backup destination credentials (credentials post).
- Create the protection policy with
create-k8s-policy.sh. - Protect your applications with the policy, using the CLI or custom resources of Backup and Recovery (CLI post).
Keep these tips in mind for production use:
- Protect your secrets: Read tokens and keys from a secrets manager, never from files in source control.
- Refresh the access token: Request a new token before each pipeline run.
- Check the result: Stop the pipeline if the response does not contain a
policyId. - Name policies meaningfully: Names such as
prod-3-2-1-azure make the policy easy to find later.
Tested combinations
We tested the scripts with the Console and Trident™ Protect 26.09.0 on 2026-10-22. All four policy types are tested for Azure Blob, AWS S3, and ONTAP S3. The StorageGRID scripts are available but untested so far. Weekly and monthly schedules, --secondary-target, and --partial-backup are untested against a live system. We compared the request bodies of all combinations with the payloads that the Console UI sends.
Summary
With the protection policy scripts, the last manual step in setting up Backup and Recovery for Kubernetes now has an API-based alternative. Combined with cluster discovery and credential automation, you can onboard a cluster, connect it to a backup target, and apply a policy without opening the Console UI.
Ready to try it? Clone the Verda repository, run the scripts against a test account, and tell us in the comments what else you would like to automate. Then start protecting your Kubernetes applications with NetApp Backup and Recovery.