Migrate from Bitnami to Operator-Managed PostgreSQL and RabbitMQ
This guide covers the manual steps required to migrate from the Bitnami chart-managed PostgreSQL and RabbitMQ installation (the default up to and including operator 25.1 releases) to the operator-managed equivalents:
- PostgreSQL - CloudNativePG (CNPG) operator (required)
- RabbitMQ - RabbitMQ Cluster Operator (optional), recommended when upgrading at the same time
The Bitnami PostgreSQL PVC and the CNPG Cluster PVC are incompatible on-disk formats. A manual dump-and-restore is required. No data migration is needed for RabbitMQ messages in-flight at the time of upgrade will be lost. Make sure there are no active deployments during this migration.
Before proceeding with any actions, ensure you have created a backup of your database.
Replace <NAMESPACE> with your actual namespace (default: digitalai) throughout these steps.
Step 1 — Retrieve Credentials from the Bitnami PostgreSQL Cluster
POSTGRES_PASSWORD=$(kubectl get secret -n <NAMESPACE> dai-xld-postgresql \
-o jsonpath="{.data.postgres-password}" | base64 -d)
echo "Postgres password: $POSTGRES_PASSWORD"
Step 2 — Dump All Databases from the Bitnami Pod
Ensure there is enough free space on the PVC: kubectl exec -n <NAMESPACE> dai-xld-postgresql-0 -- df -h /bitnami/postgresql.
kubectl exec -n <NAMESPACE> dai-xld-postgresql-0 -- mkdir -p /bitnami/postgresql/backup
kubectl exec -n <NAMESPACE> dai-xld-postgresql-0 \
-- bash -c "PGPASSWORD='$POSTGRES_PASSWORD' pg_dump \
-U postgres -F c \
-f /bitnami/postgresql/backup/xld-db.dump xld-db"
kubectl exec -n <NAMESPACE> dai-xld-postgresql-0 \
-- bash -c "PGPASSWORD='$POSTGRES_PASSWORD' pg_dump \
-U postgres -F c \
-f /bitnami/postgresql/backup/xld-report-db.dump xld-report-db"
# Verify the dump files exist and have non-zero size
kubectl exec -n <NAMESPACE> dai-xld-postgresql-0 -- ls -lh /bitnami/postgresql/backup/
Step 3 — Copy Dumps to Local Machine
mkdir -p ./postgres-backup/deploy
kubectl cp -n <NAMESPACE> \
dai-xld-postgresql-0:/bitnami/postgresql/backup/xld-db.dump \
./postgres-backup/deploy/xld-db.dump
kubectl cp -n <NAMESPACE> \
dai-xld-postgresql-0:/bitnami/postgresql/backup/xld-report-db.dump \
./postgres-backup/deploy/xld-report-db.dump
ls -lh ./postgres-backup/deploy/
Step 4 — Scale Application to Zero Replicas
# Record current replicaCounts
MASTER_REPLICA_COUNT=$(kubectl get digitalaideploys.xld.digital.ai dai-xld -n <NAMESPACE> \
-o jsonpath='{.spec.master.replicaCount}')
WORKER_REPLICA_COUNT=$(kubectl get digitalaideploys.xld.digital.ai dai-xld -n <NAMESPACE> \
-o jsonpath='{.spec.worker.replicaCount}')
CC_REPLICA_COUNT=$(kubectl get digitalaideploys.xld.digital.ai dai-xld -n <NAMESPACE> \
-o jsonpath='{.spec.centralConfiguration.replicaCount}')
echo "Master: $MASTER_REPLICA_COUNT, Worker: $WORKER_REPLICA_COUNT, CentralConfig: $CC_REPLICA_COUNT"
# Scale all three application components down to 0
kubectl patch -n <NAMESPACE> digitalaideploys.xld.digital.ai dai-xld \
--type=merge --patch '{"spec": {"master": {"replicaCount": 0}, "worker": {"replicaCount": 0}, "centralConfiguration": {"replicaCount": 0}}}'
# Wait until all Deploy application pods are terminated
kubectl wait --for=delete pod -l app.kubernetes.io/name=digitalai-deploy \
-n <NAMESPACE> --timeout=300s
Step 5 — Run xl kube upgrade
Run the upgrade wizard normally.
When prompted about PostgreSQL installation:
Answer:
operator[CloudNativePG Operator (experimental/non-production)]
When prompted about RabbitMQ installation (Deploy only):
Answer:
operator[RabbitMQ Cluster Operator (experimental/non-production)] (optional)
When prompted about PVC preservation:
Should we preserve persisted volume claims? If not all volume data will be lost: Yes
Answer: Y (Yes)
Do not answer No to the PVC preservation prompt. Answering No deletes all PVCs in the namespace, including Deploy's own repository and central configuration volumes causing data loss beyond the intended Bitnami migration.
Preserving PVCs keeps Deploy's application volumes intact. After the upgrade completes, manually delete only the old Bitnami PostgreSQL and RabbitMQ PVCs:
# Delete the Bitnami PostgreSQL PVC
kubectl delete pvc -n <NAMESPACE> data-dai-xld-postgresql-0
# Delete the Bitnami RabbitMQ PVC (if migrating RabbitMQ)
kubectl delete pvc -n <NAMESPACE> data-dai-xld-rabbitmq-0
When the wizard asks Edit list of custom resource keys that will migrate to the new Deploy CR, the following keys must NOT be present in the preserve-keys list. If they are pre-populated, remove them before continuing:
spec.postgresql.installspec.rabbitmq.installspec.external.db(and all sub-keys:enabled,main.url,main.username,main.password,report.url,report.username,report.password)spec.external.mq(and all sub-keys:enabled,url,username,password,driverClassName)
Step 6 — Verify the CR After Upgrade
Confirm that external.db and external.mq point at the new operator-managed services with enabled: true, and that postgresql.install and rabbitmq.install are false.
kubectl get digitalaideploys.xld.digital.ai dai-xld -n <NAMESPACE> \
-o jsonpath='{.spec.external.db}'
kubectl get digitalaideploys.xld.digital.ai dai-xld -n <NAMESPACE> \
-o jsonpath='{.spec.external.mq}'
Step 7 — Wait for the New CNPG Cluster to be Ready
kubectl get cluster -n <NAMESPACE> dai-xld-postgres -w
kubectl wait pod/dai-xld-postgres-1 \
-n <NAMESPACE> \
--for=condition=Ready \
--timeout=300s
# Confirm the expected PostgreSQL image is in use
kubectl get pod dai-xld-postgres-1 -n <NAMESPACE> \
-o jsonpath='{.spec.containers[0].image}'; echo
Step 8 — Copy Dumps into the New CNPG Cluster Pod
The CNPG pod mounts /tmp as a read-only filesystem. Use /run/tmp/restore instead.
kubectl exec -n <NAMESPACE> dai-xld-postgres-1 -- mkdir -p /run/tmp/restore
kubectl cp -n <NAMESPACE> \
./postgres-backup/deploy/xld-db.dump \
dai-xld-postgres-1:/run/tmp/restore/xld-db.dump
kubectl cp -n <NAMESPACE> \
./postgres-backup/deploy/xld-report-db.dump \
dai-xld-postgres-1:/run/tmp/restore/xld-report-db.dump
kubectl exec -n <NAMESPACE> dai-xld-postgres-1 -- ls -lh /run/tmp/restore/
Step 9 — Restore the Database Dumps
Restore as the database owner. postInitApplicationSQLRefs already created the users and set ownership no grants required.
First, retrieve the database owner passwords from the Deploy secret:
MAIN_DB_PASSWORD=$(kubectl get secret -n <NAMESPACE> dai-xld-digitalai-deploy \
-o jsonpath="{.data.mainDatabasePassword}" | base64 -d)
REPORT_DB_PASSWORD=$(kubectl get secret -n <NAMESPACE> dai-xld-digitalai-deploy \
-o jsonpath="{.data.reportDatabasePassword}" | base64 -d)
Then restore each database:
# Restore xld-db as its database owner (xld)
kubectl exec -n <NAMESPACE> dai-xld-postgres-1 \
-- bash -c "PGPASSWORD='$MAIN_DB_PASSWORD' pg_restore \
-U xld \
-h localhost \
-d xld-db \
--no-owner \
--exit-on-error \
/run/tmp/restore/xld-db.dump"
# Restore xld-report-db as its database owner (xld-report)
kubectl exec -n <NAMESPACE> dai-xld-postgres-1 \
-- bash -c "PGPASSWORD='$REPORT_DB_PASSWORD' pg_restore \
-U 'xld-report' \
-h localhost \
-d xld-report-db \
--no-owner \
--exit-on-error \
/run/tmp/restore/xld-report-db.dump"
Step 10 — Verify Row Counts (Optional)
kubectl exec -n <NAMESPACE> dai-xld-postgres-1 \
-- bash -c "PGPASSWORD='$MAIN_DB_PASSWORD' psql -U xld -h localhost -d xld-db \
-c 'SELECT count(*) FROM \"XLD_CIS\";'"
Step 11 — Scale Application Back to Original Replica Count
kubectl patch -n <NAMESPACE> digitalaideploys.xld.digital.ai dai-xld \
--type=merge --patch "{\"spec\": {\"master\": {\"replicaCount\": $MASTER_REPLICA_COUNT}, \"worker\": {\"replicaCount\": $WORKER_REPLICA_COUNT}, \"centralConfiguration\": {\"replicaCount\": $CC_REPLICA_COUNT}}}"
kubectl get pods -n <NAMESPACE> -l app.kubernetes.io/name=digitalai-deploy -w
Wait for all application pods to reach Running and pass their readiness probes before treating the migration as complete.
Cleanup
Once the application is confirmed healthy, remove the dump files:
# Local cleanup
rm -rf ./postgres-backup/
# Pod cleanup
kubectl exec -n <NAMESPACE> dai-xld-postgres-1 -- rm -rf /run/tmp/restore