Migrate on Kubernetes using Helm

Migrate a running Anchore Enterprise v5.x Helm deployment to v6.1 or later, including the move to PostgreSQL 17.

This runbook migrates a running Anchore Enterprise v5.x Helm deployment to v6.1 or later. Before you begin, read the migration overview to understand what the migration does and what you need in place.

The v6.x Helm chart (the enterprise chart v4.1+ to migrate) requires a customer-provided PostgreSQL 17 database with the pg_cron extension. Unlike the v5.x chart, it does not deploy a PostgreSQL instance for you. How much work the migration involves depends on where your v5.x database runs today:

  • If your v5.x deployment already uses an external PostgreSQL database, you upgrade that database to PostgreSQL 17, enable pg_cron, update your values file, and run helm upgrade. No data is moved. Follow Path A.
  • If your v5.x deployment uses the chart’s bundled (Bitnami) PostgreSQL, that subchart is removed in v6.x. You must migrate your data into a new PostgreSQL 17 database before installing v6.1. Follow Path B.

Prerequisites

Complete these steps regardless of which path you follow.

  • A running v5.x Helm deployment (v5.0.0 or later) with admin access to its PostgreSQL database.
  • A provisioned or upgradeable PostgreSQL 17 database with the pg_cron extension, cron.use_background_workers enabled, and USAGE on the cron schema granted to the Anchore user. See Requirements.
  • A CNCF-certified Kubernetes version within the chart’s supported kubeVersion range (1.23–1.36 at the time of writing), Helm v3.8+, and kubectl configured for the target cluster.
  • A valid v6.x license. Optionally a database encryption key: at-rest column encryption is off by default and can be enabled now or later, so it is not a prerequisite for migrating. See the migration overview.
  • A v6.1 values file built from the enterprise chart v4.1+. Do not reuse your v5-era values file unchanged — several keys were removed, renamed, or relocated in v6.x. See Values File Changes.

Set some environment variables used throughout this runbook:

export RELEASE=<your-helm-release-name>
export NAMESPACE=<your-namespace>

Confirm the license and pull-credential secrets already exist in your namespace (they carry over from v5.x):

kubectl get secret anchore-enterprise-license -n ${NAMESPACE}
kubectl get secret anchore-enterprise-pullcreds -n ${NAMESPACE}

Back Up the Database

This step is critical. Because the schema migration is one-way, this backup is your only way back to v5.x.

# Dump from the v5.x database in custom format (adjust host/user/db as needed).
# For the chart's bundled PostgreSQL, exec into the postgres pod:
kubectl exec -it ${RELEASE}-postgresql-0 -n ${NAMESPACE} -- \
  pg_dump -U anchore -d anchore -Fc -f /tmp/anchore_backup.dump

# Copy the dump off the pod to durable storage
kubectl cp ${NAMESPACE}/${RELEASE}-postgresql-0:/tmp/anchore_backup.dump ./anchore_backup.dump

Verify the backup is usable, then store it somewhere durable (not on ephemeral pod storage):

ls -lh anchore_backup.dump
pg_restore --list anchore_backup.dump | head -20

Path A: Upgrade with an Existing External PostgreSQL

Use this path if your v5.x deployment already connects to an external PostgreSQL database (RDS, Cloud SQL, CNPG, or self-managed). This is the simplest path — your data stays where it is.

Step 1: Upgrade PostgreSQL to 17 and Enable pg_cron

If your database is not already on PostgreSQL 17+, upgrade it first. Take a snapshot before upgrading.

  • Amazon RDS: Use the RDS major version upgrade process.
  • Google Cloud SQL: Use the in-place major version upgrade.
  • CloudNativePG: Update spec.imageName to a PostgreSQL 17 image that includes pg_cron; the operator performs a rolling restart.
  • Self-managed: Upgrade using your normal process.

Then enable pg_cron using your platform’s method (see the provider-specific steps in the EKS, GKE, and AKS guides). In every case, finish by creating the extension and granting the Anchore user access to the cron schema:

CREATE EXTENSION IF NOT EXISTS pg_cron;
GRANT USAGE ON SCHEMA cron TO <ANCHORE_DB_USER>;

Step 2: Update Your Values File

Apply the changes in Values File Changes to your existing values file. At minimum, remove postgresql.chartEnabled and any other removed keys, and relocate any settings you previously passed via extraEnv.

Step 3: Run the Upgrade

helm upgrade ${RELEASE} -n ${NAMESPACE} anchore/enterprise -f anchore-values.yaml

The chart runs its database upgrade job (upgradeJob, executed as a pre-upgrade hook) automatically during helm upgrade. It verifies database connectivity, scales down the running v5.x pods, runs the database schema migration, and then starts all services on the v6.1 image. Do not interrupt this process. The Legacy Imported SBOM migration then runs in the background on first boot.

Continue to Verify the Migration.


Path B: Migrate Off the Bundled PostgreSQL

Use this path if your v5.x deployment uses the chart’s bundled Bitnami PostgreSQL (postgresql.chartEnabled: true). That subchart is removed in v6.x, and it runs PostgreSQL 13 by default (or another pre-17 version if you overrode the image tag) — so you must move your data to a new PostgreSQL 17 database before installing v6.1.

Step 1: Scale Down Anchore Enterprise

Stop all Anchore Enterprise services so no writes occur during the migration. Leave the bundled PostgreSQL pod running.

for deploy in $(kubectl get deploy -n ${NAMESPACE} -l app.kubernetes.io/name=${RELEASE}-enterprise -o name); do
  kubectl scale ${deploy} -n ${NAMESPACE} --replicas=0
done

# Verify all Anchore Enterprise pods are gone (the PostgreSQL pod should still be running)
kubectl get pods -n ${NAMESPACE}

Step 2: Provision the PostgreSQL 17 Database

Stand up a new PostgreSQL 17 database with pg_cron enabled, using the same database name as your v5.x database (for example, anchore), and grant the Anchore user USAGE on the cron schema. Use a managed service (see the EKS / GKE / AKS guides) or an in-cluster operator (see Run PostgreSQL In-Cluster with CloudNativePG).

Step 3: Move Your Data

Choose one migration method.

Method A: pg_dump / pg_restore (Any Target)

This works for any PostgreSQL 17 target. Dump from the bundled PostgreSQL pod (you may already have this from Back Up the Database):

kubectl exec -it ${RELEASE}-postgresql-0 -n ${NAMESPACE} -- \
  pg_dump -U anchore -d anchore -Fc -f /tmp/anchore_full.dump
kubectl cp ${NAMESPACE}/${RELEASE}-postgresql-0:/tmp/anchore_full.dump ./anchore_full.dump

Restore into the new PostgreSQL 17 database (reachable from where you run this):

pg_restore -h <TARGET_HOST> -U anchore -d anchore \
  --no-owner --no-privileges --clean --if-exists \
  anchore_full.dump

Verify the tables restored:

psql -h <TARGET_HOST> -U anchore -d anchore -c "\dt" | head -30

Method B: CloudNativePG Bootstrap Import

If your target is CloudNativePG, CNPG can import directly from the running bundled PostgreSQL using its bootstrap.initdb.import feature, so you do not run pg_dump/pg_restore by hand. Follow the CNPG operator and custom-image setup in the main deployment guide, then create a Cluster with an import bootstrap that points its externalClusters source at the ${RELEASE}-postgresql service. The bundled PostgreSQL pod must stay running during the import.

After the import completes, two follow-up tasks are required before the database is usable:

  1. Create the pg_cron extension. A CNPG import bootstrap does not run the cluster’s postInitApplicationSQL, so the pg_cron extension is not created automatically, even though shared_preload_libraries includes pg_cron. Anchore Enterprise v6.x will not start without it. Create it on the imported database manually:

    kubectl exec -it anchore-pg-1 -n ${NAMESPACE} -- \
      psql -U postgres -d anchore -c \
      "CREATE EXTENSION IF NOT EXISTS pg_cron; GRANT USAGE ON SCHEMA cron TO anchore;"
    
  2. Reset the Anchore role’s password. PostgreSQL 13 may store password hashes in the older md5 format, which PostgreSQL 17 does not accept, leaving every service failing to authenticate. Reset the password on the new database so it is rewritten using scram-sha-256:

    kubectl exec -it <new-pg-pod> -n ${NAMESPACE} -- \
      psql -U postgres -d anchore -c \
      "SET password_encryption = 'scram-sha-256'; ALTER ROLE anchore WITH PASSWORD '<PASSWORD>';"
    

Step 4: Update Your Values File

Create your v6.1 values file pointing at the new database, applying the changes in Values File Changes. A minimal example:

licenseSecretName: anchore-enterprise-license
imagePullSecretName: anchore-enterprise-pullcreds

postgresql:
  externalEndpoint: <NEW_DB_HOST>   # e.g. anchore-pg-rw.anchore.svc for CNPG
  auth:
    username: anchore
    password: <PASSWORD>
    database: anchore
  port: 5432

Step 5: Install v6.x

Choose one of two approaches based on your risk tolerance.

  • Approach 1 — helm upgrade in place (recommended). Keeps the same release name, service names, and any existing ingress/DNS configuration. Because the v5.x release is already scaled down and the data has moved, upgrade the existing release directly:

    helm upgrade ${RELEASE} -n ${NAMESPACE} anchore/enterprise -f anchore-values.yaml
    

    Helm cleans up the old bundled PostgreSQL resources during the upgrade. A PVC may be left behind; remove it once you have verified the new deployment:

    kubectl get pvc -n ${NAMESPACE} -l app.kubernetes.io/name=postgresql
    # kubectl delete pvc data-${RELEASE}-postgresql-0 -n ${NAMESPACE}
    
  • Approach 2 — install as a new release (side-by-side). Keeps the scaled-down v5.x release as a safety net until you are confident. Install with a new release name:

    helm install anchore-v6 -n ${NAMESPACE} anchore/enterprise -f anchore-values.yaml
    

Continue to Verify the Migration.


Values File Changes

Several chart values were removed, renamed, or relocated in v6.x. Review your existing values file against the tables below before running helm upgrade. The chart validates these on install/upgrade and fails fast with a descriptive error if a removed or relocated key is present, so it is safe to iterate.

Removed and Restructured Keys

v5.x valueStatus in v6.xAction
postgresql.chartEnabledRemovedDelete it. The bundled PostgreSQL subchart no longer exists.
postgresql.primary.*RemovedDelete it. Use postgresql.port instead of postgresql.primary.service.ports.postgresql.
postgresql.image.*RemovedDelete it.
startMigrationPodRemovedDelete it.
migrationPodImageRemovedDelete it.
migrationAnchoreEngineSecretNameRemovedDelete it.
anchoreConfig.webhooksRemovedDelete it.
anchoreConfig.internalServicesSSL.*RestructuredReplace with anchoreConfig.internal_ssl_verify plus per-service external_tls fields.
anchoreConfig.<service>.external.enabledRestructuredReplace with anchoreConfig.<service>.external_hostname, external_port, and external_tls.
anchoreConfig.reports_worker.runtime_report_generation.use_legacy_loaders_and_queriesRemovedDelete it.
anchoreConfig.analyzer.configFile.retrieve_filesRenamedRename to anchoreConfig.analyzer.configFile.file_contents.
Object store swift driverRemovedOnly db and s3 are supported. Migrate from Swift to S3 before upgrading.

Settings Moved Out of extraEnv

In v6.x, a number of settings that were commonly passed as environment variables via extraEnv must be set in their dedicated anchoreConfig value instead. The chart rejects these environment variables and will not install or upgrade until they are moved.

Environment variable (remove from extraEnv)Set this value instead
ANCHORE_LAYER_CACHE_ENABLEDanchoreConfig.analyzer.layer_cache_max_gigabytes
ANCHORE_LAYER_CACHE_SIZE_GBanchoreConfig.analyzer.layer_cache_max_gigabytes
ANCHORE_HINTS_ENABLEDanchoreConfig.analyzer.enable_hints
ANCHORE_OWNED_PACKAGE_FILTERING_ENABLEDanchoreConfig.analyzer.enable_owned_package_filtering
ANCHORE_KEEP_IMAGE_ANALYSIS_TMPFILESanchoreConfig.analyzer.keep_image_analysis_tmpfiles
ANCHORE_CATALOG_IMAGE_GC_WORKERSanchoreConfig.catalog.image_gc.max_worker_threads
ANCHORE_ENTERPRISE_RUNTIME_INVENTORY_TTL_DAYSanchoreConfig.catalog.runtime_inventory.inventory_ttl_days
ANCHORE_ENTERPRISE_RUNTIME_INVENTORY_INGEST_OVERWRITEanchoreConfig.catalog.runtime_inventory.inventory_ingest_overwrite
ANCHORE_ENTERPRISE_INTEGRATION_HEALTH_REPORTS_TTL_DAYSanchoreConfig.catalog.integrations.integration_health_report_ttl_days
ANCHORE_IMPORT_OPERATION_EXPIRATION_DAYSanchoreConfig.catalog.import_operation_expiration_days
ANCHORE_POLICY_EVAL_CACHE_TTL_SECONDSanchoreConfig.policy_engine.policy_evaluation_cache_ttl
ANCHORE_POLICY_ENGINE_ENABLE_PACKAGE_DB_LOADanchoreConfig.policy_engine.enable_package_db_load
ANCHORE_ENTERPRISE_REPORTS_ENABLE_GRAPHIQLanchoreConfig.reports.enable_graphiql
ANCHORE_ENTERPRISE_REPORTS_MAX_ASYNC_EXECUTION_THREADSanchoreConfig.reports.max_async_execution_threads
ANCHORE_ENTERPRISE_REPORTS_ASYNC_EXECUTION_TIMEOUTanchoreConfig.reports.async_execution_timeout
ANCHORE_ENTERPRISE_REPORTS_ENABLE_DATA_INGRESSanchoreConfig.reports_worker.enable_data_ingress
ANCHORE_ENTERPRISE_REPORTS_ENABLE_DATA_EGRESSanchoreConfig.reports_worker.enable_data_egress
ANCHORE_ENTERPRISE_REPORTS_DATA_EGRESS_WINDOWanchoreConfig.reports_worker.data_egress_window
ANCHORE_ENTERPRISE_REPORTS_DATA_REFRESH_MAX_WORKERSanchoreConfig.reports_worker.data_refresh_max_workers
ANCHORE_ENTERPRISE_REPORTS_DATA_LOAD_MAX_WORKERSanchoreConfig.reports_worker.data_load_max_workers
ANCHORE_ENTERPRISE_UI_URLanchoreConfig.notifications.ui_url
ANCHORE_DATA_SYNC_AUTO_SYNC_ENABLEDanchoreConfig.data_syncer.auto_sync_enabled
ANCHORE_ADMIN_EMAILanchoreConfig.default_admin_email
ANCHORE_API_DRIVEN_CONFIGURATION_ENABLEDanchoreConfig.api_driven_configuration_enabled
ANCHORE_ALLOW_ECR_IAM_AUTOanchoreConfig.allow_awsecr_iam_auto
ANCHORE_AUTH_PRIVKEYanchoreConfig.keys.privateKeyFileName
ANCHORE_AUTH_PUBKEYanchoreConfig.keys.publicKeyFileName
ANCHORE_DISABLE_METRICS_AUTHanchoreConfig.metrics.auth_disabled
ANCHORE_ENABLE_METRICSanchoreConfig.metrics.enabled
ANCHORE_MAX_COMPRESSED_IMAGE_SIZE_MBanchoreConfig.max_compressed_image_size_mb
ANCHORE_MAX_IMPORT_CONTENT_SIZE_MBanchoreConfig.max_import_content_size_mb
ANCHORE_MAX_IMPORT_SOURCE_SIZE_MBanchoreConfig.max_source_import_size_mb
ANCHORE_OAUTH_TOKEN_EXPIRATIONanchoreConfig.user_authentication.oauth.default_token_expiration_seconds
ANCHORE_OAUTH_REFRESH_TOKEN_EXPIRATIONanchoreConfig.user_authentication.oauth.refresh_token_expiration_seconds
ANCHORE_SSO_REQUIRES_EXISTING_USERSanchoreConfig.user_authentication.sso_require_existing_users
ANCHORE_IMAGE_ANALYZE_TIMEOUT_SECONDSanchoreConfig.image_analyze_timeout_seconds

Verify the Migration

After the upgrade, watch the pods come up and confirm system status:

kubectl get pods -n ${NAMESPACE} -w

# Port-forward the API and check status (use the new release name for Path B, Approach 2)
kubectl port-forward svc/${RELEASE}-enterprise-api 8228:8228 -n ${NAMESPACE}
anchorectl system status
anchorectl system feeds list

Confirm that all services report up, that your existing images, policies, and scan results are present, that feed syncs are running, and that the UI is reachable.

The Legacy Imported SBOM migration runs in the background on first boot; your deployment is fully usable while it runs, though migrated SBOMs may not all appear as Apps, App Versions, and Assets until it finishes. Report its progress with anchore-enterprise-manager, which reads the database directly and so must be run from inside a running Anchore Enterprise pod:

kubectl exec -it deploy/${RELEASE}-enterprise-catalog -n ${NAMESPACE} -- bash -c \
  'anchore-enterprise-manager --json db --db-connect postgresql://"${ANCHORE_DB_USER}":"${ANCHORE_DB_PASSWORD}"@"${ANCHORE_DB_HOST}":"${ANCHORE_DB_PORT}"/"${ANCHORE_DB_NAME}" legacy-sbom-migration-status'

The database environment variables are already present in every Anchore Enterprise pod, so the connection string above needs no editing. It is quoted for the pod’s shell to expand, not yours.

{
  "message": "Legacy SBOM Migration is in progress. SBOM mappings migrated so far: 604, SBOM mappings remaining: 549, SBOM mappings failed to migrate: 0",
  "migrated": 604,
  "remaining": 549,
  "state": "in_progress",
  "total": 1153,
  "failed": 0
}

state reports one of:

stateMeaning
not_triggeredNo Legacy Imported SBOM migration ran. Expected when the v5.x deployment had no imported SBOMs.
in_progressStill working. remaining counts the SBOM mappings left to process.
completeEvery SBOM mapping migrated, with no failures.
complete_with_failuresFinished, but failed mappings did not migrate.

The counts are SBOM mappings — one per SBOM per SBOM Group it belongs to, plus one per empty SBOM Group — not SBOM documents, so the total will exceed your imported SBOM count when SBOMs belong to more than one group. Drop --json to print the message line on its own.

If the migration ends as complete_with_failures, contact Anchore Customer Success via support.anchore.com before retiring your v5.x backup.

Roll Back

Rollback restores the pre-upgrade state from your backup, because the schema migration cannot be reversed.

  1. Uninstall or scale down the v6.1 release.
  2. Restore the database backup taken in Back Up the Database into a v5.x-compatible PostgreSQL instance.
  3. Restart or reinstall the v5.x release pointing at the restored database.
  4. Verify all services come back up with your pre-upgrade data intact.

Any work done in v6.1 after cutover is lost on rollback, so keep the v6.1 trial period write-light until you commit to it.

Troubleshooting

SymptomLikely cause
Pre-upgrade hook fails with a DB connection errorPostgreSQL is unreachable or credentials changed. Check the hook job logs with kubectl get jobs -n ${NAMESPACE} and kubectl logs.
Helm upgrade fails citing postgresql.chartEnabledThe removed key is still in your values file. Delete it.
Helm upgrade fails naming an extraEnv variableA setting must move out of extraEnv. See Settings Moved Out of extraEnv.
pg_cron errors in pod logsThe extension is not enabled on the target database. Enable it and run CREATE EXTENSION pg_cron;.
pg_restore errors: role "anchore" does not existCreate the Anchore user on the target first.
FATAL: password authentication failed after migrating off PG13Reset the role password so it is rewritten as scram-sha-256 (see the password-reset task in Method B).
Services are up but data appears missingYou are pointed at the wrong database. Confirm the endpoint and check \dt in psql.
pg_dump is very slow on a large databaseAdd -j <N> to pg_dump/pg_restore for parallelism, and run during off-hours.
Last modified August 11, 2026