APEX + DB Operator: Deploying Customer Managed ORDS for ADB with the Oracle Database Operator on OKE


When you provision an Autonomous Database instance, by default Oracle REST Data Services (ORDS) is preconfigured and available for the instance. When you use the default ORDS on Autonomous Database, you cannot modify any of the ORDS configuration options.
About Customer Managed Oracle REST Data Services on Autonomous Database
Use a customer managed environment if you want manual control of the configuration and management of Oracle REST Data Services. For example, use this option when your applications require larger connection pools or if you need more control over the ORDS configuration options.
When ORDS runs in a customer managed environment, you are responsible for configuration, patching, and maintenance of ORDS
See Installing and Configuring Customer Managed ORDS on Autonomous Database for more information.
Well, that’s according to the documentation and for most part customers should really test and verify that is required for this stateless component.
LOW
database service.Performance or specific ORDS configuration options may require a Customer Managed ORDS deployment.
About this Post
Irrespective of the reason, self-managing resources, installing, patching, scaling, etc. appears to be a bit counter to the cloud philosophy.
During a discussion with a Partner this was a topic of discussion, and though they had experience in deploying APEX and ORDS, improving efficiency through automation is a top mandate in today’s fast release cycles.
After making the recommendation to review the capabilities of the Oracle Database Operator (a.k.a Operator) and OCI Kubernetes Engine (OKE), which includes a controller for ORDS Services. I went through the steps to see if the rubber meets the road, so to speak.
Getting Started - Meet the PreReqs
I used OKE for my cluster environment along with the Operator itself and the container images to rollout this solution.
The Prerequisites should be somewhat clear but here are the highlights:
Configure OCI Credential, Dynamic Group, & Policies for all tasks and compartments as required. API Key authentication or granted with Instance Principal
Installation of the Oracle Database Operator into the cluster (ReadMe)
Create namespaces to group resources
Ensure that your Oracle Container Registry User, has a Token generated.
Create secret to store Container Registry Credentials (ReadMe)
Provisioned an Autonomous Database (this can also be provisioned with the Operator - example included)
Many of these steps have individual requirements, so follow the documentation for each of those. For more robust or better hardening follow the Best Practices for OKE to start.
The above is not a comprehensive list, so review and follow all of the documentation
PreReqs for the examples
I am using the sidb-ns namespace, create you own or use the ones provided in the Operator instructions.
# Create my custom namespace sidb-ns
kubectl create namespace sidb-ns
# Set this as the context default namespace just in case
kubectl config set-context --current --namespace=sidb-ns
Add the namespace to WATCH_NAMESPACE of the Operator
Create a YAML file to update/patch the operator configuration
# patch-oracle-database-operator.yaml
# Copyright (c) 2022, 2024, Oracle and/or its affiliates.
# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl.
#
apiVersion: apps/v1
kind: Deployment
metadata:
name: oracle-database-operator-controller-manager
namespace: oracle-database-operator-system
spec:
replicas: 1
selector:
matchLabels:
control-plane: controller-manager
template:
metadata:
labels:
control-plane: controller-manager
spec:
containers:
- args:
name: manager
env:
- name: WATCH_NAMESPACE
value: "default,sidb-ns"
image: container-registry.oracle.com/database/operator:1.2.0
Apply the configuration changes to the Operator
# Patch the running Operator with the WATCH_NAMESPACE configuration
kubectl apply -f patch-oracle-database-operator.yaml
Create secret to store Container Registry Credentials
Go to the Oracle Container Registry site and review the Alert message
To generate a Token, signin and select AuthToken from the dropdown menu under your username
This will take you to the page to manage or generate a Token
# Save the Secret, so you can donwload container images that require authorization
#Create container registry secret in the namespaces it will be used
kubectl create secret docker-registry ocr-reg-cred \
--docker-server=container-registry.oracle.com \
--docker-username='sydney.nurse@oracle.com' \
--docker-password='ImNotTellingYou' \
--docker-email='sydney.nurse@oracle.com' -n sidb-ns
# Don't forget to specify the namespace "-n YourNamespace"
Setup an Autonomous Database
The Operator supports the following operations:
Being pessimistic, I provisioned a new instance.
Provisioning an Autonomous Database
Follow the steps for the ADB_PREREQUISITES and Provision an Autonomous Database in your compartment
An example .yaml file is available here: config/samples/adb/autonomousdatabase_create.yaml
The following table is an excerpt from the ADB (ReadMe) describing the attributes. It takes several reads to understand the correct combination as so much details are provided and the sample uses cpuCoreCount which is no longer recommended.
Spec Attributes
spec.details.cpuCoreCount | int | The number of CPU cores to be made available to the database. | Note: This parameter cannot be used with the ocpuCount parameter. |
spec.details.computeModel | string | The compute model of the Autonomous Database, ECPU compute model is the recommended model and OCPU compute model is legacy |
If using cpuCoreCount then it is an error to specify computeModel to a non-null value. | **Note: Providing computeModel and computeCount is the preferred method for both OCPU and ECPU.
Note: This is required if using the computeCount parameter.** | | spec.details.computeCount | int | The compute amount (CPUs) available to the database.
For an Autonomous Database Serverless instance, the 'ECPU' compute model requires a minimum value of one
When using cpuCoreCount parameter, it is an error to specify computeCount to a non-null value. | Note: Providing computeModel and computeCount is the preferred method for both OCPU and ECPU.
Note: Required when using the computeModel parameter. | | spec.details.ocpuCount | float32 | The number of OCPU cores to be made available to the database.
- For Autonomous Database Serverless instances, this parameter is not used. | Note: This parameter cannot be used with the cpuCoreCount parameter. |
In the my YAML sample, I have added the custom namespace, supplied the reference to my K8S secret for the Admin password, and used computeModel and computeCount.
# adb_snodbodb23ai_create.yaml
# Copyright (c) 2022, 2024, Oracle and/or its affiliates.
# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl.
#
apiVersion: database.oracle.com/v1alpha1
kind: AutonomousDatabase
metadata:
name: snodbodb23ai
namespace: sidb-ns
spec:
action: Create
details:
# Update compartmentOCID with your compartment OCID.
compartmentId: ocid1.compartment.oc1..sydney.nurse
# The dbName must begin with an alphabetic character and can contain a maximum of 14 alphanumeric characters. Special characters are not permitted. The database name must be unique in the tenancy.
dbName: snodbodb23ai
displayName: snodbodb23ai
computeModel: ECPU
computeCount: 4
adminPassword:
# Comment out k8sSecret and uncomment ociSecret if you pass the admin password using OCI Secret.
k8sSecret:
# The Name of the K8s secret where you want to hold the password of the ADMIN account.
name: adb-admin-secret
# ociSecret:
# # The OCID of the OCI Secret that holds the password of the ADMIN account. It should start with ocid1.vaultsecret... .
# ocid: ocid1.vaultsecret...
dataStorageSizeInTBs: 1
# networkAccess:
# # Uncomment this block to configure the network access type with the PUBLIC option, which allows secure access from everywhere.
# accessType: PUBLIC
# # Uncomment this block to configure the network access type with the RESTRICTED option.
# # This option lets you restrict access by defining access control rules in an Access Control List (ACL).
# # By specifying an ACL, the database will be accessible from a whitelisted set of IP addresses, CIDR (Classless Inter-Domain Routing) blocks, or VCNs.
# # Use a semicolon (;) as a deliminator between the VCN-specific subnets or IPs.
# accessType: RESTRICTED
# accessControlList:
# - 1.1.1.1
# - 1.1.0.0/16
# - ocid1.vcn...
# - ocid1.vcn...;1.1.1.1
# - ocid1.vcn...;1.1.0.0/16
# isMTLSConnectionRequired: true
# # Uncomment this block to configure the network access type with the PRIVATE option.
# # This option assigns a private endpoint, private IP, and hostname to your database.
# # Specifying this option allows traffic only from the VCN you specify.
# # This allows you to define security rules, ingress/egress, at the Network Security Group (NSG) level and to control traffic to your Autonomous Database.
# accessType: PRIVATE
# privateEndpoint:
# subnetOCID: ocid1.subnet...
# nsgOCIDs:
# - ocid1.networksecuritygroup...
# isMTLSConnectionRequired: true
# # Uncomment this block to configure the network access of an dedicated Autonomous Database (ADB-D) with an access control list.
# isAccessControlEnabled: true
# accessControlList:
# - 1.1.1.1
# - 1.1.0.0/16
# Authorize the operator with API signing key pair. Comment out the ociConfig fields if your nodes are already authorized with instance principal.
#ociConfig:
## configMapName: oci-cred
# Comment out secretName if using OKE workload identity
## secretName: oci-privatekey
The instructions will have you create the secrete before running the apply.
# Create a Kubernetes secret to store the database Admin user password
kubectl create secret generic -n sidb-ns adb-admin-secret \
--from-literal=adb-admin-secret=...uSEaN_ADB_9a55W0rd!
# Provision ADB instance and create a ADB reference that can be used by the operator
kubectl apply -f adb_snodbodb23ai_create.yaml
# Watch the ADB instance in the namespace
kubectl get adb -n sidb-ns snodbodb23ai -w
NAME DISPLAY NAME DB NAME STATE DEDICATED OCPUS STORAGE (TB) WORKLOAD TYPE CREATED
snodbodb23ai snodbodb23ai snodbodb23ai AVAILABLE false 0 1 OLTP 2025-07-11 08:06:47 UTC
Get the ADB Wallet and Save it as a Secret
The Operator allows you to download the wallet to support client connections. This is needed for the remote or customer managed ORDS pool.
# adb_snodbodb23ai_getWallet.yaml
# Copyright (c) 2022, Oracle and/or its affiliates.
# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl.
#
apiVersion: database.oracle.com/v1alpha1
kind: AutonomousDatabase
metadata:
name: snodbodb23ai
namespace: sidb-ns
spec:
action: Update
details:
id: ocid1.autonomousdatabase.oc1.snodbodb23ai
wallet:
name: snodbodb23ai-wallet
password:
k8sSecret:
name: snodbodb23ai-wallet-password
# Authorize the operator with API signing key pair. Comment out the ociConfig fields if your nodes are already authorized with instance principal.
#ociConfig:
## configMapName: oci-cred
## secretName: oci-privatekey
# Create the secret to store the Wallet
kubectl create secret generic snodbodb23ai-wallet-password \
--from-literal=snodbodb23ai-wallet-password='Wallet-very53CR3T9a55' \
-n sidb-ns
# Download and save the ADB wallet in the secret
kubectl apply -f adb_snodbodb23ai_getWallet.yaml
Provisioning ORDS Services instance
The prerequisites finally completed, of course we could have simply ran this in the OCI console but what was the fun in that?!
The next step is to setup another YAML file for the ORDSSRVS by following the example ADB using the OraOperator where it creates the encrypted password.
# Encrypt and Store the DB User Passwords for ORDSSRVS
echo ${DB_PWD} > sidb-db-auth
openssl genpkey -algorithm RSA \
-pkeyopt rsa_keygen_bits:2048 \
-pkeyopt rsa_keygen_pubexp:65537 > ca.key
openssl rsa -in ca.key -outform PEM -pubout -out public.pem
# Save the Certificate as a Secret
kubectl create secret generic prvkey \
--from-file=privateKey=ca.key \
-n sidb-ns
openssl rsautl -encrypt -pubin -inkey public.pem \
-in sidb-db-auth |base64 > e_sidb-db-auth
# Save the Encrypted Password
kubectl create secret generic sidb-db-auth-enc \
--from-file=password=e_sidb-db-auth -n sidb-ns
# Remove the evidence
rm sidb-db-auth e_sidb-db-auth
The documentation retrieves the DB service name for the transactional processing with the expectations that these will be fast and short connections. I’ve added replicas for future testing and used a tns connection type.
Enumerated list of supported values can be found in the CRD file database.oracle.com_ordssrvs.yaml. For Production or load testing environments, review all the available attributes for the global caching & security, and pool. The image provides a minimal linux cli tooling, so editing the configuration, identifying processing or using normal cli will be limited.
# adb_snodbodb23ai_create_ordsSrvs.yaml
# Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl.
#
apiVersion: database.oracle.com/v4
#kind: OracleRestDataService
kind: OrdsSrvs
metadata:
name: snordsodbo
namespace: sidb-ns
spec:
## ORDS image details
image: container-registry.oracle.com/database/ords:25.2.0
imagePullSecrets: ocr-reg-cred
forceRestart: true
replicas: 1
encPrivKey:
secretName: prvkey
passwordKey: privateKey
globalSettings:
database.api.enabled: true
poolSettings:
- poolName: snodbodb23ai
restEnabledSql.active: true
feature.sdw: true
plsql.gateway.mode: proxied
jdbc.MaxConnectionReuseCount: 5000
jdbc.MaxConnectionReuseTime: 900
jdbc.SecondsToTrustIdleConnection: 1
jdbc.InitialLimit: 100
jdbc.MaxLimit: 100
db.connectionType: tns
db.tnsAliasName: snodbodb23ai_TP
tnsAdminSecret:
secretName: snodbodb23ai-wallet
db.username: ORDS_PUBLIC_USER_OPER
db.secret:
secretName: sidb-db-auth-enc
db.adminUser: ADMIN
db.adminUser.secret:
secretName: sidb-db-auth-enc
# Provision the Customer Managed ORDS instance
kubectl apply -f adb_snodbodb23ai_create_ordsSrvs.yaml
# Check the State
kubectl get ordssrvs -n sidb-ns snordsodbo
NAME STATUS WORKLOADTYPE ORDSVERSION HTTPPORT HTTPSPORT MONGOPORT RESTARTREQUIRED AGE ORDSINSTALLED
snordsodbo Healthy Deployment 25.2.0 8080 8443 false 2d1h true
# Status should updte from Preparing and eventually Healthy.
## If it stays in Preparing, most likely you will also not be listed as a POD
### 0.0 Oh No!!! Time ⏰ to Troubleshot!
# List the PODS
kubectl get pods -n sidb-ns
NAME READY STATUS RESTARTS AGE
snordsodbo-858b4576df-cd2sp 1/1 Running 0 2d1h
# Check the Operator POD logs
kubectl logs -f -n oracle-database-operator-system \
$(kubectl get pod -o name -n oracle-database-operator-system|tail -1)
# Check the POD init script logs
## kubectl logs -n <NAMESPACE> <ORDSSRVSPOD> -c <ordssrvs_name>-init
kubectl logs -n sidb-ns pod/snordsodbo-74bb4b7558-bm2ll -c snordsodbo-init
If things did work out then you can port forward the HTTPs port and test from localhost
Don’t be like me and forget to include the pool name in the url. If you do you will see this instead.
APEX works as well
Tips
Error in ConfigMapReconcile
2025-07-15T14:43:19Z INFO SetStatus Deployment not ready {"controller": "ordssrvs", "controllerGroup": "database.oracle.com", "controllerKind": "OrdsSrvs", "OrdsSrvs": {"name":"snordsodbo","namespace":"sidb-ns"}, "namespace": "sidb-ns", "name": "snordsodbo", "reconcileID": "a174c231-71f6-4bcf-a620-7cf220b6713d"}
2025-07-15T14:43:19Z ERROR Error in ConfigMapReconcile (init-script) {"controller": "ordssrvs", "controllerGroup": "database.oracle.com", "controllerKind": "OrdsSrvs", "OrdsSrvs": {"name":"snordsodbo","namespace":"sidb-ns"}, "namespace": "sidb-ns", "name": "snordsodbo", "reconcileID": "a174c231-71f6-4bcf-a620-7cf220b6713d", "error": "expected pointer, but got nil"}
github.com/oracle/oracle-database-operator/controllers/database.(*OrdsSrvsReconciler).Reconcile
/workspace/controllers/database/ordssrvs_controller.go:163
If this is found in the Operator log, run the delete for the create YAML file, check and/or update the Operator WATCH_NAMESPACE as described above.
# Remove the Customer Managed ORDS instance registration
kubectl delete -f adb_snodbodb23ai_create_ordsSrvs.yaml
# Patch the running Operator with the WATCH_NAMESPACE configuration
kubectl apply -f patch-oracle-database-operator.yaml
# Provision the Customer Managed ORDS instance
kubectl apply -f adb_snodbodb23ai_create_ordsSrvs.yaml
# Check the State
kubectl get ordssrvs -n sidb-ns snordsodbo
# List the PODS
kubectl get pods -n sidb-ns
# Check the Operator POD logs
kubectl logs -f -n oracle-database-operator-system \
$(kubectl get pod -o name -n oracle-database-operator-system|tail -1)
POD stat in Init:Error
# POD stat in Init:Error
kubectl get all -n sidb-ns
NAME READY STATUS RESTARTS AGE
pod/snordsodbo-74bb4b7558-bm2ll 0/1 Init:Error 1 (16s ago) 33s
# Check the POD init script logs
## kubectl logs -n <NAMESPACE> <ORDSSRVSPOD> -c <ordssrvs_name>-init
kubectl logs -n sidb-ns pod/snordsodbo-74bb4b7558-bm2ll -c snordsodbo-init
Check if there are any issues listed, connectivity, access denied or invalid login, etc.
Conclusion
The operator is appealing for repeatable deployment scenarios and for some complex tasks. Patching and redeployment will be simplified, though I have yet to test this use case. It is on my list along with leveraging replicas and load balancer with OKE.
All of the Operator code and resources are available, OpenSource on GitHub and open for contributions.
From my side and organisation we will be testing the Operator’s usefulness in Database DevOps and CICD Pipelines for the Oracle Database with APEX and ORDS.
Are you deploying Oracle components in K8s or considering the Database Operator?
Let me know and thanks for the feedback
Subscribe to my newsletter
Read articles from Sydney Nurse directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Sydney Nurse
Sydney Nurse
I work with software but it does not define me and my constant is change and I live a life of evolution. Learning, adapting, forgetting, re-learning, repeating I am not a Developer, I simply use software tools to solve interesting challenges and implement different use cases.