Tuesday, June 23, 2026

Automating Creating Long Term backups from the Autonomous Recovery Service

 When I wrote my last blog on listing the Long Term Backups created by Autonomous Recovery Service, I didn't go through the process of how to dynamically create a new backup.

Below is how you create a long term backup in the console, but most customers want to automate this process.


The oci cli command you would use to create a new backup is "oci db backup create".

In order to create an end-of-month backup with a restore point as of midnight what I recommend customers do is

  1. Ensure you have a nightly backup that runs late at night (about 22:00) and will finish by midnight on a regular basis. 
  2. Schedule the automatic long term backup creation to occur about 00:45 on the next day. This ensures that a log sweep has occurred. For BaseDB you would wait until after the next hour.  If you have enabled the zero data loss feature (real-time redo), you want to make sure that the ARCHIVE_LAG_TARGET is set to 30 minutes or less and forcing a periodic log switch.
This ensures you are creating a long term backup with minimal archive logs to defuzzy the backup.

Command inputs

The easiest way to determine the input for this command is to use the --generate-full-command-json-input option.

oci db backup create --generate-full-command-json-input

What is returned is the JSON example below showing you what parameters need to be filled in to create the backup.

{
  "databaseId": "string",
  "displayName": "string",
  "maxWaitSeconds": 0,
  "retentionDays": 0,
  "retentionYears": 0,
  "waitForState": [
    "CREATING|ACTIVE|DELETING|DELETED|FAILED|RESTORING|UPDATING|CANCELING|CANCELED"
  ],
  "waitIntervalSeconds": 0
}

Source database and backup identifying name

  • databaseID : This is the OCID for the database that you want to create the long term backup for.
  • displayName: This is the name to identify the backup from a listing and would match the name I would put in the GUI.

Wait for state of command (Optional)

  • waitForState: Since the backup can take awhile to run, you can have the command wait to return until a specified state (or any one of a list of states) occurs. When creating a new backup the valid states would be
    • ACTIVE
    • CANCELED
    • CANCELING
    • CREATING
    • FAILED
  • maxWaitSeconds: How long to wait between state checks when waiting for a state to occur.
Example of  wait

oci db backup create --waitForState CREATING --waitForState CANCELED 

Would wait for the backup to start to be created or canceled before returning.

Retention (mandatory for long term backups and must be greater than 90 days and less than 10 years)

You would enter the number of days you want to keep backups for (retentionDays), or you would enter the number of years (retentionYears) , but not both.

Below is an example JSON file that would create a new long term backup named "bsgtest" and keep the backup for 100 days.


{
  "databaseId": "ocid1.database.oc1.phx.anyhqljtbv6267ia2wse63oz7xadpv5lfi2gf233333eomezhadfbdkt2eq",
  "displayName": "bsgtest",
  "maxWaitSeconds": 0,
  "retentionDays": 100
}


That's all you need to know about creating a long term backup dynamically.


Thursday, May 21, 2026

Autonomous Recovery Service - Listing backups

Autonomous Recovery Service - Listing backups

One of the unique features of the Autonomous Recovery Service (RCV) is the ability to create Long Term Backups by using existing backups that are currently stored in RCV.

NOTE: Long term backups, also known as "Keep" backups are self contained backups that provide the ability to restore to a small predetermined point-in-time window. These long term backups are often stored for months, or even years and are typically used for auditing purposes.

These backups are created dynamically outside of the database itself and the DB host is not used.
Because the DB host is bypassed, the normal backup listings on the DB host using the DBAASCLI tool do not see long term backups.


Viewing backups with OCI

All of the backups can be viewed in both the OCI Console and by using the OCI CLI tool.
In this blog, I will describe how you can use the OCI Cli tool to view all of the backups.
The command I am utilizing to display backups is

oci db backup list


Unfortunately, the output from this command is JSON objects which can be difficult to read if you want to produce a report.  

In this blog, I show examples leveraging JMESPath queries via the --query flag.



The Foundation: Listing Database Backups

The baseline command to list backups for a specific database requires the --database-id (OCID). By default, we want to output this as a table, grab all records across pages using the --all flag, and project key fields like Shape and Type:

oci db backup list \
  --database-id {DB OCID} \
  --output table \
  --query "data[?\"lifecycle-state\" == 'ACTIVE'] | sort_by(@, &\"time-started\")[].{Backup_Name: \"display-name\", Time_Started: \"time-started\", Status: \"lifecycle-state\", Version: \"version\", OCID: \"id\", Database_size_GBs: \"database-size-in-gbs\",Shape: \"shape\",Type: \"type\"}"

Running this execution in your environment outputs a perfectly structured text report directly in your shell stream:

+------------------+-------------------+-------------------------------------------------------------------------------------+-------------+--------+----------------------------------+-------------+-------------+
| Backup_Name      | Database_size_GBs | OCID                                                                                | Shape       | Status | Time_Started                     | Type        | Version     |
+------------------+-------------------+-------------------------------------------------------------------------------------+-------------+--------+----------------------------------+-------------+-------------+
| Automatic Backup | 24.01171875       | ocid1.dbbackup.oc1.phx.anyhqljtbv6267iafzxovfcfwpr3pcact3e3vu2exz..........         | Exadata.X8M | ACTIVE | 2026-03-30T12:04:06.553000+00:00 | INCREMENTAL | 19.26.0.0.0 |
| Automatic Backup | 24.01171875       | ocid1.dbbackup.oc1.phx.anyhqljtbv6267iat6czxrwvfnhrgl4voypnunsf2qmfc......          | Exadata.X8M | ACTIVE | 2026-03-31T12:05:41.932000+00:00 | INCREMENTAL | 19.26.0.0.0 |
| Automatic Backup | 24.01171875       | ocid1.dbbackup.oc1.phx.anyhqljtbv6267iapdhn7ekp7ax2y74pbbk32yi4c5zr.......          | Exadata.X8M | ACTIVE | 2026-04-01T12:05:13.350000+00:00 | INCREMENTAL | 19.26.0.0.0 |
| Automatic Backup | 24.01171875       | ocid1.dbbackup.oc1.phx.anyhqljtbv6267iakasw46kzqpk74g335esjwehihmkfzuj............. | Exadata.X8M | ACTIVE | 2026-04-02T12:04:54.891000+00:00 | INCREMENTAL | 19.26.0.0.0 |
| Automatic Backup | 24.01171875       | ocid1.dbbackup.oc1.phx.anyhqljtbv6267iagzx75s2s6zravvpjoem2rkvtm7ue54d............. | Exadata.X8M | ACTIVE | 2026-04-03T12:05:40.559000+00:00 | INCREMENTAL | 19.26.0.0.0 |
| Automatic Backup | 24.01171875       | ocid1.dbbackup.oc1.phx.anyhqljtbv6267ia2j7s7r464swgvrfc34dqh7b635hupho............. | Exadata.X8M | ACTIVE | 2026-04-04T12:03:59.219000+00:00 | INCREMENTAL | 19.26.0.0.0 |
| Automatic Backup | 24.01171875       | ocid1.dbbackup.oc1.phx.anyhqljtbv6267iawkwrmemheourosyriczq3v7lecgmrcj............. | Exadata.X8M | ACTIVE | 2026-04-05T07:24:00.238000+00:00 | INCREMENTAL | 19.26.0.0.0 |
| Automatic Backup | 24.01171875       | ocid1.dbbackup.oc1.phx.anyhqljtbv6267ia2j25fk53uf5fmfkivkoa6ijcbn3fdg7c............ | Exadata.X8M | ACTIVE | 2026-04-06T12:04:22.389000+00:00 | INCREMENTAL | 19.26.0.0.0 |
| Automatic Backup | 24.01171875       | ocid1.dbbackup.oc1.phx.anyhqljtbv6267iass2o5w5bryallijsjjpk2nso2654jr2............. | Exadata.X8M | ACTIVE | 2026-04-07T12:03:56.898000+00:00 | INCREMENTAL | 19.26.0.0.0 |
| Automatic Backup | 24.01171875       | ocid1.dbbackup.oc1.phx.anyhqljtbv62

Finding the most recent backup

In this example, I am limiting the command to extract only daily backups (no long term backups), sort by the execution time, and return only the first record.

This command can be also be used in any scripting to ensure you are cloning from the most recent backup.

oci db backup list \
  --database-id {DB OCID} \
--output table \ --query "data[?\"retention-period-in-days\" == \`null\` && \"retention-period-in-years\" == \`null\` && \"lifecycle-state\" == 'ACTIVE'] | sort_by(@, &\"time-started\")[-1].{Backup_Name: \"display-name\", Time_Started: \"time-started\", Status: \"lifecycle-state\", Version: \"version\", OCID: \"id\", Database_size_GBs: \"database-size-in-gbs\",Shape: \"shape\",Type: \"type\"}"

Executing this slice pattern evaluates down to a single, isolated record representing your absolute most recent backup:

+------------------+-------------------+-------------------------------------------------------------------------------------+-------------+--------+----------------------------------+-------------+-------------+
| Backup_Name      | Database_size_GBs | OCID                                                                                | Shape       | Status | Time_Started                     | Type        | Version     |
+------------------+-------------------+-------------------------------------------------------------------------------------+-------------+--------+----------------------------------+-------------+-------------+
| Automatic Backup | 24.01171875       | ocid1.dbbackup.oc1.phx.anyhqljtbv6267iaufuy74ggl2bvf66czd75x3v4366qrwc............. | Exadata.X8M | ACTIVE | 2026-05-19T12:04:12.917000+00:00 | INCREMENTAL | 19.26.0.0.0 |
+------------------+-------------------+-------------------------------------------------------------------------------------+-------------+--------+----------------------------------+-------------+-------------+

Finding all of the long term backups

This command will display only backups that were creating with a future expiration date. This can be used to view all of the long term backups that were created for this database.

 oci db backup list \
>   --database-id {Database OCID} \
>   --output table \
>   --query "data[?\"time-expiry-scheduled\" != \`null\` && \"lifecycle-state\" == 'ACTIVE'] | sort_by(@, &\"time-started\")[].{Backup_Name: \"display-name\", Time_Bacdkup_Started: \"time-started\", Status: \"lifecycle-state\", Version: \"version\", Time_Backup_Expires: \"time-expiry-scheduled\", OCID: \"id\", Database_size_GBs: \"database-size-in-gbs\",Shape: \"shape\"}"


+------------------+-------------------+-------------------------------------------------------------------------------------+-------------+--------+----------------------------------+----------------------------------+-------------+
| Backup_Name      | Database_size_GBs | OCID                                                                                | Shape       | Status | Time_Bacdkup_Started             | Time_Backup_Expires              | Version     |
+------------------+-------------------+-------------------------------------------------------------------------------------+-------------+--------+----------------------------------+----------------------------------+-------------+
| Long_term_backup | 21.0              | ocid1.dbbackup.oc1.phx.anyhqljtbv6267iaskl3otjhgovkhvefzcjzckdg3v5sfwfybglhrl2mqz7a | Exadata.X8M | ACTIVE | 2026-05-14T14:39:17.539000+00:00 | 2027-05-14T14:39:18.871000+00:00 | 19.26.0.0.0 |
+------------------+-------------------+-------------------------------------------------------------------------------------+-------------+--------+----------------------------------+----------------------------------+-------------+

Friday, April 10, 2026

Automating cloning of your Exadata Database Service on Dedicated Infrastructure database

One of the questions I often get from customers is 

"How do I automate the cloning of a production database backup to a non-prod copy?  This is something we do often."

There are three different OCI commands  to do what seems like the exact same thing. Specifically, when it comes to restoring a database from a backup, the OCI CLI gives us three primary paths.

The "secret sauce" to choosing the right command is understanding where the database is going and ensuring you have the right OCIDs for your target infrastructure. Let's break down the full parameter sets you need to keep your automation from failing.

These are all "oci db database " commands


The Restore Matrix: Choosing Your Command

Command Infrastructure Target Required Target ID Primary Use Case
create-database-from-backup Exadata / C@C --db-home-id Restoring into an existing Exadata Home.
create-from-backup Base DB (VM/BM) --db-system-id Adding a DB to an existing DB System.
create --source DB_BACKUP Base DB (VM/BM) --compartment-id Building a NEW DB System from a backup.

1. The Exadata Full Set: create-database-from-backup

This command uses a JSON object for the --database flag. This is where you define the identity of the clone within the Exadata rack along with the backup you want to use to create the new database.

{
  "adminPassword": "YourPassword123#",
  "backupId": "ocid1.dbbackup.oc1...",
  "backupTDEPassword": "SourceWalletPassword",
  "dbName": "EXACLON",
  "dbUniqueName": "EXACLON_PRD",
  "sidPrefix": "EXACL",
  "pluggableDatabases": ["PDB1", "PDB2"],
  "dbHomeId": "ocid1.dbhome.oc1...",
  "storageSizeDetails": {
    "dataStorageSizeInGBs": 256,
    "recoStorageSizeInGBs": 512
  },
  "sourceEncryptionKeyLocationDetails": {
    "providerType": "AWS|AZURE|GCP|EXTERNAL",
    "awsEncryptionKeyId": "string",
    "hsmPassword": "string"
  }
}

2. The VM/BM In-Place Clone: create-from-backup

`

This is for standard Virtual Machine shapes. You must provide the dbSystemId (the OCID of the VM) to tell OCI exactly where to deploy the restored data.

{
  "adminPassword": "NewAdminPassword123#",
  "backupId": "ocid1.dbbackup.oc1...",
  "backupTdePassword": "SourceWalletPassword",
  "dbSystemId": "ocid1.dbsystem.oc1.iad.example_vm_ocid",
  "dbName": "VMCLON",
  "dbUniqueName": "VMCLON_DEV",
  "sidPrefix": "VMCL",
  "kmsKeyId": "ocid1.key.oc1...",
  "dataStorageSizeInGbs": 256,
  "recoStorageSizeInGbs": 512,
  "databaseSoftwareImageId": "ocid1.dbsoftwareimage.oc1...",
  "isUnifiedAuditingEnabled": true,
  "waitForState": ["AVAILABLE"],
  "maxWaitSeconds": 3600
}

3. Provisioning New Infra: create with --source

This is the "All-In-One" command. It creates the VM Cluster or DB System infrastructure from scratch. Because of this, it requires networking IDs (VCN/Subnet) and hardware shapes.

{
  "source": "DB_BACKUP",
  "backupId": "ocid1.dbbackup.oc1...",
  "tdeWalletPassword": "SourceWalletPassword",
  "compartmentId": "ocid1.compartment.oc1...",
  "subnetId": "ocid1.subnet.oc1...",
  "vmClusterId": "ocid1.vmcluster.oc1...",
  "dbSystemId": "ocid1.dbsystem.oc1...",
  "dbHomeId": "ocid1.dbhome.oc1...",
  "dbName": "NEWDB",
  "dbUniqueName": "NEWDB_U",
  "shape": "VM.Standard.E4.Flex",
  "vaultId": "ocid1.vault.oc1...",
  "kmsKeyId": "ocid1.key.oc1...",
  "dbWorkload": "OLTP",
  "autoBackupEnabled": true,
  "waitForState": ["AVAILABLE"]
}

Key Identification Checklist:
  • Exadata: You must have the --db-home-id of an existing home on the rack.
  • VM In-Place: You need the --db-system-id of the running VM instance.
  • Identity: Every command requires a dbName (8 chars max) and dbUniqueName. For automation, use the sidPrefix to prevent instance ID collisions.



Example from my tenancy

This example shows the command I am using in my tenancy to clone a database

{
  oci db database create \
  --config-file          /home/opc/clone/config \		#--> My OCI authentication config file
  --profile              DEFAULT \				#--> Entry in the config file to use credentials for		
  --region               us-phoenix-1 \				#--> Region I am connecting to execute (source region)
  --source               DB_BACKUP \				#--> Source for the new database is a DB_BACKUP
  --db-home-id           ocid1.dbhome.{...} \			#--> Target home OCID to create new DB
  --vm-cluster-id        ocid1.cloudvmcluster.{...} \		#--> Target VM OCID to create the new DB in
  --admin-password       "$_ADMIN_PW" \				#--> Target DB admin password when creating
  --from-json            file:///{file location}/xx.json	#--> JSON input file 


}

Example from my tenancy (cont)

This is the contents of the .json input file

{
  "source": "DB_BACKUP",				#--> Source is a DB_BACKUP
  "dbHomeId": "ocid1.dbhome.{...}",			#--> Target DB Home OCID
  "database": {
    "backupId": "ocid1.dbbackup.{...}",		        #--> Backup OCID to create database from
    "dbName": "BGRENNC",				#--> New DB name
    "dbUniqueName": "bgrennc_clone",		        #--> New DB Unique name
    "adminPassword": "dd",				#--> New DB Admin password (new TDE password will be the same)
    "backupTDEPassword": "dd",				#--> Original TDE wallet password
    "dbBackupConfig": {
      "autoBackupEnabled": true,			#--> Configure automatic backups
      "recoveryWindowInDays": 30			#--> Set recovery window for new backups
    },
    "definedTags": {
      "Oracle-Tags": {
        "CostType": "Shared"
      }
    }
  }
}


Mastering TDE & Key Management

One of the biggest hurdles in database cloning is handling the Transparent Data Encryption (TDE) layer. If your source backup was encrypted using a key from a different cloud provider or a local HSM, you must tell OCI how to decrypt it during the restore process.

1. Cross-Cloud & External Key Providers

When using the create-database-from-backup command (Exadata), you use the sourceEncryptionKeyLocationDetails parameter. This is a JSON object where you must specify the providerType and the corresponding Key OCID or ID from the source provider.

Provider Type Parameter Required Description
AWS awsEncryptionKeyId The ARN of the AWS KMS key used on the source.
AZURE azureEncryptionKeyId The Azure Key Vault key URI.
GCP googleCloudProviderEncryptionKeyId The fully qualified resource name of the GCP KMS key.
EXTERNAL hsmPassword Used for backups protected by an on-premises Hardware Security Module.

2. Native OCI Vault Integration

For native OCI restores, you have two choices: use the standard Oracle-managed keys (default) or use your own keys via OCI Vault (KMS). If you want to use your own keys, you must provide the kmsKeyId and, in some cases, the vaultId.

  • kmsKeyId: The OCID of the Master Encryption Key in the OCI Vault.
  • kmsKeyVersionId: (Optional) Use this if you need to pin the restore to a specific version of your key.
  • vaultId: Required by the create command to identify which Vault the key resides in.
Important Security Note: If you are restoring a database into a different compartment or tenancy than the source, your Dynamic Group for the target DB System must have READ and USE permissions for the Vault and Key. Without these IAM policies, the restore will fail immediately with a "Not Authorized" error.

By correctly mapping these key parameters, you ensure that your data remains encrypted and compliant throughout its entire lifecycle, even as it moves across cloud boundaries.


Automating with Infrastructure as Code (Terraform)

While the CLI is great for one-off tasks, most of my customers eventually want to bake these clones into their CI/CD pipelines. In Terraform, we use the oci_database_database resource. The "magic" happens in the source attribute and the database_details block.

resource "oci_database_database" "cloned_db" {
    # This maps to the --source flag in the CLI
    source = "DB_BACKUP"

    database {
        admin_password      = var.database_admin_password
        db_name             = "CLONEDB"
        db_unique_name      = "CLONEDB_IAD"
        character_set       = "AL32UTF8"
        ncharacter_set      = "AL16UTF16"
        db_workload         = "OLTP"
        
        # TDE Management
        tde_wallet_password = var.source_tde_password
        kms_key_id          = var.target_vault_key_ocid
    }

    # Target Infrastructure IDs
    db_home_id   = var.target_db_home_ocid
    database_id  = var.source_database_backup_ocid

    # Best Practice: Ignore password changes after initial provision
    lifecycle {
        ignore_changes = [database[0].admin_password]
    }
}

Terraform Pro-Tip: Always use the ignore_changes lifecycle hook for the admin_password. Once the database is restored, security policies often require a password rotation. Without this hook, Terraform will try to revert the password to the plain-text value in your .tfvars every time you run an update!