Storage Configuration
Understand and configure storage for your Anchore Enterprise deployment
Anchore Enterprise uses three categories of storage, each serving a distinct purpose. This page describes each type and provides configuration guidance.
Anchore recommends configuring scratch and layer caching to suit your workload, then using the PostgreSQL database driver for the Active Data Set and external object storage (such as Amazon S3 or an S3-compatible service) for the Archive Data Set. Use
Archive Rules and
Artifact Lifecycle Policies to keep your Active Data Set small.
| Storage Type | Purpose | Required? |
|---|
| Scratch space | Temporary storage for downloading and unpacking image layers during analysis | Optional (recommended) |
| Layer cache | Caches downloaded layers to speed up repeated analysis | Optional |
| Object storage and analysis archiving | Stores analysis data, policies, and archived results | Required (defaults to PostgreSQL) |
During image analysis, Anchore Enterprise downloads and extracts image layers to a local directory on each analyzer worker. This scratch space is ephemeral and must not be shared between services.
Sizing guidance: Without layer caching, size the scratch volume to at least three times the uncompressed size of the largest image you expect to analyze.
By default, Anchore Enterprise uses /tmp inside the container. To use a dedicated volume, mount it and set tmp_dir in your config.yaml:
A dedicated temporary volume is required on container hosts that use OverlayFS or OverlayFS2 with a kernel older than 4.13, due to a kernel driver bug.
For full details, see Scratch Configuration.
Layer caching reduces analysis time and network usage by storing previously downloaded image layers so they can be reused across analyses.
Sizing guidance: Size the layer cache to at least three times the uncompressed image size plus 4GB. The minimum supported size is 1GB. The cache uses a least recently used (LRU) eviction policy and stores files in the anchore_layercache directory under your configured tmp_dir.
Helm
In your values.yaml:
anchoreConfig:
analyzer:
layer_cache_max_gigabytes: 4
Docker Compose
In your config.yaml under the analyzer service:
services:
analyzer:
layer_cache_max_gigabytes: 4
For full details, see Layer Caching Configuration.
Anchore Enterprise stores less-structured data — image manifests, analysis reports, and policy evaluations — in an internal object store. By default this uses PostgreSQL, but it can be offloaded to Amazon S3 or an S3-compatible provider for improved scalability and cost management.
Anchore Enterprise separates storage into two logical data sets:
- Active Data Set: The working set of current image analyses, queried frequently by the policy engine and API. Configured via the catalog’s
object_store key. - Archive Data Set: Completed analyses moved out of active storage to reduce database size. Can be restored on demand. Configured via the catalog’s
analysis_archive key.
Sizing guidance: Estimate approximately 10MB per image across both data sets.
Active Data Set
PostgreSQL is the default and requires no additional configuration. To offload to Amazon S3, update the catalog’s object_store in your config.yaml:
services:
catalog:
object_store:
compression:
enabled: false
min_size_kbytes: 100
storage_driver:
name: 's3'
config:
iamauto: true
region: <AWS_REGION>
bucket: 'anchore-object-store'
create_bucket: true
Anchore strongly recommends using IAM roles for secure access to Amazon S3.
For the full list of driver options (PostgreSQL, Amazon S3, S3-compatible), see Object Storage Configuration.
Archive Data Set
The analysis archive uses the same storage drivers as the active data set but is configured separately. This lets you direct archived data to lower-cost storage independently of your working set.
Example — keep the active data set in PostgreSQL while archiving to Amazon S3:
services:
catalog:
object_store:
compression:
enabled: false
min_size_kbytes: 100
storage_driver:
name: db
config: {}
analysis_archive:
compression:
enabled: false
min_size_kbytes: 100
storage_driver:
name: 's3'
config:
iamauto: true
region: <AWS_REGION>
bucket: 'anchore-archive'
create_bucket: true
If no analysis_archive key is present in config.yaml, Anchore Enterprise falls back to using the object_store configuration for archive data.
For full details and instructions on migrating between backends, see Analysis Archive Configuration.
1 - 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_load in your values file. In v6.x the chart no longer accepts this as the ANCHORE_POLICY_ENGINE_ENABLE_PACKAGE_DB_LOAD environment variable via extraEnv. - Docker Compose: set the
ANCHORE_POLICY_ENGINE_ENABLE_PACKAGE_DB_LOAD environment 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_dump output.
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.
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_CURRENT to <new-key> and ANCHORE_DB_ENCRYPTION_KEY_PREVIOUS to 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 rotate
Then verify no rows remain under a non-current key:
anchore-enterprise-manager db encryption status
# ... Rotation status: complete
Drop the previous key - remove ANCHORE_DB_ENCRYPTION_KEY_PREVIOUS, or set it to an empty value, or let the store age out AWSPREVIOUS, 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.
Enabling encryption is permanent. Once the deployment has recorded a key generation, encryption cannot be turned off again: disabling it is not supported, and neither is converting encrypted rows back to plaintext. Treat enabling it as a one-time decision, and make certain the keyring is backed up somewhere you can recover it from before you begin - see
Catastrophic Key Loss.
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.
3.3 - Amazon S3
This page describes configuration when using Amazon S3 for object storage with IAM role authentication.
Anchore strongly recommends using IAM roles for secure access to Amazon S3.
IAM Role Authentication
For Anchore Enterprise to use an AWS IAM role, the environment it runs in (such as an EC2 instance, ECS task, or Kubernetes pod) must have an AWS IAM role with the necessary S3 bucket permissions:
"Action": [
"s3:PutObject*",
"s3:GetObject*",
"s3:DeleteObject*"
]
If using KMS w/ s3 Bucket Key you will need the following as well:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "S3BucketAccess",
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": "arn:aws:s3:::myapp-prod-data-bucket"
},
{
"Sid": "S3ObjectAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::myapp-prod-data-bucket/*"
},
{
"Sid": "KMSAccessToSpecificKey",
"Effect": "Allow",
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "arn:aws:kms:us-east-1:123456789012:key/11111111-2222-3333-4444-555555555555"
}
]
}
The KMS key’s IAM resource policy needs to grant the S3 principal access as well. By default KMS keys grant access to the AWS Account’s IAM root which would work in combination with the policy above. Please note that this is meant to serve as a working example as opposed to being the most restrive configuration possible.
Reference: https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-key.html?icmpid=docs_amazons3_console
In your values.yaml file storage_driver section, set the iamauto parameter to true:
services:
catalog:
object_store:
storage_driver:
name: 's3'
config:
iamauto: true
With iamauto: true, Anchore Enterprise automatically adopts the IAM role of its host environment. This is the most secure method for granting Amazon S3 access as it removes the need to store credentials such as ACCESS_KEY and SECRET_KEY in configuration files. For docker-compose environments the IAM role will be shared with the instance profile. For Helm deployments there are multiple options for assigning the IAM role to Anchore Enterprise:
- Pod Identity
- IRSA
- EKS Worker Role (least secure)
For Pod Identity and IRSA you can designate the name of the service account via the Helm values file as follows:
serviceAccountName: "anchore-sa" # This should match the name of the service account created in EKS
If using an IAM role to grant access to ECR then you will need to combine the S3 permissions with the ECR permissions in the same IAM role.
Please note that serviceAccountName needs to be set on the OSAAMigrationJob specifically, if running the OSAA migration. In doing so this will give the migration job access to S3 but it will not be able to spin down the Anchore Enterprise pods during the migration.
Other S3 Configuration Options
Below are other configurable parameters for the Anchore Enterprise S3 driver:
The Anchore Enterprise S3 driver supports document compression to reduce storage space. Set to true to enable or false to disable and
min_size_kbytes sets the minimum document size in kilobytes to be compressed.
config:
...
compression:
enabled: true
min_size_kbytes: 100
region - the AWS region of your Amazon S3 bucket. It is required if url is not specified.
bucket - the name of the Amazon S3 bucket for Anchore’s data storage.
create_bucket - if set to true, Anchore Enterprise will attempt to create the bucket if it doesn’t exist. It is, however, recommended to pre-create the bucket.
Example
Here is a full configuration example for the S3 driver using IAM role authentication (config.yaml used in docker-compose):
services:
catalog:
object_store:
verify_content_digests: true
storage_driver:
name: 's3'
config:
# AWS IAM role authentication
iamauto: true
# Amazon S3 bucket configuration
region: 'us-east-1'
bucket: 'my-anchore-data'
create_bucket: false
# Optional compression
compression:
enabled: true
min_size_kbytes: 100
Helm chart values for kubernetes deployment:
anchoreConfig:
catalog:
object_store:
verify_content_digests: true
storage_driver:
name: 's3'
config:
# AWS IAM role authentication
iamauto: true
# Amazon S3 bucket configuration
region: 'us-east-1'
bucket: 'my-anchore-data'
create_bucket: false
# Optional compression
compression:
enabled: true
min_size_kbytes: 100
3.4 - S3-Compatible
Anchore Enterprise can be configured to use third-party S3 API-compatible object storage systems.
Anchore strongly recommends using Kubernetes secrets rather than plaintext entries in your values.yaml to store your S3-compatible access keys.
Example Configuration
object_store:
compression:
enabled: False
min_size_kbytes: 100
storage_driver:
name: 's3'
config:
access_key: 'MY_ACCESS_KEY'
secret_key: 'MY_SECRET_KEY'
#iamauto: True
url: 'https://my-s3-compatible-endpoint.example.com:optional_port'
region: False
bucket: "anchorearchive"
create_bucket: True
Configuration Options
The following additional configuration parameters can be used.
Compression
The S3 driver supports compression of documents. The documents are JSON formatted and will see significant reduction in
size through compression there is an overhead incurred by running compression and decompression on every access of these
documents. Anchore Enterprise can be configured to only compress documents above a certain size to reduce unnecessary
overhead. In the example below any document over 100kb in size will be compressed.
Authentication
Anchore Enterprise can authenticate against the S3-compatible service using access keys.
Endpoints
url - (required) A URL to set to reach an S3-API compatible service. Note that if the URL is configured, the region config value is ignored, as this is only used for Amazon S3.
Buckets
bucket - (required) The name of the S3 bucket that Anchore Enterprise will use for storing data.
create_bucket- (default: false) Try to create the bucket if it doesn’t already exist. This should be used very sparingly. For most cases, you should pre-create the bucket so that it has the permissions you desire, then set this to false.
Storing Object Store API keys in a Kubernetes Secret
You can configure your object store API keys to be pulled from a Kubernetes Secret as follows:
extraEnv:
- name: ANCHORE_OBJ_STORAGE_ACCESS_KEY
valueFrom:
secretKeyRef:
name: minio-secret
key: accessKey
- name: ANCHORE_OBJ_STORAGE_SECRET_KEY
valueFrom:
secretKeyRef:
name: minio-secret
key: secretKey
anchoreConfig:
catalog:
object_store:
storage_driver:
name: s3
config:
access_key: ${ANCHORE_OBJ_STORAGE_ACCESS_KEY}
secret_key: ${ANCHORE_OBJ_STORAGE_SECRET_KEY}
In this example the secret was called minio-secret but you can use whatever name you would like. The secret looks as follows:
apiVersion: v1
data:
accessKey: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
secretKey: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
kind: Secret
3.5 - Migrating Data to New Drivers
To migrate data from one driver to another (e.g. DB to S3), Anchore Enterprise includes capabilities that automate the process in the anchore-manager tool packaged with the system. For Helm-based deployments, this is further automated via Helm upgrade helpers, whereas for Docker Compose deployments the tool must be run manually.
The migration process is an offline process; Anchore Enterprise is not designed to handle an online migration.
The object storage migration process migrates any data stored in the source config to the destination configuration, if the analysis archive is configured to use the same storage backend as the primary object store then that data is migrated along with all other data, but if the source or destination configurations define different storage backends for the analysis archive than that which is used by the primary object store, then additional parameters are necessary to indicate which configurations to migrate to/from.
The most common migration patterns are:
- Migrate from a single backend configuration to a split configuration to keep the Active Data Set in the DB and then move the Archive Data Set (analysis archive data) to an external system (db -> db + s3)
- Migrate from a dual-backend configuration to a single-backend configuration with a different config (e.g. db + s3-compatible -> s3-compatible)
At a high-level the process is:
- Shutdown all Anchore Enterprise services and components. The system should be fully offline, but the database must be online and available. For a docker compose install, this is achieved by simply stopping the engine container, but not deleting it.
- Prepare a new
config.yaml that includes the new driver configuration for the destination of the migration (dest-config.yaml) in the same location as the existing config.yaml - Test a new
dest-config.yaml to ensure correct configuration - Run the migration
- Get coffee… this could take a while if you have a lot of analysis data
- When complete, view the results
- Ensure the
dest-config.yaml is in place for all the components as config.yaml - Start Anchore Enterprise services and components.
EXAMPLE: Migration of Object Store in Helm-based Deployment from DB to Amazon S3
The Anchore Enterprise Helm Chart provides a way to run the migration steps listed in this page automatically by spinning up a job and crafting the configs required and running the necessary migration commands. Further information is available via instructions found in our Helm Chart here. Below are example configurations:
Anchore recommends using IAM roles for access to Amazon S3, as in the example below, you can find role configuration details
here.
Example config
osaaMigrationJob:
enabled: true # note that we are enabling the migration job
analysisArchiveMigration:
run: true
bucket: "analysis_archive"
mode: to_analysis_archive
analysis_archive:
enabled: true
compression:
enabled: true
min_size_kbytes: 100
storage_driver:
name: s3
config:
iamauto: true
region: <MY_AWS_REGION>
bucket: anchore-analysis-archive
objectStoreMigration:
run: true
object_store:
verify_content_digests: true
compression:
enabled: true
min_size_kbytes: 100
storage_driver:
name: s3
config:
iamauto: true
region: <MY_AWS_REGION>
bucket: anchore-object-store
# When running the migration you may omit or specify the following which will act as the source of the migration
anchoreConfig:
default_admin_password: foobar
catalog:
analysis_archive:
enabled: true
compression:
enabled: true
min_size_kbytes: 100
storage_driver:
name: db
config: {}
object_store:
verify_content_digests: true
compression:
enabled: true
min_size_kbytes: 100
storage_driver:
name: db
config: {}
Once the migration has completed disable the osaaMigrationJob and move the configurations to the anchoreConfig.catalog section.
EXAMPLE: Migration of Object Store in Helm-based Deployment from DB to S3-compatible
Example config
osaaMigrationJob:
enabled: true # note that we are enabling the migration job
analysisArchiveMigration:
run: true # we are specifying to run the analysis_archive migration
bucket: "analysis_archive"
mode: to_analysis_archive
# the deployment will be migrated to use the following configs for catalog.analysis_archive
analysis_archive:
enabled: true
compression:
enabled: true
min_size_kbytes: 100
storage_driver:
name: s3
config:
access_key: MY_ACCESS_KEY
secret_key: MY_SECRET_KEY
url: 'https://my-s3-compatible-endpoint.example.com:optional_port'
region: null
bucket: anchore-analysis-archive
objectStoreMigration:
run: true
# note that since this is the same as anchoreConfig.catalog.object_store, the migration
# command for migrating the object store will still run, but it will not do anything as there
# is nothing to be done
object_store:
verify_content_digests: true
compression:
enabled: true
min_size_kbytes: 100
storage_driver:
name: s3
config:
access_key: MY_ACCESS_KEY
secret_key: MY_SECRET_KEY
url: 'https://my-s3-compatible-endpoint.example.com:optional_port'
region: null
bucket: anchore-object-store
# When running the migration you may omit or specify the following which will act as the source of the migration
anchoreConfig:
default_admin_password: foobar
catalog:
analysis_archive:
enabled: true
compression:
enabled: true
min_size_kbytes: 100
storage_driver:
name: db
config: {}
object_store:
verify_content_digests: true
compression:
enabled: true
min_size_kbytes: 100
storage_driver:
name: db
config: {}
EXAMPLE: Migration of Object Store in Docker Compose from DB to S3-compatible
The following example demonstrates migration for a Docker Compose deployment.
Preparing for Migration
For the migration process you will need:
- The original
config.yaml used by the services already, if services are split out or using different config.yaml for different services, you need the config.yaml used by the catalog services - An updated
config.yaml (named dest-config.yaml in this example), with the archive driver section of the catalog service config set to the config you want to migrate to - The db connection string from
config.yaml, this is needed by the anchore-manager script directly - Credentials and resources (bucket etc) for the destination of the migration
If Anchore Enterprise is deployed using Docker Compose, the migration must be manually initiated using the anchore-manager script. The following is an example migration for Anchore Enterprise deployed via Docker Compose on a single host with a local postgresql container. This process requires that you run the command in a location that has access to both the source archive driver configuration and the new archive driver configuration.
Step 1: Shutdown all services
All services should be stopped, but the postgresql db must still be available and running. You can use the docker compose stop command and supply all services names except the DB:
docker compose stop anchore-analyzer anchore-api anchore-catalog anchore-policy-engine anchore-queue anchore-enterprise-api-gateway anchore-enterprise-rbac-service redis
Step 2: Prepare a new config.yaml
Both the original and new configurations are needed, so create a copy and update the archive driver section to the configuration you want to migrate to
cd config
cp config.yaml dest-config.yaml
<edit dest-config.yaml>
Step 3: Test the destination config
Assuming that config is dest-config.yaml:
$ docker compose run anchore-catalog /bin/bash
[root@3209ad44d7bb ~]# anchore-manager objectstorage --db-connect ${db} check /config/dest-config.yaml
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB params: {"db_pool_size": 30, "db_connect": "postgresql+pg8000://postgres:postgres.dev@postgres-dev:5432/postgres", "db_connect_args": {"ssl": false, "connect_timeout": 120, "timeout": 30}, "db_pool_max_overflow": 100}
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB connection configured: True
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB attempting to connect...
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB connected: True
[MainThread] [anchore_manager.cli.objectstorage/check()] [INFO] Using config file /config/dest-config.yaml
[MainThread] [anchore_engine.subsys.object_store.operations/initialize()] [INFO] Archive initialization complete
[MainThread] [anchore_manager.cli.objectstorage/check()] [INFO] Checking existence of test document with user_id = test, bucket = anchorecliconfigtest and archive_id = cliconfigtest
[MainThread] [anchore_manager.cli.objectstorage/check()] [INFO] Creating test document with user_id = test, bucket = anchorecliconfigtest and archive_id = cliconfigtest
[MainThread] [anchore_manager.cli.objectstorage/check()] [INFO] Checking document fetch
[MainThread] [anchore_manager.cli.objectstorage/check()] [INFO] Removing test object
[MainThread] [anchore_manager.cli.objectstorage/check()] [INFO] Archive config check completed successfully
Step 3a: Test the current config.yaml
If you are running the migration for a different location than one of the Anchore Enterprise containers, same as above but using /config/config.yaml as the input to check (skipped in this instance since we’re running the migration from the same container)
Step 4: Run the migration
By default, the migration process will remove data from the source once it has confirmed it has been copied to the destination and the metadata has been updated in the anchore db. To skip the deletion on the source, use the --nodelete option. It is the safest option, but if you use it, you are responsible for removing the data later.
[root@3209ad44d7bb ~]# anchore-manager objectstorage --db-connect ${db} migrate /config/config.yaml /config/dest-config.yaml
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB params: {"db_pool_size": 30, "db_connect": "postgresql+pg8000://postgres:postgres.dev@postgres-dev:5432/postgres", "db_connect_args": {"ssl": false, "connect_timeout": 120, "timeout": 30}, "db_pool_max_overflow": 100}
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB connection configured: True
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB attempting to connect...
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB connected: True
[MainThread] [anchore_manager.cli.objectstorage/migrate()] [INFO] Loading configs
[MainThread] [anchore_manager.cli.objectstorage/migrate()] [INFO] Migration from config: {
"storage_driver": {
"config": {},
"name": "db"
},
"compression": {
"enabled": false,
"min_size_kbytes": 100
}
}
[MainThread] [anchore_manager.cli.objectstorage/migrate()] [INFO] Migration to config: {
"storage_driver": {
"config": {
"access_key": "9EB92C7W61YPFQ6QLDOU",
"create_bucket": true,
"url": "http://minio-ephemeral-test:9000/",
"region": false,
"bucket": "anchore-engine-testing",
"prefix": "internaltest",
"secret_key": "TuHo2UbBx+amD3YiCeidy+R3q82MPTPiyd+dlW+s"
},
"name": "s3"
},
"compression": {
"enabled": true,
"min_size_kbytes": 100
}
}
Performing this operation requires *all* anchore-engine services to be stopped - proceed? (y/N)y
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Initializing migration from {'storage_driver': {'config': {}, 'name': 'db'}, 'compression': {'enabled': False, 'min_size_kbytes': 100}} to {'storage_driver': {'config': {'access_key': '9EB92C7W61YPFQ6QLDOU', 'create_bucket': True, 'url': 'http://minio-ephemeral-test:9000/', 'region': False, 'bucket': 'anchore-engine-testing', 'prefix': 'internaltest', 'secret_key': 'TuHo2UbBx+amD3YiCeidy+R3q82MPTPiyd+dlW+s'}, 'name': 's3'}, 'compression': {'enabled': True, 'min_size_kbytes': 100}}
[MainThread] [anchore_engine.subsys.object_store.migration/migration_context()] [INFO] Initializing source object_store: {'storage_driver': {'config': {}, 'name': 'db'}, 'compression': {'enabled': False, 'min_size_kbytes': 100}}
[MainThread] [anchore_engine.subsys.object_store.migration/migration_context()] [INFO] Initializing dest object_store: {'storage_driver': {'config': {'access_key': '9EB92C7W61YPFQ6QLDOU', 'create_bucket': True, 'url': 'http://minio-ephemeral-test:9000/', 'region': False, 'bucket': 'anchore-engine-testing', 'prefix': 'internaltest', 'secret_key': 'TuHo2UbBx+amD3YiCeidy+R3q82MPTPiyd+dlW+s'}, 'name': 's3'}, 'compression': {'enabled': True, 'min_size_kbytes': 100}}
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Migration Task Id: 1
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Entering main migration loop
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Migrating 7 documents
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Deleting document on source after successful migration to destination. Src = db://admin/policy_bundles/2c53a13c-1765-11e8-82ef-23527761d060
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Deleting document on source after successful migration to destination. Src = db://admin/manifest_data/sha256:0873c923e00e0fd2ba78041bfb64a105e1ecb7678916d1f7776311e45bf5634b
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Deleting document on source after successful migration to destination. Src = db://admin/analysis_data/sha256:0873c923e00e0fd2ba78041bfb64a105e1ecb7678916d1f7776311e45bf5634b
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Deleting document on source after successful migration to destination. Src = db://admin/image_content_data/sha256:0873c923e00e0fd2ba78041bfb64a105e1ecb7678916d1f7776311e45bf5634b
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Deleting document on source after successful migration to destination. Src = db://admin/manifest_data/sha256:a0cd2c88c5cc65499e959ac33c8ebab45f24e6348b48d8c34fd2308fcb0cc138
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Deleting document on source after successful migration to destination. Src = db://admin/analysis_data/sha256:a0cd2c88c5cc65499e959ac33c8ebab45f24e6348b48d8c34fd2308fcb0cc138
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Deleting document on source after successful migration to destination. Src = db://admin/image_content_data/sha256:a0cd2c88c5cc65499e959ac33c8ebab45f24e6348b48d8c34fd2308fcb0cc138
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Migration result summary: {"last_state": "running", "executor_id": "3209ad44d7bb:37:139731996518208:", "archive_documents_migrated": 7, "last_updated": "2018-08-15T18:03:52.951364", "online_migration": null, "created_at": "2018-08-15T18:03:52.951354", "migrate_from_driver": "db", "archive_documents_to_migrate": 7, "state": "complete", "migrate_to_driver": "s3", "ended_at": "2018-08-15T18:03:53.720554", "started_at": "2018-08-15T18:03:52.949956", "type": "archivemigrationtask", "id": 1}
[MainThread] [anchore_manager.cli.objectstorage/migrate()] [INFO] After this migration, your anchore-engine config.yaml MUST have the following configuration options added before starting up again:
compression:
enabled: true
min_size_kbytes: 100
storage_driver:
config:
access_key: 9EB92C7W61YPFQ6QLDOU
bucket: anchore-engine-testing
create_bucket: true
prefix: internaltest
region: false
secret_key: TuHo2UbBx+amD3YiCeidy+R3q82MPTPiyd+dlW+s
url: http://minio-ephemeral-test:9000/
name: s3
If something goes wrong you can reverse the parameters of the migrate command to migrate back to the original configuration (e.g. migrate /config/dest-config.yaml /config/config.yaml)
Step 5: Get coffee!
The migration time will depend on the amount of data and the source and destination systems performance.
Step 6: View migration results summary
[root@3209ad44d7bb ~]# anchore-manager objectstorage --db-connect ${db} list-migrations
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB params: {"db_pool_size": 30, "db_connect": "postgresql+pg8000://postgres:postgres.dev@postgres-dev:5432/postgres", "db_connect_args": {"ssl": false, "connect_timeout": 120, "timeout": 30}, "db_pool_max_overflow": 100}
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB connection configured: True
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB attempting to connect...
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB connected: True
id state start time end time from to migrated count total to migrate last updated
1 complete 2018-08-15T18:03:52.949956 2018-08-15T18:03:53.720554 db s3 7 7 2018-08-15T18:03:53.724628
This lists all migrations for the service and the number of objects migrated. If you’ve run multiple migrations you’ll see multiple rows in this response.
Step 7: Replace old config.yaml with updated dest-config.yaml
You should now permanently move into place the new configuration, replacing the old.
[root@3209ad44d7bb ~]# cp /config/config.yaml /config/config.old.yaml
[root@3209ad44d7bb ~]# cp /config/dest-config.yaml /config/config.yaml
Step 8: Restart Anchore Enterprise services
Run the following command at the same location as your docker-compose file to bring all services back up:
docker compose start
The system should now be up and running using the new configuration! You can verify with the anchorectl command by fetching a policy, which will have been migrated:
$anchorectl policy list
✔ Fetched policies
┌─────────────────────────┬──────────────────────────────────────┬────────┬──────────────────────┐
│ NAME │ POLICY ID │ ACTIVE │ UPDATED │
├─────────────────────────┼──────────────────────────────────────┼────────┼──────────────────────┤
│ Default bundle │ 2c53a13c-1765-11e8-82ef-23527761d060 │ true │ 2022-07-14T22:52:27Z │
│ anchore_security_only │ anchore_security_only │ false │ 2022-07-14T22:52:27Z │
│ anchore_cis_1.13.0_base │ anchore_cis_1.13.0_base │ false │ 2022-07-14T22:52:27Z │
└─────────────────────────┴──────────────────────────────────────┴────────┴──────────────────────┘
$ anchorectl -o json-raw policy get 2c53a13c-1765-11e8-82ef-23527761d060
[
{
"blacklisted_images": [],
"comment": "Default bundle",
"id": "2c53a13c-1765-11e8-82ef-23527761d060",
... <lots of json>
If that returns the content properly, then you’re all done!