Backup, Restore, and Disaster Recovery
Back up a production GoodData.CN deployment, restore it, roll back a failed upgrade, and recover from a full loss of infrastructure. This page covers the platform state that lives outside the Helm chart.
What to Back Up
The metadata PostgreSQL databases are the source of truth. Everything else is derived, rebuildable, or already in Git.
| State | What it holds | Back up? |
|---|---|---|
Metadata PostgreSQL databases (md, automation, genai, dex, gw) | Workspaces, models, dashboards, users, schedules, data source definitions. The source of truth. | Back up |
Tink encryption keyset secret (for example gdcn-encryption) | The keyset used to encrypt data source credentials. Without it, restored credentials cannot be decrypted. | Back up and store securely alongside the database backup |
License secret (for example gdcn-license) | The GoodData.CN license key. | Back up (or re-obtain from GoodData) |
Helm values files (gdcn-values.yaml, gdcn-values-sizing.yaml) | Your deployment configuration. | Back up or keep in Git |
Kubernetes Organization custom resources | Organization definitions applied to the cluster. | Back up or keep in Git |
| Object storage: exports bucket | Transient export artifacts. | Optional |
| Object storage: quiver-cache bucket | Rebuildable analytical cache. | Do not back up; it regenerates automatically |
| Object storage: quiver-datasource-fs bucket | Uploaded CSV / file-based data source files. | Back up if you rely on file data sources |
Keep the Encryption Keyset With the Database Backup
The metadata databases store data source credentials encrypted with the Tink keyset. Without the matching keyset, restored credentials are unreadable. Store the keyset secret securely.
How to Back Up
Recommended: Managed Database Snapshots
With an external PostgreSQL instance (Amazon RDS, Azure Database for PostgreSQL Flexible Server, and similar), make the provider’s native backups your primary method:
- Enable automated snapshots with a retention window that meets your recovery objectives.
- Enable point-in-time recovery (PITR) to restore to a specific moment.
Snapshots are consistent across all databases on the instance and need no downtime.
Alternative: Logical Backup With
pg_dump
If you cannot use snapshots, dump each database from a pod or host with the PostgreSQL client tools and network access to the instance:
export PGPASSWORD='<POSTGRES_PASSWORD>'
SOURCE_HOST=<POSTGRES_HOST>
pg_dump -h $SOURCE_HOST -p 5432 -U postgres -d md -Fc -v > /tmp/md.dump
pg_dump -h $SOURCE_HOST -p 5432 -U postgres -d automation -Fc -v > /tmp/automation.dump
pg_dump -h $SOURCE_HOST -p 5432 -U postgres -d genai -Fc -v > /tmp/genai.dump # skip if you do not deploy the service
pg_dump -h $SOURCE_HOST -p 5432 -U postgres -d dex -Fc -v > /tmp/dex.dump # skip if you do not deploy the service
pg_dump -h $SOURCE_HOST -p 5432 -U postgres -d gw -Fc -v > /tmp/gw.dumpSkip optional databases you do not deploy: genai requires deployGenAIService, dex requires deployDexIdP. gw is always present, because the core gateway-forge service owns it.
Consistency
pg_dump is consistent within a database but not across databases while the platform is writing. For a fully consistent logical backup, dump during a maintenance window with the writing services scaled down, or use snapshots.
Object Storage
Of the three buckets, only quiver-datasource-fs holds data that cannot be regenerated: the files that users uploaded as file-based data sources. If you rely on file data sources, copy that bucket to a backup location on the same schedule as the database backup:
# AWS
aws s3 sync s3://<DATASOURCE_FS_BUCKET_NAME> s3://<BACKUP_BUCKET_NAME>/quiver-datasource-fs
# Azure
az storage blob copy start-batch \
--source-account-name '<AZURE_STORAGE_ACCOUNT_NAME>' \
--source-container quiver-datasource-fs \
--account-name '<BACKUP_STORAGE_ACCOUNT_NAME>' \
--destination-container quiver-datasource-fs \
--auth-mode loginstart-batch returns before the server-side copy finishes. Re-run this check until it prints nothing, then confirm the destination container holds the same blobs as the source:
az storage blob list \
--account-name '<BACKUP_STORAGE_ACCOUNT_NAME>' \
--container-name quiver-datasource-fs \
--auth-mode login \
--query "[?properties.copy.status=='pending'].name" -o tsvThe exports bucket holds transient artifacts, and the quiver-cache bucket regenerates automatically, so neither one needs a backup.
Before Every Upgrade
An upgrade can migrate database schemas, so the pre-upgrade backup is the only way back to the previous version. Before you run helm upgrade:
Take a full backup of every database GoodData.CN uses internally (
md,automation, etc.). On managed PostgreSQL providers, taking a snapshot of the instance is the most convenient method. If you run PostgreSQL yourself, use apg_dumpof every database.Read the release notes and Upgrade GoodData.CN for the target version and perform any extra steps they require.
Record the current Helm revision and chart version so you can roll back to it:
helm history gooddata-cn -n gooddata-cn
If the upgrade causes a serious issue, follow Roll Back a Failed Upgrade.
How to Restore
Restore into a namespace that is not serving traffic. These steps restore the metadata databases and redeploy the chart from your Git-managed values.
Provision or restore PostgreSQL. Restore the snapshot or PITR to a new or existing instance, or provision an empty instance and create the databases
md,automation,genai,dex, andgw.Recreate the namespace and secrets from your secure backup:
kubectl create namespace gooddata-cn kubectl -n gooddata-cn create secret generic gdcn-encryption --from-file=keySet=gdcn-keyset.json kubectl -n gooddata-cn create secret generic gdcn-license --from-literal=license='<YOUR_GDCN_LICENSE_KEY>'Scale every deployment in the namespace to zero. Skip this step if nothing is running in the target namespace yet. Stopping everything guarantees that no service writes to the databases during the restore:
kubectl scale deployment --all --replicas=0 -n gooddata-cnRestore the databases. Skip this step if you restored a managed snapshot into a fresh instance. Restore each dump into its database:
export PGPASSWORD='<POSTGRES_PASSWORD>' TARGET_HOST=<POSTGRES_HOST> pg_restore -h $TARGET_HOST -p 5432 -U postgres -d md -v /tmp/md.dump pg_restore -h $TARGET_HOST -p 5432 -U postgres -d automation -v /tmp/automation.dump pg_restore -h $TARGET_HOST -p 5432 -U postgres -d genai -v /tmp/genai.dump # skip if you do not deploy the service pg_restore -h $TARGET_HOST -p 5432 -U postgres -d dex -v /tmp/dex.dump # skip if you do not deploy the service pg_restore -h $TARGET_HOST -p 5432 -U postgres -d gw -v /tmp/gw.dumpThese commands assume empty target databases. If the targets still contain objects (for example during a rollback), drop and recreate each database before restoring. Do not rely on
pg_restore --clean --if-existsfor this: it removes only objects that exist in the archive, so anything created after the backup survives it.for db in md automation genai dex gw; do # skip genai/dex if you do not deploy those services psql -h $TARGET_HOST -p 5432 -U postgres \ -c "DROP DATABASE IF EXISTS $db WITH (FORCE);" \ -c "CREATE DATABASE $db;" doneA managed snapshot restore or point-in-time recovery replaces the whole instance and makes this step unnecessary. Use
--clean --if-existsonly when you know the target schema matches the archive.Restore the uploaded data source files. Skip this step if you do not use file-based data sources. Copy the backed-up quiver-datasource-fs contents into the bucket that the restored deployment points at:
# AWS aws s3 sync s3://<BACKUP_BUCKET_NAME>/quiver-datasource-fs s3://<DATASOURCE_FS_BUCKET_NAME> # Azure az storage blob copy start-batch \ --source-account-name '<BACKUP_STORAGE_ACCOUNT_NAME>' \ --source-container quiver-datasource-fs \ --account-name '<AZURE_STORAGE_ACCOUNT_NAME>' \ --destination-container quiver-datasource-fs \ --auth-mode loginOn Azure,
start-batchreturns before the server-side copy finishes; re-run the pending-copies check from How to Back Up against<AZURE_STORAGE_ACCOUNT_NAME>until it prints nothing before you continue.The quiver-cache bucket needs no restore, because it rebuilds automatically as queries run. The exports bucket holds transient artifacts, so its contents are safe to lose.
Update the database endpoint in your Git-managed values. If the restore produced a new PostgreSQL endpoint, set
service.postgres.hostto the new hostname before you redeploy, along withservice.postgres.port,service.postgres.username, and the password orservice.postgres.existingSecretif those changed too. If you skip this, the services come back pointing at the old endpoint.Redeploy the chart from those Git-managed values. The upgrade also returns every deployment to the replica counts in your values files:
helm upgrade --install gooddata-cn gooddata/gooddata-cn \ --namespace gooddata-cn \ --version "<GOODDATA_CN_VERSION>" \ --values gdcn-values.yaml \ --values gdcn-values-sizing.yaml \ --wait --timeout 30mRe-apply your
Organizationcustom resources from Git if they are missing.Verify. All pods reach
Ready, login works, dashboards load. The quiver-cache rebuilds automatically, so the first queries after a restore are slow.
Roll Back a Failed Upgrade
If an upgrade causes a serious issue, return the deployment to the previous version using the backup you took before the upgrade. Run the steps in this order:
Scale every deployment in the namespace to zero replicas:
kubectl scale deployment --all --replicas=0 -n gooddata-cnRestore the pre-upgrade database backup. Restore the snapshot you took before the upgrade, for example with an RDS snapshot restore, or restore the logical dumps as described in How to Restore. The databases must go back to their pre-upgrade schema, because the new chart version may have migrated them.
Roll the Helm release back to the pre-upgrade version. Use the revision number from
helm history:helm rollback gooddata-cn <PRE_UPGRADE_REVISION> --namespace gooddata-cn --wait --timeout 30mIf you restored the database to a new endpoint instead of in place,
helm rollbackreplays the values stored with the old revision, which still carry the oldservice.postgres.*settings. In that case, updateservice.postgres.hostand the other connection settings in your Git-managed values and runhelm upgradewith the pre-upgrade chart version and those values, rather thanhelm rollback.Scale the deployments back up. The rollback re-applies the chart’s replica counts, so most deployments come back on their own. Check with
kubectl get deployments -n gooddata-cnand scale up anything still at zero, then verify that pods reachReady, login works, and dashboards load.
Rolling Back Discards Recent Changes
The rollback returns the metadata to the state captured in the backup. Any changes users made after the backup are lost, including edits to the logical data model and to the analytics model such as new or modified metrics, visualizations, and dashboards.
Full Disaster Recovery
If you lose the whole environment (cluster, database, and buckets), recover in this order:
- Reprovision infrastructure: the Kubernetes cluster, the PostgreSQL instance, the object storage buckets (exports, quiver-cache, quiver-datasource-fs), and the identity/OIDC configuration.
- Restore the database from the latest snapshot or PITR, or from your logical dumps.
- Restore the quiver-datasource-fs bucket contents from your object storage backup, using the same
aws s3 syncoraz storage blob copy start-batchcommand as in How to Restore. The new buckets are empty, so recreating them is not enough: without this step, every file-based data source breaks. The quiver-cache bucket does not need restoring, because it rebuilds automatically, and the exports bucket only holds transient artifacts. - Recreate secrets: the encryption keyset and the license, as in the restore procedure above.
- Point your Git-managed values at the new infrastructure. Update
service.postgres.*for the new database endpoint and the bucket or container names if they changed, and commit the change before you redeploy. - Redeploy the chart via GitOps. Apply your Git-managed Helm values and
Organizationcustom resources. - Repoint DNS so the organization and auth hostnames route to the new ingress.
- Validate: pods ready, login works, dashboards load, data sources connect.
Recovery time depends mainly on the database restore and the cache warmup, since the quiver-cache rebuilds lazily as queries run.
Organization-Level (Logical) Backup
The declarative API in Create and Restore Backups captures one organization’s configuration: workspaces, models, and data source definitions, but not secrets. Use it to migrate or clone an organization, or to snapshot before a risky change. It complements infrastructure-level backups but does not replace them: only database backups recover the whole deployment.