The offline store, online store, and registry each need a place to store data. The operator supports two persistence patterns for each:
- File persistence — a path on a volume (ephemeral or PVC-backed)
- DB/store persistence — an external database or managed store, wired via a Kubernetes Secret
The pattern is the same for all three services; only the store types differ.
| Pattern | Best for | Data survives pod restart? |
|---|---|---|
| File — emptyDir | Dev / CI | No |
| File — PVC (ref) | Single-node prod or testing | Yes (if PVC is retained) |
| File — PVC (create) | Operator-managed storage | Yes |
| DB store | Production, HA, multi-pod | Yes |
The default when no persistence block is set. Data lives on the pod's local disk and is
lost on restart. Suitable for development only.
services:
onlineStore:
server: {} # no persistence block → emptyDirWhen you already have a PVC provisioned (e.g. by your storage team):
services:
onlineStore:
persistence:
file:
path: online_store.db # filename relative to mountPath
pvc:
ref:
name: online-pvc # name of the existing PVC
mountPath: /data/online # where the PVC is mounted in the podservices:
offlineStore:
persistence:
file:
type: duckdb # offline file type: file | dask | duckdb
pvc:
create:
storageClassName: standard
resources:
requests:
storage: 20Gi
mountPath: /data/offlineOmitting storageClassName uses the cluster default StorageClass. Omitting create
entirely creates a PVC with the operator's built-in defaults (1 Gi, default StorageClass).
registry:
local:
persistence:
file:
path: registry.db
pvc:
create: {} # operator defaults: 1 Gi, default StorageClass
mountPath: /data/registryFor production, point the operator at an external database. The operator reads connection
details from a Kubernetes Secret and writes them into feature_store.yaml.
The Secret must contain one key per store component. The key name is the store type
(e.g. postgres, sql, redis). The value is a YAML snippet identical to what you
would write under the corresponding section in feature_store.yaml, minus the type: key
(the operator inserts it from persistence.store.type).
apiVersion: v1
kind: Secret
metadata:
name: feast-data-stores
stringData:
# Key for online store type "postgres"
postgres: |
host: postgres.feast.svc.cluster.local
port: 5432
database: feast
db_schema: public
user: feast
password: feast
# Key for registry type "sql" (SQLAlchemy URL)
sql: |
path: postgresql+psycopg://feast:feast@postgres.feast.svc.cluster.local:5432/feast #pragma: allowlist secret
cache_ttl_seconds: 60
sqlalchemy_config_kwargs:
echo: false
pool_pre_ping: trueReference the Secret from the CR:
services:
onlineStore:
persistence:
store:
type: postgres
secretRef:
name: feast-data-stores # key "postgres" is read automatically
registry:
local:
persistence:
store:
type: sql
secretRef:
name: feast-data-stores # key "sql" is read automaticallyKey lookup rule: the operator looks up the Secret key that matches
persistence.store.type(e.g.type: postgres→ keypostgres). Keep all stores in one Secret or split across multiple — both work.
The Secret key values in the example above hard-code passwords. For production, keep
credentials in a separate Secret and inject them as environment variables using envFrom,
then reference them with ${VAR} substitution in the data-stores Secret value:
apiVersion: v1
kind: Secret
metadata:
name: postgres-creds
stringData:
POSTGRES_USER: feast
POSTGRES_PASSWORD: s3cr3t
---
apiVersion: v1
kind: Secret
metadata:
name: feast-data-stores
stringData:
postgres: |
host: postgres.feast.svc.cluster.local
port: 5432
database: feast
user: ${POSTGRES_USER}
password: ${POSTGRES_PASSWORD}services:
onlineStore:
server:
envFrom:
- secretRef:
name: postgres-creds # expands ${POSTGRES_USER} / ${POSTGRES_PASSWORD}
persistence:
store:
type: postgres
secretRef:
name: feast-data-storestype |
Secret key | SDK docs |
|---|---|---|
sqlite |
sqlite |
SQLite |
redis |
redis |
Redis |
postgres |
postgres |
Postgres |
cassandra |
cassandra |
Cassandra |
hazelcast |
hazelcast |
Hazelcast |
hbase |
hbase |
HBase |
datastore |
datastore |
Datastore |
dynamodb |
dynamodb |
DynamoDB |
bigtable |
bigtable |
Bigtable |
For all store-specific YAML keys (connection options, pool sizes, etc.) see the linked SDK docs — the Secret value accepts the same keys.
type (file) |
Notes |
|---|---|
file |
Default pandas-based parquet offline store |
dask |
Dask-based parallel parquet |
duckdb |
DuckDB in-process analytical engine |
For external DB-backed offline stores (BigQuery, Snowflake, Spark, Trino, etc.), use
persistence.store.type and a Secret with the matching key. See
Offline Stores in the SDK docs.
type |
Secret key | Notes |
|---|---|---|
file |
(file persistence, no Secret) | SQLite-backed file registry |
sql |
sql |
SQLAlchemy URL — supports PostgreSQL, MySQL, SQLite |
snowflake.registry |
snowflake.registry |
Snowflake-backed registry |
For sql, the Secret value is a path: (SQLAlchemy URL) plus optional cache_ttl_seconds
and sqlalchemy_config_kwargs:
# feast-data-stores Secret, key "sql"
sql: |
path: postgresql+psycopg://user:pass@host:5432/feast #pragma: allowlist secret
cache_ttl_seconds: 60apiVersion: v1
kind: Secret
metadata:
name: feast-data-stores
stringData:
redis: |
connection_string: redis.feast.svc.cluster.local:6379
---
apiVersion: feast.dev/v1
kind: FeatureStore
metadata:
name: sample-redis
spec:
feastProject: my_project
services:
onlineStore:
server: {}
persistence:
store:
type: redis
secretRef:
name: feast-data-storesservices:
onlineStore:
persistence:
store:
type: postgres
secretRef:
name: feast-data-stores # reads key "postgres"
offlineStore:
persistence:
file:
type: duckdb
pvc:
create: {}
mountPath: /data/offline
registry:
local:
persistence:
store:
type: sql
secretRef:
name: feast-data-stores # reads key "sql"By default the operator looks up the Secret key that matches persistence.store.type (e.g.
type: postgres → key postgres). To use a different key, set secretKeyName:
services:
onlineStore:
persistence:
store:
type: postgres
secretRef:
name: feast-data-stores
secretKeyName: my_custom_key # reads key "my_custom_key" instead of "postgres"This is useful when a single Secret holds configuration for multiple stores of the same type, or when you want a more descriptive key name.
The operator enforces these rules on Secret values at reconciliation time:
- The Secret key value must be valid YAML that deserializes to a map.
- If the YAML contains a
typefield, its value must match the CR'spersistence.store.type. Otherwise the operator rejects it with an error. Best practice: omittypefrom the Secret. - If the YAML contains a
registry_typefield (for registry stores), the same matching rule applies. - The Secret must exist in the same namespace as the FeatureStore CR.
- Only one of
fileorstoremay be set under each persistence block (enforced by CRD validation).
Below are copy-paste-ready Secret YAML snippets for every operator-supported store type. Each snippet shows the Secret data key and the YAML value the operator expects.
Note: omit the
typefield from Secret values — the operator injects it frompersistence.store.type. Including a matchingtypevalue is tolerated but not recommended.
apiVersion: v1
kind: Secret
metadata:
name: feast-online-store
stringData:
redis: |
connection_string: redis.feast.svc.cluster.local:6379Redis Cluster with SSL:
apiVersion: v1
kind: Secret
metadata:
name: feast-online-store
stringData:
redis: |
redis_type: redis_cluster
connection_string: "redis1:6379,redis2:6379,ssl=true,password=my_password"CR snippet:
services:
onlineStore:
persistence:
store:
type: redis
secretRef:
name: feast-online-storeSDK reference: Redis
apiVersion: v1
kind: Secret
metadata:
name: feast-online-store
stringData:
postgres: |
host: postgres.feast.svc.cluster.local
port: 5432
database: feast
db_schema: public
user: feast
password: feastWith SSL:
apiVersion: v1
kind: Secret
metadata:
name: feast-online-store
stringData:
postgres: |
host: postgres.feast.svc.cluster.local
port: 5432
database: feast
db_schema: public
user: feast
password: feast
sslmode: verify-ca
sslrootcert_path: /path/to/server-ca.pemCR snippet:
services:
onlineStore:
persistence:
store:
type: postgres
secretRef:
name: feast-online-storeSDK reference: Postgres
apiVersion: v1
kind: Secret
metadata:
name: feast-online-store
stringData:
cassandra: |
hosts:
- 192.168.1.1
- 192.168.1.2
- 192.168.1.3
keyspace: KeyspaceName
port: 9042
username: user
password: secret
protocol_version: 5
load_balancing:
local_dc: datacenter1
load_balancing_policy: TokenAwarePolicy(DCAwareRoundRobinPolicy)
read_concurrency: 100
write_concurrency: 100CR snippet:
services:
onlineStore:
persistence:
store:
type: cassandra
secretRef:
name: feast-online-storeSDK reference: Cassandra
apiVersion: v1
kind: Secret
metadata:
name: feast-online-store
stringData:
snowflake.online: |
account: snowflake_deployment.us-east-1
user: user_login
password: user_password
role: SYSADMIN
warehouse: COMPUTE_WH
database: FEASTCR snippet:
services:
onlineStore:
persistence:
store:
type: snowflake.online
secretRef:
name: feast-online-storeSDK reference: Snowflake
apiVersion: v1
kind: Secret
metadata:
name: feast-online-store
stringData:
dynamodb: |
region: us-west-2
batch_size: 100CR snippet:
services:
onlineStore:
persistence:
store:
type: dynamodb
secretRef:
name: feast-online-storeSDK reference: DynamoDB
apiVersion: v1
kind: Secret
metadata:
name: feast-online-store
stringData:
bigtable: |
project_id: my_gcp_project
instance: my_bigtable_instanceCR snippet:
services:
onlineStore:
persistence:
store:
type: bigtable
secretRef:
name: feast-online-storeSDK reference: Bigtable
apiVersion: v1
kind: Secret
metadata:
name: feast-online-store
stringData:
datastore: |
project_id: my_gcp_project
namespace: my_datastore_namespaceCR snippet:
services:
onlineStore:
persistence:
store:
type: datastore
secretRef:
name: feast-online-storeSDK reference: Datastore
apiVersion: v1
kind: Secret
metadata:
name: feast-online-store
stringData:
mysql: |
host: mysql.feast.svc.cluster.local
port: 3306
database: feast
user: feast
password: feastCR snippet:
services:
onlineStore:
persistence:
store:
type: mysql
secretRef:
name: feast-online-storeSDK reference: MySQL
apiVersion: v1
kind: Secret
metadata:
name: feast-online-store
stringData:
hazelcast: |
cluster_name: dev
cluster_members:
- "localhost:5701"
key_ttl_seconds: 36000CR snippet:
services:
onlineStore:
persistence:
store:
type: hazelcast
secretRef:
name: feast-online-storeSDK reference: Hazelcast
apiVersion: v1
kind: Secret
metadata:
name: feast-online-store
stringData:
hbase: |
host: hbase-thrift.feast.svc.cluster.local
port: "9090"
connection_pool_size: 4CR snippet:
services:
onlineStore:
persistence:
store:
type: hbase
secretRef:
name: feast-online-storeSDK reference: HBase
The following types also use the same pattern (persistence.store.type + secretRef).
Place the driver-specific YAML keys from the SDK docs under the matching Secret key:
type |
Secret key | SDK docs |
|---|---|---|
sqlite |
sqlite |
SQLite |
singlestore |
singlestore |
SingleStore |
elasticsearch |
elasticsearch |
Elasticsearch |
qdrant |
qdrant |
Qdrant |
couchbase.online |
couchbase.online |
Couchbase |
milvus |
milvus |
Milvus |
mongodb |
mongodb |
MongoDB |
hybrid |
hybrid |
Hybrid |
Offline DB stores follow the same pattern. The type field tells the operator which
store driver to use; the Secret value holds the connection parameters.
apiVersion: v1
kind: Secret
metadata:
name: feast-offline-store
stringData:
snowflake.offline: |
account: snowflake_deployment.us-east-1
user: user_login
password: user_password
role: SYSADMIN
warehouse: COMPUTE_WH
database: FEAST
schema: PUBLICCR snippet:
services:
offlineStore:
persistence:
store:
type: snowflake.offline
secretRef:
name: feast-offline-storeSDK reference: Snowflake
apiVersion: v1
kind: Secret
metadata:
name: feast-offline-store
stringData:
bigquery: |
dataset: feast_bq_dataset
project_id: my_gcp_projectCR snippet:
services:
offlineStore:
persistence:
store:
type: bigquery
secretRef:
name: feast-offline-storeSDK reference: BigQuery
apiVersion: v1
kind: Secret
metadata:
name: feast-offline-store
stringData:
postgres: |
host: postgres.feast.svc.cluster.local
port: 5432
database: feast
db_schema: public
user: feast
password: feastCR snippet:
services:
offlineStore:
persistence:
store:
type: postgres
secretRef:
name: feast-offline-storeSDK reference: Postgres
type |
Secret key | SDK docs |
|---|---|---|
redshift |
redshift |
Redshift |
spark |
spark |
Spark |
trino |
trino |
Trino |
athena |
athena |
Athena |
mssql |
mssql |
MSSQL |
couchbase.offline |
couchbase.offline |
Couchbase |
clickhouse |
clickhouse |
ClickHouse |
ray |
ray |
Ray |
oracle |
oracle |
Oracle |
The most common production registry. Uses a SQLAlchemy URL to connect to PostgreSQL, MySQL, or SQLite:
apiVersion: v1
kind: Secret
metadata:
name: feast-registry-store
stringData:
sql: |
path: postgresql+psycopg://feast:feast@postgres.feast.svc.cluster.local:5432/feast #pragma: allowlist secret
cache_ttl_seconds: 60
sqlalchemy_config_kwargs:
echo: false
pool_pre_ping: trueCR snippet:
services:
registry:
local:
persistence:
store:
type: sql
secretRef:
name: feast-registry-storeSDK reference: SQL Registry
apiVersion: v1
kind: Secret
metadata:
name: feast-registry-store
stringData:
snowflake.registry: |
account: snowflake_deployment.us-east-1
user: user_login
password: user_password
role: SYSADMIN
warehouse: COMPUTE_WH
database: FEAST
schema: PUBLIC
cache_ttl_seconds: 60CR snippet:
services:
registry:
local:
persistence:
store:
type: snowflake.registry
secretRef:
name: feast-registry-storeSDK reference: Snowflake Registry
You can combine all store configurations into a single Secret:
apiVersion: v1
kind: Secret
metadata:
name: feast-data-stores
stringData:
redis: |
connection_string: redis.feast.svc.cluster.local:6379
snowflake.offline: |
account: snowflake_deployment.us-east-1
user: user_login
password: user_password
role: SYSADMIN
warehouse: COMPUTE_WH
database: FEAST
schema: PUBLIC
sql: |
path: postgresql+psycopg://feast:feast@postgres.feast.svc.cluster.local:5432/feast #pragma: allowlist secret
cache_ttl_seconds: 60
---
apiVersion: feast.dev/v1
kind: FeatureStore
metadata:
name: production-store
spec:
feastProject: my_project
services:
onlineStore:
persistence:
store:
type: redis
secretRef:
name: feast-data-stores
offlineStore:
persistence:
store:
type: snowflake.offline
secretRef:
name: feast-data-stores
registry:
local:
persistence:
store:
type: sql
secretRef:
name: feast-data-storesThe batchEngine is the only operator component that uses a ConfigMap rather than a
Secret for its configuration. The ConfigMap must contain a YAML value under key config
(default) or the key specified in configMapKey.
Unlike store Secrets, the batch engine ConfigMap value must include the type field:
apiVersion: v1
kind: ConfigMap
metadata:
name: feast-batch-engine
data:
config: |
type: spark
master: local
spark_conf:
spark.executor.memory: 4g
---
apiVersion: feast.dev/v1
kind: FeatureStore
metadata:
name: sample
spec:
feastProject: my_project
batchEngine:
configMapRef:
name: feast-batch-engine
# configMapKey: config # optional, defaults to "config"See Guide 6 — Batch & Jobs for full details.
| Symptom | Likely cause | Fix |
|---|---|---|
secret key X doesn't exist in secret Y |
The Secret key name doesn't match the store type |
Either rename the Secret key to match type, or set secretKeyName in the CR |
secret X contains invalid value |
The Secret value is not valid YAML | Check indentation and quoting in the stringData value |
contains tag named type with value X |
The Secret includes a type field that doesn't match the CR's persistence.store.type |
Remove type from the Secret value, or correct it to match |
invalid secret X for offline store |
The referenced Secret doesn't exist | Create the Secret in the same namespace as the FeatureStore CR |
One selection required between file or store |
Both file and store are set under a persistence block |
Keep only one — choose either file persistence or store (DB) persistence |