Keeping your ExaCS clusters up to date is important. In this article I will provide some basic steps on what to do. I made an interactive script so I can do the whole patch cycle in my terminal without clicking through the console. It lets me choose the environment, which update I would like to precheck/apply, and provides me the status of the patch.
This can be helpful if not every employee can access the OCI console but needs to patch the system. For example your DBA or Service Desk Engineer can do it without any special tools — just run the script!
Prerequisites
Make sure you have a Linux machine installed with oci-cli and jq. In this case I have an ExaCS infrastructure with 4 different clusters — one for each DTAP (Development, Test, Acceptance, Production) environment. The goal is to keep all environments the same.
Declaring variables
Start by declaring all variables you need: the OCIDs for all ExaCS VM clusters and today’s date in UTC.
# Make sure to enter your OCIDs
DV_OCID="ocid1.cloudvmcluster.oc1.eu-amsterdam-1.xxxxxx"
TS_OCID="ocid1.cloudvmcluster.oc1.eu-amsterdam-1.xxxxxx"
AC_OCID="ocid1.cloudvmcluster.oc1.eu-amsterdam-1.xxxxxx"
PR_OCID="ocid1.cloudvmcluster.oc1.eu-amsterdam-1.xxxxxx"
# Current date in UTC
TODAY=$(date -u +%F) # e.g. "2025-09-04"
Fetching available patches
Fetch all patches available for a cluster and format the JSON output as a readable table:
PATCH_LIST=$(oci db cloud-vm-cluster list-updates \
--cloud-vm-cluster-id "$DV_OCID" \
--all \
--query "data[*].{id:id,description:description,version:version,release:\"time-released\",action:\"available-actions\"}" \
--output table)
echo "$PATCH_LIST"
The result is a table with patch OCID, description, version, release time and available action (ROLLING-APPLY/PRECHECK). Save the OCID of the patch you want to install.
Running the precheck
oci db cloud-vm-cluster update \
--cloud-vm-cluster-id "$DV_OCID" \
--update-id "[SAVED PATCH OCID]" \
--update-action PRECHECK \
--force
Monitor the precheck status with a polling loop. The script checks every 60 seconds for up to 60 minutes and stops when the lifecycle state reaches SUCCEEDED or FAILED:
precheck_status() {
local max_attempts="${2:-60}"
local interval=60
local attempt=1
echo "Waiting for patch state to reach status SUCCEEDED..."
while (( attempt <= max_attempts )); do
vm_patch_status=$(oci db cloud-vm-cluster list-update-histories \
--cloud-vm-cluster-id "$DV_OCID" \
--output json | jq -r \
--arg today "$TODAY" \
'.data[] | select(."update-id" == "[SAVED_PATCH_ID]") | select(."update-action" == "PRECHECK") | select(."time-started" | startswith($today)) | ."lifecycle-state"')
echo "Status is '$vm_patch_status'"
if [[ "$vm_patch_status" == "SUCCEEDED" ]]; then
echo "Precheck is successful!"
run_vm_apply
elif [[ "$vm_patch_status" == "FAILED" ]]; then
echo "Patch failed the precheck. Please check the console for details."
exit 0
fi
((attempt++))
sleep "$interval"
done
echo "Timeout reached after $((max_attempts * interval / 60)) minutes."
exit 0
}
Applying the patch
Assuming the precheck succeeds, apply the patch by swapping PRECHECK for APPLY:
oci db cloud-vm-cluster update \
--cloud-vm-cluster-id "$CLUSTER_OCID" \
--update-id "[SAVED PATCH OCID]" \
--update-action APPLY \
--force
Then run the same polling loop, filtering on APPLY instead of PRECHECK:
apply_status() {
local max_attempts="${2:-60}"
local interval=60
local attempt=1
echo "Waiting for patch state to reach status SUCCEEDED..."
while (( attempt <= max_attempts )); do
vm_apply_status=$(oci db cloud-vm-cluster list-update-histories \
--cloud-vm-cluster-id "$DV_OCID" \
--output json | jq -r \
--arg today "$TODAY" \
'.data[] | select(."update-id" == "[SAVED_PATCH_ID]") | select(."update-action" == "APPLY") | select(."time-started" | startswith($today)) | ."lifecycle-state"')
echo "Status is '$vm_apply_status'"
if [[ "$vm_apply_status" == "SUCCEEDED" ]]; then
echo "Patch has successfully been applied!"
exit 0
elif [[ "$vm_apply_status" == "FAILED" ]]; then
echo "Patch failed to apply. Please check the console for details."
exit 0
fi
((attempt++))
sleep "$interval"
done
echo "Timeout reached after $((max_attempts * interval / 60)) minutes."
exit 0
}
When the patch is installed successfully it will confirm this. With some bash scripting skills you can combine everything into one single command and patch all your DTAP environments in one go. If you would like details on the full script, don’t hesitate to reach out!