Database Storage
Anchore Enterprise stores all metadata in a structured format in a PostgreSQL database to support API operations and searches.
Examples of data persisted in the database:
- Image metadata (distro, version, layer counts, …)
- Image digests to tag mapping (docker.io/nginx:latest is hash sha256:abcd at time t)
- Image analysis content indexed for policy evaluation (files, packages, ..)
- Feed data
- vulnerability info
- package info from upstream (gem/npm)
- Accounts, users…
- …
If the object store is not explicitly set to an external provider, then that data is also persisted in the database but can be migrated
Reducing Database Storage Usage
Beyond enabling a non-DB object store there are some configuration options to reduce database storage and IO used by Anchore Enterprise.
Configuration of Indexed DB Storage for Package DB File Entries
There is a configuration option for the policy engine service to disable the usage of
the database for storing indexed package database entries from each analyzed image. This data represents the files in
each distro package and their metadata (digests and permissions) from each scanned image in the image_package_db_entries table.
That table is only used by the policy engine to deliver the policy trigger [‘packages.verify’],
but if you do not use that trigger then the use of the storage can be disabled thereby reducing database load and resource usage.
The data can be quite large, often in the thousands of rows per analyzed image, so for some customers that do not use this
data for policy, disabling the loading of this data can reduce database consumption significantly.
Disabling Indexed DB Storage for Package DB File Entries
In each policy engine’s config.yaml file, change:
enable_package_db_load: true
to
enable_package_db_load: false
You can configure the enable_package_db_load property, which is enabled (true) by default, through any of the usual mechanisms:
- Helm: set
anchoreConfig.policy_engine.enable_package_db_loadin your values file. In v6.x the chart no longer accepts this as theANCHORE_POLICY_ENGINE_ENABLE_PACKAGE_DB_LOADenvironment variable viaextraEnv. - Docker Compose: set the
ANCHORE_POLICY_ENGINE_ENABLE_PACKAGE_DB_LOADenvironment variable on the policy engine service. - API: if API-driven configuration is enabled, set it at runtime through the API, as with many other settings.
Note that disabling the table usage will also disable support for the packages.verify trigger and any policies that have the
trigger in a rule will be considered invalid and return errors on evaluation. Any new policies that attempt to use the trigger
will be rejected on upload as invalid if the trigger is included.
Once this configuration is set, you may delete data in that db table to reclaim some database storage capacity. If you’re interested in this option please contact support for guidance on this process.
Enabling Indexed DB Storage for Package DB File Entries
If you find that you do need the trigger, you can change the configuration to use the table then support will be restored. However, any images analyzed while the setting was ‘false’ will need to be re-analyzed in order to populate their data in that table correctly.
Encrypting Database Secrets at Rest
Starting with v6.1, Anchore Enterprise can encrypt sensitive columns in the database at rest using AES-256-GCM. The encryption is transparent to all users.
Encryption is off by default. A deployment that configures no encryption key stores these columns as plaintext, exactly as releases before v6.1 did, and starts normally with no additional configuration. Encryption is enabled by supplying a key - see Delivering the Keyring. Anchore recommends enabling it.
You can opt in whenever you are ready; it does not have to be at install time. Existing plaintext values stay readable after you add a key, and rotate seals them under it. Note that opting in is effectively one-way: once a deployment has encrypted data, it will not start again without its keyring (see Once Encryption Is Enabled).
What Is Encrypted
Database stored credential materials are encrypted at rest, this includes registry access information, notifications authentication material and others.
Threat Model
Protects against: leaked database backups, lost replica disks, casual DBA access, exfiltrated
pg_dumpoutput.Does not protect against: a compromised service host (the keyring lives in process memory by necessity), or an attacker who can read wherever the operator stores the keyring source.
The deployer is responsible for protecting the keyring source. The implementation is the same across all platforms, if you choose to use a service like AWS Secrets Manager you are still responsible for keeping a secure backup copy of the key data.
Key Format
Every encryption key is 32 random bytes, base64url-encoded. Each keyring entry is just that bare base64url string.
The first entry in the keyring is primary (used for new writes); the second, if present, is decrypt-only and retained only while a rotation is in flight. Primacy is position based, not value based - the first entry is always the primary.
A keyring holds at most two keys - the primary plus one retained previous key. The service refuses to start with more. This forces each rotation to complete (rotate, verify, drop the old key) before the next one begins, and stops retired generations accumulating in the keyring.
A key’s bytes must never change once it has encrypted data, and a retired key must never be reintroduced as primary. Because the kid (key id) is derived from the key bytes, a kid permanently identifies one specific key - you cannot reuse it.
- No rebind. If a kid is configured whose fingerprint differs from the one previously recorded for it, the service refuses to start. (Restoring a dropped key with its original bytes is always allowed - the fingerprint matches.)
- No rollback. The primary key must not be older (by the timestamp it was first registered) than a key the deployment has since retired. Configuring a superseded key as primary looks like a rollback to a key you have moved past, and the service refuses to start. Restore the most recent key as the primary. Deliberately reverting to an older key is not a recommended action; if you believe you genuinely need to, contact Anchore support.
Minting a Key
A key is simply 32 random bytes, base64url-encoded. Two equivalent methods:
Method 1: The management CLI
anchore-enterprise-manager db encryption generate-key
# Hk7q...base64-32-bytes...==
Place the emitted key in the current-key slot (ANCHORE_DB_ENCRYPTION_KEY_CURRENT); during a rotation, move the existing current key to the previous slot (ANCHORE_DB_ENCRYPTION_KEY_PREVIOUS) and put the new key in current.
Method 2: A Python snippet
For audit-friendly key minting without any Anchore code on the path:
python3 -c "import secrets, base64; print(base64.urlsafe_b64encode(secrets.token_bytes(32)).decode())"
# Hk7q...base64-32-bytes...==
Delivering the Keyring
The keyring lives in the existing config file under credentials.database.encryption, alongside the database credentials the operator already manages there. provider selects the key provider (only static is supported), and keys is a list of bare base64url keys. Giving each slot its own reference - a current key and an optional previous key - lets each map to a distinct entry/version in your secret store, which is what makes managed rotation work (see Rotating a Key).
credentials:
database:
user: ${ANCHORE_DB_USER}
password: ${ANCHORE_DB_PASSWORD}
host: ${ANCHORE_DB_HOST}
port: ${ANCHORE_DB_PORT}
database: ${ANCHORE_DB_NAME}
encryption:
provider: static
keys:
- ${ANCHORE_DB_ENCRYPTION_KEY_CURRENT}
- ${ANCHORE_DB_ENCRYPTION_KEY_PREVIOUS}
ANCHORE_DB_ENCRYPTION_KEY_CURRENT is the primary, used for new writes. ANCHORE_DB_ENCRYPTION_KEY_PREVIOUS is the retained previous key, present only during a rotation. Both slots are optional - a slot that is unset, or set to an empty value, is simply dropped, so the template above is valid whether you have two keys, one, or none.
With neither slot supplied the keyring is empty, which is how encryption stays off: the service starts normally and logs a warning that these columns are being written as plaintext. The one exception is a deployment that has previously used a keyring - it will refuse to start without it, rather than silently downgrade (see Once Encryption Is Enabled).
Keys never appear in service logs, API responses, or configuration-dump output.
Deployment Examples
Docker Compose - one env-var per slot:
services:
enterprise-api:
environment:
ANCHORE_DB_ENCRYPTION_KEY_CURRENT: 'Hk7q...base64-32-bytes...=='
# ANCHORE_DB_ENCRYPTION_KEY_PREVIOUS is set only during a rotation
Kubernetes (Helm) - the Anchore Enterprise Helm chart manages the keyring for you. Supplying a key is what enables encryption; a values file with no key leaves it off. Set the keys in your values file and the chart creates the Kubernetes Secret and projects each slot into the service pods:
anchoreConfig:
database:
encryption:
currentKey: 'Hk7q...base64-32-bytes...=='
# previousKey: '9aZ4...==' # set only during a rotation
To keep key material out of your values file, reference a Secret you manage yourself instead. When existingSecret is set, currentKey/previousKey are ignored:
anchoreConfig:
database:
encryption:
existingSecret: anchore-db-encryption
currentKeySecretKey: current_key
previousKeySecretKey: previous_key # read only while a rotation is in flight
When retiring the previous key after a rotation, delete the key from the Secret rather than setting it to an empty string - either is accepted, but a deleted key is unambiguous.
AWS Secrets Manager - because the key value can be any random 32 bytes, you can let Secrets Manager rotate it for you. Map the secret’s staged versions to the two slots: AWSCURRENT is the current key, AWSPREVIOUS is the previous key that Secrets Manager retains across a rotation. The store itself then supplies the decrypt overlap a rotation needs, with no key-formatting step. The snippet below goes in the secrets array of the container definition in your Amazon ECS task definition (for example, when running Anchore Enterprise on ECS or Fargate):
[
{
"name": "ANCHORE_DB_ENCRYPTION_KEY_CURRENT",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:anchore/db-encryption-key"
},
{
"name": "ANCHORE_DB_ENCRYPTION_KEY_PREVIOUS",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:anchore/db-encryption-key:AWSPREVIOUS::"
}
]
Rotating a Key
A rotation moves the current key into the previous slot, promotes a freshly minted key to current, re-encrypts existing rows under it, then drops the old key. The procedure is identical across any deployment target.
Mint a new key and place the existing current key in the previous slot:
anchore-enterprise-manager db encryption generate-key # <new-key>Set
ANCHORE_DB_ENCRYPTION_KEY_CURRENTto<new-key>andANCHORE_DB_ENCRYPTION_KEY_PREVIOUSto the key that was current.Restart all replicas. New writes are sealed under the new current key; existing rows still decrypt because the previous key is retained.
Run the rotation pass. It rewrites every row sealed under the previous key using the current key. It requires both slots to be configured - it needs the previous key to decrypt and the current key to re-encrypt:
anchore-enterprise-manager db encryption rotateThen verify no rows remain under a non-current key:
anchore-enterprise-manager db encryption status # ... Rotation status: completeDrop the previous key - remove
ANCHORE_DB_ENCRYPTION_KEY_PREVIOUS, or set it to an empty value, or let the store age outAWSPREVIOUS, then restart. All three are equivalent: an unset or empty slot is dropped from the keyring. The keyring now holds only the current key.
Dropping the previous key is not optional: because the keyring holds at most two keys, the service refuses to start if you add a new key before dropping the previous one. Each rotation must fully complete - rotate, verify, drop - before the next one can begin. The no-rollback gate prevents accidentally reverting to the dropped key on a later boot.
Encrypting Existing Plaintext
rotate is also how you encrypt values still stored as plaintext - which is the second half of opting in, after running with encryption off and then configuring a keyring. Plaintext needs no previous key (there is nothing to decrypt first), so in this case rotate accepts a single configured key and encrypts the plaintext under it:
anchore-enterprise-manager db encryption rotate
A single-key rotate is refused if encryption has previously been enabled. One key with no plaintext to act on signals a key rotation started without the previous key configured, which would leave the outgoing rows unreadable to the rotation. This same plaintext-to-ciphertext conversion also runs automatically as part of db upgrade.
Checking the Current Keyring
Two read-only commands report on the configured keyring without modifying anything:
anchore-enterprise-manager db encryption check-keyring- parses the configured keyring and reports which kids are present, which is primary, and any validation errors. Does not touch the database. Because a kid is the key’s own fingerprint (the leading hex of the SHA-256 of its bytes), comparing the kids reported here across replicas confirms they all loaded the same keyring - without ever revealing the key material.anchore-enterprise-manager db encryption status- counts the rows in each registered encrypted column by their stored kid (primary, secondary, plaintext, unknown). Useful before and after rotation.
Boot Behaviour
The presence of a keyring is the only switch. There is no separate enable/disable setting:
| Keys configured | Deployment has encrypted before | Result |
|---|---|---|
| No | No | Starts normally, encryption off, columns written as plaintext, warning logged at every startup |
| Yes | No | Starts with encryption on, records this key generation, new writes encrypted |
| Yes | Yes | Starts, provided the keyring can still read the stored data |
| No | Yes | Refuses to start - see below |
Running Without Encryption
This is the default state and needs no configuration: supply no keys and the deployment behaves as it did before v6.1, storing these columns as plaintext. A warning naming the affected behaviour is logged on every startup, and anchore-enterprise-manager db encryption check-keyring reports that encryption is off.
We do not recommend running production deployments this way. Because you can enable encryption at any later point without downtime beyond a restart, there is no need to decide at install time.
Once Encryption Is Enabled
The first time a keyring boots, the deployment records that key generation. From that point on, starting without the keyring is refused:
No encryption keys are configured in credentials.database.encryption.keys, but this
deployment has previously registered encryption keys (kids ['c30a71b6ecab']). Starting
without them would store new writes to encrypted columns as PLAINTEXT alongside the
existing encrypted data, and the data already encrypted would be unreadable. Restore
the keyring in credentials.database.encryption.keys before starting the service.
This is deliberate. Booting on would write new secrets in the clear beside unreadable ciphertext, so the failure is loud instead of silent. It is also the error you will see if a key variable goes missing by accident - a Secret key deleted, or a task definition that no longer resolves - so treat it as “the keyring did not arrive”, not as a request to remove encryption. The fix is to restore the keys and restart.
Catastrophic Key Loss
Loss of the encryption keyring is unrecoverable. Every encrypted value in the affected tables becomes permanently unreadable: registry credentials must be re-entered and notification endpoints must be reconfigured from scratch. Restoring from a database backup will not help unless the keyring at the time of backup is also recoverable.
Treat the keyring as you would a database root-credential backup: store it in your secret-management system, back it up to at least one independent location, and ensure that at least two operators have recovery access.
Pruning Unreadable Rows
anchore-enterprise-manager db encryption prune-rows is the targeted tool for removing only the rows you can no longer read - for example after intentionally dropping a key generation - while keeping everything that is still decryptable. It performs a full decryption pass (like anchore-enterprise-manager db encryption verify) and deletes a row only if one of its encrypted columns actually fails to decrypt with the configured keyring, for any reason: a dropped kid, wrong key bytes, a corrupted ciphertext, or a malformed envelope. Every row that still decrypts under a current key is left untouched.
anchore-enterprise-manager db encryption prune-rows
# This will permanently delete rows that cannot be decrypted with the current keyring (retained kids: 4bb06f8e4e3a, 75877bb41d39). Rows that still decrypt are kept:
# registries: 3 rows
# ...
# To instead delete ALL encrypted rows (e.g. the keyring is lost entirely), use `anchore-enterprise-manager db encryption purge-rows`.
# This action is irreversible. Type 'confirm delete' (case-sensitive) to proceed.
Unlike purge-rows, prune-rows requires a configured keyring - it needs to attempt decryption to decide what is recoverable. If the keyring is lost entirely there is nothing to decrypt against; use the Disaster-Recovery Purge instead.
Like purge, prune is deployment-wide (not scoped to an account), requires the exact confirm delete string to proceed, and records an audit event before any rows are deleted. Run anchore-enterprise-manager db encryption verify first to see exactly which rows fail to decrypt before pruning.
Disaster-Recovery Purge
anchore-enterprise-manager db encryption purge-rows is the last-resort tool when the keyring is unrecoverable. It deletes every row in the registered encrypted-column tables whose encrypted column carries the enc:: prefix - that is, every row whose payload would be unrecoverable. If you still hold keys for some of the data, prefer Pruning Unreadable Rows, which removes only the rows that fail to decrypt and keeps the rest.
anchore-enterprise-manager db encryption purge-rows
# This will permanently delete EVERY row containing encrypted data, whether or not it is still decryptable:
# registries: 24 rows
# notifications_endpoint_configurations: 11 rows
# ...
# If you still hold keys for some of this data, use `anchore-enterprise-manager db encryption prune-rows` instead - it removes only rows under kids missing from the keyring and keeps the rest.
# This action is irreversible. Type 'confirm delete' (case-sensitive) to proceed.
# Any other input - including empty - aborts.
The command requires the operator to type confirm delete exactly (case-sensitive). Any other input - including empty input, alternative capitalisations, or surrounding whitespace - aborts safely. The command operates raw against the database and does not need the keyring to be configured, so it works in the exact scenario where the keyring is unreachable.
What you will lose
Purge removes whole records, not just the encrypted fields, so the loss is operational, not merely cryptographic. After a purge you should expect to re-establish, from scratch, anything that depended on the deleted records:
- Registry connection details - the deployment can no longer authenticate to the affected registries, so any registry whose record was removed must be re-added before its images can be analyzed again.
- Notification endpoint setup - configured destinations stop receiving notifications and must be re-created and re-enabled.
Restoring from a database backup does not recover this data unless the keyring that was in use when the backup was taken is also available. Treat purge as a clean slate for the affected records.
Run anchore-enterprise-manager db encryption status first to see exactly which rows will be deleted before invoking purge.