The OCI command-line interface (oci) and the Exadata/DB System management CLI (dbaascli) are the tools of choice for automating Oracle database operations on OCI. This article covers real-world scenarios with complete, runnable commands — including the complex case of creating a Data Guard standby when the primary database uses Oracle Key Vault (OKV) for TDE key management.


Reference Environment

All commands use the following environment:

Region:              eu-frankfurt-1
Compartment:         prod-compartment  (OCID: ocid1.compartment.oc1..aaaaxxxx)

Primary DB System:   prod-db-system
  Hostname:          proddb01.prod.subnet.vcn.oraclevcn.com
  Database:          PRODCDB (CDB + PDB: PDB_PROD)
  Shape:             VM.Standard.E4.Flex (4 OCPUs, 60 GB RAM)
  DB Version:        19.22.0.0
  DB Home OCID:      ocid1.dbhome.oc1.eu-frankfurt-1.aaaaxxxx
  Database OCID:     ocid1.database.oc1.eu-frankfurt-1.aaaaxxxx

Standby DB System:   standby-db-system  (to be created)
  Hostname:          stbydb01.stby.subnet.vcn.oraclevcn.com
  AD:                AD-2 (primary is AD-1)

OKV Server:          okv01.prod.example.com  (port 5695)
  OKV Wallet:        /etc/oracle/okv/  (on each DB System compute node)

Jump/Admin Host:     admin01.prod.example.com
  OCI CLI version:   3.x.x
  Python:            3.9

Part 1: OCI-CLI — Foundation Commands

Install and Configure OCI-CLI

# [[email protected] ~]
# Install OCI CLI
bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh)"

# Configure with API key authentication
oci setup config
# Prompts:
#   User OCID:        ocid1.user.oc1..aaaaxxxx
#   Tenancy OCID:     ocid1.tenancy.oc1..aaaaxxxx
#   Region:           eu-frankfurt-1
#   Generate new API key pair: Y
#   Key file location: ~/.oci/oci_api_key.pem

# Upload the generated public key to OCI Console: Identity → Users → API Keys → Add

Useful OCI-CLI Shortcut: Environment Variables

# [[email protected] ~]
# Set frequently used OCIDs as environment variables
export OCI_COMPARTMENT=ocid1.compartment.oc1..aaaaxxxx
export OCI_PRIMARY_DB_SYSTEM=ocid1.dbsystem.oc1.eu-frankfurt-1.aaaaxxxx
export OCI_PRIMARY_DB=ocid1.database.oc1.eu-frankfurt-1.aaaaxxxx
export OCI_PRIMARY_DB_HOME=ocid1.dbhome.oc1.eu-frankfurt-1.aaaaxxxx
export OCI_REGION=eu-frankfurt-1

# Also useful: set output format to table for human-readable output
export OCI_CLI_TABLE_OUTPUT_STYLE=table

Part 2: Creating a Data Guard Standby via OCI-CLI

Step 1: Verify Primary Database Status

# [[email protected] ~]
oci db database get \
  --database-id $OCI_PRIMARY_DB \
  --query 'data.{Name:"db-name", State:"lifecycle-state", "DB Unique Name":"db-unique-name", Version:"db-version"}' \
  --output table
+-------------------+--------+--------------------+-----------+
| DB Unique Name    | Name   | State              | Version   |
+-------------------+--------+--------------------+-----------+
| PRODCDB           | PRODCDB| AVAILABLE          | 19.22.0.0 |
+-------------------+--------+--------------------+-----------+

Step 2: Get the Subnet OCID for the Standby AD

# [[email protected] ~]
# Find the standby subnet in AD-2
oci network subnet list \
  --compartment-id $OCI_COMPARTMENT \
  --query 'data[?contains("display-name",`stby`)].{Name:"display-name", OCID:id, AD:"availability-domain"}' \
  --output table

Step 3: Create the Data Guard Association (New DB System)

This single command provisions the standby DB System, configures redo transport, and establishes Data Guard:

# [[email protected] ~]
oci db data-guard-association create with-new-db-system \
  --database-id $OCI_PRIMARY_DB \
  --creation-type "NewDbSystem" \
  --database-admin-password "StrongPass1#2026" \
  --protection-mode "MAXIMUM_AVAILABILITY" \
  --transport-type "SYNC" \
  --display-name "PRODCDB-Standby" \
  --hostname "stbydb01" \
  --shape "VM.Standard.E4.Flex" \
  --cpu-core-count 4 \
  --data-storage-size-in-gbs 512 \
  --availability-domain "cKmO:EU-FRANKFURT-1-AD-2" \
  --subnet-id "ocid1.subnet.oc1.eu-frankfurt-1.aaaaxxxx-stby" \
  --node-count 1 \
  --time-zone "Europe/Berlin" \
  --license-model "LICENSE_INCLUDED" \
  --is-active-data-guard-enabled true \
  --peer-db-unique-name "PRODCDB_STBY"

Step 4: Monitor Standby Provisioning

Provisioning takes 20–40 minutes. Poll until lifecycle-state is AVAILABLE:

# [[email protected] ~]
# Get the Data Guard Association OCID from the output of the previous command
DG_ASSOC_OCID="ocid1.dgassociation.oc1.eu-frankfurt-1.aaaaxxxx"

watch -n 30 "oci db data-guard-association get \
  --database-id $OCI_PRIMARY_DB \
  --data-guard-association-id $DG_ASSOC_OCID \
  --query 'data.{Status:\"lifecycle-state\", \"DG Role\":role, \"Peer DB\":\"peer-db-unique-name\", \"Lag Secs\":\"apply-lag\"}' \
  --output table"
+-----------+----------+------------------+-----------+
| DG Role   | Lag Secs | Peer DB          | Status    |
+-----------+----------+------------------+-----------+
| PRIMARY   | 0        | PRODCDB_STBY     | AVAILABLE |
+-----------+----------+------------------+-----------+

Part 3: Data Guard Standby Creation When Primary Uses OKV

When the primary database uses Oracle Key Vault (OKV) for TDE master key management, the standby must be enrolled with the same OKV server before redo apply can start. Without OKV connectivity, the standby cannot decrypt redo containing encrypted tablespace data.

Architecture with OKV

Primary (proddb01)  ──SYNC redo──►  Standby (stbydb01)
       │                                    │
       └──────────── OKV ─────────────────►└── (same OKV server/wallet)
              okv01.prod.example.com:5695

Step 1: Install OKV Client on the Standby Compute Node

The OCI-provisioned standby compute node does not automatically have the OKV client installed. Connect to the standby via SSH:

# [[email protected] ~]
# Find the standby DB System's compute node public/private IP
oci db db-node list \
  --db-system-id "ocid1.dbsystem.oc1.eu-frankfurt-1.aaaaxxxx-stby" \
  --query 'data[0].{Hostname:"hostname", "Private IP":"private-ip-address", State:"lifecycle-state"}' \
  --output table

# SSH to the standby node
ssh [email protected]
# [[email protected] ~]
# Switch to root and install OKV client
sudo su -

# Download OKV client from OKV server (or from internal repository)
# The okvutil binary comes from the OKV client install package
# Transfer from admin host if needed:
scp [email protected]:/u01/software/okv_client_21.9.zip /tmp/
unzip /tmp/okv_client_21.9.zip -d /opt/okv_install/
cd /opt/okv_install/
./install.sh
# Follow prompts:
#   OKV Server: okv01.prod.example.com
#   OKV Port:   5695
#   Install dir: /etc/oracle/okv

Step 2: Enrol the Standby with OKV

# [[email protected] ~]
# Enrol the standby database endpoint with OKV
okvutil enroll \
  --okv-server okv01.prod.example.com:5695 \
  --okv-endpoint stbydb01-prodcdb \
  --password <okv_admin_password>

Step 3: Configure sqlnet.ora and Wallet on the Standby

# [[email protected] ~]
# Configure sqlnet.ora to use OKV keystore
cat >> $ORACLE_HOME/network/admin/sqlnet.ora << 'EOF'
ENCRYPTION_WALLET_LOCATION =
  (SOURCE =
    (METHOD = OKV)
    (METHOD_DATA =
      (DIRECTORY = /etc/oracle/okv)))
EOF

Step 4: Open the Keystore on the Standby

-- [[email protected] ~]
-- Connect to the standby as SYSDBA
sqlplus / as sysdba

-- Open the OKV keystore on the standby (mount mode — before redo apply)
ADMINISTER KEY MANAGEMENT SET KEYSTORE OPEN
  IDENTIFIED BY EXTERNAL STORE
  CONTAINER = ALL;

-- Verify keystore is open
SELECT wrl_type, wrl_parameter, status, wallet_type, con_id
FROM v$encryption_wallet;
WRL_TYPE WRL_PARAMETER        STATUS  WALLET_TYPE  CON_ID
-------- -------------------- ------- ------------ ------
OKV      /etc/oracle/okv/     OPEN    OKV               0
OKV      /etc/oracle/okv/     OPEN    OKV               1
OKV      /etc/oracle/okv/     OPEN    OKV               3

Step 5: Start Managed Recovery on the Standby

-- [[email protected] ~] as sysdba
-- Now that OKV is open, start redo apply
ALTER DATABASE RECOVER MANAGED STANDBY DATABASE USING CURRENT LOGFILE DISCONNECT FROM SESSION;

-- Verify apply is running
SELECT process, status, thread#, sequence#
FROM v$managed_standby
ORDER BY process;

Step 6: Automate OKV Keystore Open on Standby Startup

Add an OKV keystore open to the standby startup trigger to ensure it always opens automatically after a DB restart:

-- Create a STARTUP trigger to open OKV keystore automatically
CREATE OR REPLACE TRIGGER open_okv_on_startup
  AFTER STARTUP ON DATABASE
BEGIN
  EXECUTE IMMEDIATE 'ADMINISTER KEY MANAGEMENT SET KEYSTORE OPEN
    IDENTIFIED BY EXTERNAL STORE CONTAINER = ALL';
END;
/

Part 4: dbaascli — OCI DB System Management CLI

dbaascli runs on the OCI DB System compute node itself (not on the admin host). It manages database lifecycle operations — patching, backup, switchover — at the OS level.

Connect to the DB System

# [[email protected] ~]
ssh [email protected]
sudo su - oracle

Check Database and DB System Status

# [[email protected] ~]
dbaascli database list
{
  "databases": [
    {
      "uniqueName": "PRODCDB",
      "status": "RUNNING",
      "oracleHome": "/u01/app/oracle/product/19.0.0/dbhome_1",
      "version": "19.22.0.0"
    }
  ]
}

Apply a Database Patch via dbaascli

# [[email protected] ~]
# List available patches for the current DB Home
dbaascli dbhome queryPatches --dbHomeDirectoryPath /u01/app/oracle/product/19.0.0/dbhome_1

# Apply the January 2026 Release Update
dbaascli dbhome patch \
  --dbHomeDirectoryPath /u01/app/oracle/product/19.0.0/dbhome_1 \
  --patchId 37056207 \
  --action apply \
  --dbName PRODCDB

# Monitor patch progress
dbaascli job show --jobid <job_id_from_previous_output>

Backup a Database via dbaascli

# [[email protected] ~]
# Create an on-demand backup to OCI Object Storage
dbaascli database backup \
  --dbName PRODCDB \
  --backupType INCREMENTAL \
  --tag "MANUAL_BACKUP_$(date +%Y%m%d)"

# List recent backups
dbaascli database listBackups --dbName PRODCDB

Data Guard Switchover via dbaascli

# [[email protected] ~]
# Perform a planned switchover (primary → standby)
dbaascli database switchover \
  --dbName PRODCDB \
  --targetDbName PRODCDB_STBY

# Monitor switchover status
dbaascli job show --jobid <switchover_job_id>

Part 5: Useful Automation Scripts

Script 1: Daily Data Guard Lag Report

#!/bin/bash
# [[email protected] ~]
# File: /u01/scripts/dg_lag_report.sh
# Purpose: Query Data Guard associations and report lag to Slack/email

OCI_DB_ID="ocid1.database.oc1.eu-frankfurt-1.aaaaxxxx"
DG_ASSOC_ID="ocid1.dgassociation.oc1.eu-frankfurt-1.aaaaxxxx"
REPORT_DATE=$(date '+%Y-%m-%d %H:%M:%S')

echo "=== Data Guard Lag Report: $REPORT_DATE ==="

oci db data-guard-association get \
  --database-id "$OCI_DB_ID" \
  --data-guard-association-id "$DG_ASSOC_ID" \
  --query 'data.{"Apply Lag":"apply-lag",
                  "Transport Lag":"transport-lag",
                  "Status":"lifecycle-state",
                  "Role":role,
                  "Peer Role":"peer-role",
                  "Protection Mode":"protection-mode"}' \
  --output table

# Alert if apply lag > 5 minutes
LAG=$(oci db data-guard-association get \
  --database-id "$OCI_DB_ID" \
  --data-guard-association-id "$DG_ASSOC_ID" \
  --query 'data."apply-lag"' \
  --raw-output)

if [[ "$LAG" > "+00 00:05:00" ]]; then
  echo "WARNING: Apply lag ($LAG) exceeds 5 minutes!" | \
    mail -s "ALERT: DG Lag Warning - proddb01" [email protected]
fi
# Schedule as cron job on admin01:
crontab -e
# Add:
# */15 * * * * /u01/scripts/dg_lag_report.sh >> /u01/logs/dg_lag.log 2>&1

Script 2: OCI DB System Health Check

#!/bin/bash
# [[email protected] ~]
# File: /u01/scripts/oci_db_health.sh
# Purpose: Quick health snapshot of all DB Systems in a compartment

COMPARTMENT="ocid1.compartment.oc1..aaaaxxxx"

echo "=== OCI Database System Health: $(date) ==="
echo ""

echo "-- DB Systems --"
oci db system list \
  --compartment-id "$COMPARTMENT" \
  --query 'data[*].{"Name":"display-name", "Shape":shape, "State":"lifecycle-state", "Nodes":"node-count"}' \
  --output table

echo ""
echo "-- Databases --"
oci db database list \
  --compartment-id "$COMPARTMENT" \
  --query 'data[*].{"DB Name":"db-name", "Unique Name":"db-unique-name", "State":"lifecycle-state", "Version":"db-version"}' \
  --output table

echo ""
echo "-- Work Requests (last 24h) --"
oci work-requests work-request list \
  --compartment-id "$COMPARTMENT" \
  --query 'data[?contains(`["ACCEPTED","IN_PROGRESS","CANCELING"]`, status)].{"Operation":"operation-type", "Status":status, "Percent":("percent-complete"|to_string(@))}' \
  --output table

Script 3: Automated Patch Application with Pre/Post Checks

#!/bin/bash
# [[email protected] ~]
# File: /u01/scripts/apply_patch.sh
# Usage: ./apply_patch.sh <patch_id> <dbhome_path> <db_name>

PATCH_ID=$1
DBHOME=$2
DB_NAME=$3
LOG_DIR="/u01/logs/patching"
LOG_FILE="$LOG_DIR/patch_${PATCH_ID}_$(date +%Y%m%d_%H%M%S).log"
mkdir -p "$LOG_DIR"

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"; }

log "=== Starting patch $PATCH_ID for $DB_NAME ==="
log "DB Home: $DBHOME"

# Pre-patch: capture baseline performance metrics
log "Capturing pre-patch AWR snapshot..."
sqlplus -S / as sysdba << EOF >> "$LOG_FILE"
EXEC DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT();
SELECT snap_id, begin_interval_time FROM dba_hist_snapshot WHERE rownum = 1 ORDER BY snap_id DESC;
EXIT;
EOF

# Pre-patch: validate database
log "Running pre-patch database validation..."
dbaascli database precheck \
  --dbName "$DB_NAME" >> "$LOG_FILE" 2>&1

if [ $? -ne 0 ]; then
  log "ERROR: Pre-patch validation failed. Aborting."
  exit 1
fi

# Apply patch
log "Applying patch $PATCH_ID..."
dbaascli dbhome patch \
  --dbHomeDirectoryPath "$DBHOME" \
  --patchId "$PATCH_ID" \
  --action apply \
  --dbName "$DB_NAME" >> "$LOG_FILE" 2>&1

PATCH_STATUS=$?
log "Patch exit code: $PATCH_STATUS"

# Post-patch: AWR snapshot
log "Capturing post-patch AWR snapshot..."
sqlplus -S / as sysdba << EOF >> "$LOG_FILE"
EXEC DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT();
EXIT;
EOF

# Post-patch: check for invalid objects
log "Checking for invalid objects post-patch..."
sqlplus -S / as sysdba << EOF | tee -a "$LOG_FILE"
SET PAGESIZE 100 LINESIZE 200
SELECT owner, object_name, object_type, status
FROM dba_objects
WHERE status = 'INVALID'
  AND owner NOT IN ('SYS','SYSTEM','DBSNMP','XDB')
ORDER BY owner, object_name;
EXIT;
EOF

log "=== Patch $PATCH_ID completed. Log: $LOG_FILE ==="

# Email report
if command -v mail &>/dev/null; then
  mail -s "Patch $PATCH_ID Applied to $DB_NAME" \
       [email protected] < "$LOG_FILE"
fi

Script 4: OKV Keystore Status Check Across All PDBs

# [[email protected] ~]
# Quick OKV wallet status across all containers
sqlplus -S / as sysdba << 'EOF'
SET LINESIZE 120 PAGESIZE 50
COLUMN wrl_type     FORMAT A10
COLUMN wrl_parameter FORMAT A30
COLUMN status       FORMAT A10
COLUMN wallet_type  FORMAT A12
COLUMN con_id       FORMAT 999

SELECT wrl_type,
       wrl_parameter,
       status,
       wallet_type,
       con_id
FROM v\$encryption_wallet
ORDER BY con_id;

-- Alert if any container is not OPEN
SELECT 'WARNING: Container ' || con_id || ' wallet status is ' || status AS alert
FROM v\$encryption_wallet
WHERE status != 'OPEN';
EOF

Script 5: List All DB Systems and Their Data Guard Associations

#!/bin/bash
# [[email protected] ~]
# File: /u01/scripts/dg_inventory.sh
# Purpose: Full Data Guard topology report

COMPARTMENT="ocid1.compartment.oc1..aaaaxxxx"

echo "=== Data Guard Topology Report: $(date) ==="

# Get all databases
DB_IDS=$(oci db database list \
  --compartment-id "$COMPARTMENT" \
  --query 'data[*].id' \
  --raw-output | tr -d '[]"' | tr ',' '\n')

for DB_ID in $DB_IDS; do
  DB_NAME=$(oci db database get --database-id "$DB_ID" --query 'data."db-unique-name"' --raw-output 2>/dev/null)

  # Check for DG associations
  DG_ASSOCS=$(oci db data-guard-association list \
    --database-id "$DB_ID" \
    --query 'data[*].{Role:role,"Peer DB":"peer-db-unique-name","Protection Mode":"protection-mode","Apply Lag":"apply-lag",Status:"lifecycle-state"}' \
    --output table 2>/dev/null)

  if [ -n "$DG_ASSOCS" ] && echo "$DG_ASSOCS" | grep -q "PRIMARY\|STANDBY"; then
    echo ""
    echo "Database: $DB_NAME"
    echo "$DG_ASSOCS"
  fi
done

Part 6: OCI CLI for Backup Management

Create and Manage Manual Backups

# [[email protected] ~]
# Create on-demand backup
oci db backup create \
  --database-id $OCI_PRIMARY_DB \
  --display-name "pre-patch-backup-$(date +%Y%m%d)" \
  --type FULL

# List last 5 backups
oci db backup list \
  --database-id $OCI_PRIMARY_DB \
  --sort-by TIMECREATED \
  --sort-order DESC \
  --limit 5 \
  --query 'data[*].{"Backup Name":"display-name","Type":type,"State":"lifecycle-state","Time":"time-ended","Size GB":"database-size-in-gbs"}' \
  --output table

Restore from Backup (Point-in-Time Recovery)

# [[email protected] ~]
# Restore database to a specific timestamp (point-in-time recovery)
oci db database restore \
  --database-id $OCI_PRIMARY_DB \
  --timestamp "2026-03-01T08:00:00.000Z" \
  --latest false