Built and maintained by Digitalis.IO
Vals-Operator keeps Kubernetes Secrets in sync with your external secrets store.
Write a ValsSecret that points at Vault, OpenBao, AWS Secrets Manager, GCP Secret
Manager or any other store supported by vals, and
the operator creates the Kubernetes Secret and keeps it up to date. It can also issue
dynamic database credentials from the Vault/OpenBao database secrets engine, rotate
database passwords in the database itself, and restart the workloads that consume a
secret when it changes.
Built at Digitalis, where we run databases for a living — which is why the database features are here alongside plain secret syncing.
Install the operator, pointed at an OpenBao (or Vault) server using Kubernetes auth:
helm install vals-operator oci://ghcr.io/digitalis-io/helm-charts/vals-operator \
--create-namespace -n vals-operator \
--set "openbao.enabled=true" \
--set "openbao.address=http://openbao.openbao:8200" \
--set "openbao.auth.kubernetes.roleId=vals-operator"Helm 3.7 or earlier, or any other backend? See Installation and Secrets backends.
Create a secret:
apiVersion: digitalis.io/v1
kind: ValsSecret
metadata:
name: database-credentials
spec:
name: db-creds # Name of the Kubernetes Secret to create
data:
username:
ref: ref+vault://secret/database/username
password:
ref: ref+vault://secret/database/passwordkubectl apply -f database-credentials.yaml
kubectl get secret db-credsThe Secret is re-read from the backend every 5 minutes by default, and every change is recorded as an Event:
kubectl describe valssecret database-credentialsAny key in data is available as a template variable, so a whole configuration file
can be assembled from individual secret values. rollout restarts the workloads that
consume the Secret when it changes.
apiVersion: digitalis.io/v1
kind: ValsSecret
metadata:
name: app-config
spec:
name: app-config
ttl: 3600 # Seconds between backend reads
data:
username:
ref: ref+awssecrets://kube/test#username
password:
ref: ref+awssecrets://kube/test#password
ca-cert:
ref: ref+vault://secret/app/ca-cert
encoding: base64 # Decoded before it is stored
environment:
ref: production # No ref+ prefix: stored verbatim
template:
config.yaml: |
# Generated by vals-operator on {{ now | date "2006-01-02" }}
environment: {{ .environment }}
database:
username: {{ .username }}
password: {{ .password }}
rollout:
- kind: Deployment
name: myapp
- kind: StatefulSet
name: myapp-workersDbSecret requests credentials from the Vault/OpenBao
database secrets engine
and renews the lease before it expires. Requires Vault >= 1.10 or OpenBao >= 2.0.
apiVersion: digitalis.io/v1beta1
kind: DbSecret
metadata:
name: cassandra
spec:
secretName: cassandra-credentials
renew: true # Renew the lease rather than issuing new credentials each cycle
vault:
mount: cass000 # Database secrets engine mount
role: readonly # Role to request credentials for
template: # Optional: rename the keys the backend returns
CASSANDRA_USERNAME: "{{ .username }}"
CASSANDRA_PASSWORD: "{{ .password }}"
rollout:
- kind: Deployment
name: cassandra-clientWith custom targets enabled, spec.target writes the value into any resource — here
only one field of a FlinkDeployment that ArgoCD otherwise manages.
apiVersion: digitalis.io/v1
kind: ValsSecret
metadata:
name: flink-datadog
spec:
data:
datadog_api_key:
ref: ref+awssecrets://prod/datadog#api_key
target:
apiVersion: flink.apache.org/v1beta1
kind: FlinkDeployment
name: my-pipeline
mode: patch
template: |
spec:
flinkConfiguration:
metrics.reporter.dghttp.apikey: {{ .datadog_api_key | quote }}More, including ConfigMap targets and rotating a database's password in the database itself, in Usage.
| Flag | Type | Default | Description |
|---|---|---|---|
-metrics-bind-address |
string | :8080 |
Address the metrics endpoint binds to. |
-health-probe-bind-address |
string | :8081 |
Address the health probe endpoint binds to. |
-reconcile-period |
duration | 5s |
How often the controller re-queues reconciliation events. |
-ttl |
duration | 5m0s |
How often each secret is checked against the backend store for updates. |
-watch-namespaces |
string | "" |
Comma-separated list of namespaces the operator watches. Empty means all namespaces. |
-exclude-namespaces |
string | "" |
Comma-separated list of namespaces the operator ignores entirely. |
-record-changes |
bool | true |
Records each secret update as a Kubernetes Event, visible via kubectl describe. Can be overridden per resource with the annotation vals-operator.digitalis.io/record: "true". |
-leader-elect |
bool | false |
Enables leader election, ensuring only one active controller instance when running multiple replicas. |
-disable-namespace-sync |
bool | false |
Blocks all cross-namespace ref+k8s:// references. See Security. |
-allowed-namespaces-for-sync |
string | "" |
Comma-separated allowlist of namespaces that may be referenced via ref+k8s://. See Security. |
-allowed-backend-paths |
string | "" |
Restricts which backend paths each namespace may read, covering both ValsSecret references (all backends) and DbSecret mounts/roles. Semicolon-separated namespace=prefix[,prefix...] entries; * applies to all namespaces. See Security. |
-enable-custom-targets |
bool | false |
Allow spec.target to write into ConfigMaps and CRDs instead of Secrets. Needs -allowed-target-resources. See Security. |
-allowed-target-resources |
string | "" |
Comma-separated resource.group list that spec.target may write, e.g. configmaps,flinkdeployments.flink.apache.org. |
Logging and kubeconfig flags, every Helm value and the supported annotations are in the configuration reference.
The operator authenticates to its backends once at startup, with one credential
shared by every namespace. That credential's policy is the real security boundary:
by default, anybody who can create a ValsSecret or DbSecret in a watched namespace
can read anything the operator can read.
On any cluster where namespaces are not all equally trusted, set allowedBackendPaths
and restrict cross-namespace ref+k8s:// references. The threat model and the full set
of controls are in Security.
Releases are signed with cosign keyless signing and ship SPDX and CycloneDX SBOMs — see Verifying signatures.
Report security issues privately to info@digitalis.io.
Full documentation is in docs/:
- Installation — Helm, OCI, CRDs, signature and SBOM verification.
- Secrets backends — Vault, OpenBao, AWS, GCP and everything else vals supports.
- Usage —
ValsSecret,DbSecret, templates, rollouts, password rotation. - Security — threat model and multi-tenancy controls.
- Configuration reference — every flag, value and annotation.
- Upgrade notes — read before upgrading an existing deployment.
- EKS integration — IRSA setup for reading AWS secrets.
Runnable manifests live in config/samples/.
Bug reports, feature requests and pull requests are welcome. See CONTRIBUTING.md for how to build and test, and CHANGELOG.md for the release history.
Licensed under the Apache License 2.0.
Maintained by Digitalis.io. For commercial support, consulting or managed services, get in touch at digitalis.io/contact.

