# Vault Secrets Operator: Vault in the cluster, GitOps in the repository

> The Vault Secrets Operator is a Kubernetes operator that materialises Vault secrets into Kubernetes secret objects, so a custom resource in Git can stand in for the real value without the value ever being committed. It supports seven Vault auth methods and both KV engine versions, and it is positioned as a replacement for Sealed Secrets rather than as a general secrets synchroniser.

**ricoberger/vault-secrets-operator** — Create Kubernetes secrets from Vault for a secure GitOps based workflow.

- Repository: https://github.com/ricoberger/vault-secrets-operator
- Stars: 687 · Forks: 109
- Language: Go
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/ricoberger-vault-secrets-operator

## The GitOps problem this solves, and where it stops

The Vault Secrets Operator creates Kubernetes secrets from Vault. The stated idea is to manage secrets in a Kubernetes cluster using a secure GitOps based workflow, and the mechanism is a custom resource: you commit a reference to your secrets to your git repository, and when you apply that resource to your cluster the operator looks up the real secret in Vault and creates the corresponding Kubernetes secret.

So the repository holds a pointer, not a value, and the cluster holds the value, refreshed from Vault. The value is never in git and never in the custom resource, which is the whole point.

The README positions the tool against a specific alternative: if you are using something like Sealed Secrets for this workflow, the Vault Secrets Operator can be used as a replacement for it. That comparison is the honest frame for the project, and it is narrower than a general secrets tool. It is not a synchroniser that reconciles several secret backends, and it is not a secret store. It is a bridge from one specific system, Vault, into one specific system, Kubernetes.

The repository layout is what you would expect from an operator built with the operator SDK: a PROJECT file, an api/ directory for the custom resource types, cmd/ and internal/ for the implementation, a charts/ directory holding the Helm chart, a Dockerfile, a Makefile, and a test/ directory. The project is MIT licensed, the default branch is main, and the recent releases are v1.28.0 from 2026-08-13, v1.27.3 from 2026-06-21 and v1.27.2 from 2026-02-18, with the last push to the repository on 2026-10-01.

## Installing the chart and pointing it at your Vault

Installation is via Helm, and a list of all configurable values is provided in the chart's values.yaml. The install command points at an OCI registry rather than a chart repository:

```sh
helm upgrade --install vault-secrets-operator oci://ghcr.io/ricoberger/charts/vault-secrets-operator --version <VERSION>
```

One default is worth knowing before you install. The chart assumes a Vault server running at http://vault:8200, and that assumption can be overridden by specifying --set vault.address=https://vault.example.com.

The default is a cluster-local DNS name over plain HTTP, which is what you would use when Vault runs inside the same cluster with a service named vault. If Vault lives elsewhere, or if it is exposed over TLS, that single value is what you change. It is also the kind of default that silently produces a broken operator if you forget it, because the chart will come up and then fail to reach Vault.

The version placeholder in the command is there because the chart is versioned independently of the operator, and pinning it is what makes an upgrade reversible. A Helm install that does not pin a version takes whatever the registry currently serves.

## Preparing the KV engines and a read-only policy

The operator supports the KV Secrets Engine version 1 and version 2, and the README walks through enabling one of each under a path:

```sh
vault secrets enable -path=kvv1 -version=1 kv
vault secrets enable -path=kvv2 -version=2 kv
```

Then a policy is created for the operator, and the design point here is that the operator only needs read access to the paths you want to use for your secrets. The policy is written with a heredoc so the paths are visible in the command itself:

```sh
cat <<EOF | vault policy write vault-secrets-operator -
path "kvv1/*" {
  capabilities = ["read"]
}

path "kvv2/data/*" {
  capabilities = ["read"]
}
EOF
```

The asymmetry in the two path patterns is the whole difference between KV v1 and KV v2. In version 1 the secret data sits directly at the path, so the pattern is kvv1/*. In version 2 the API stores data under a data subpath, so the read pattern is kvv2/data/*. Getting this wrong is the most common first-run failure with this operator, and the failure looks like a permission error rather than a path error.

The principle of least privilege is enforced here by the operator's own design, since it only ever reads. It is worth keeping the policy as narrow as the paths you actually use, because the policy name and the paths are the only place where the operator's access to your secrets is written down.

## Seven ways to authenticate, and why Kubernetes auth is recommended

The operator can authenticate to Vault in seven ways: Token Auth, Kubernetes Auth, AppRole Auth, Username and Password Auth, AWS Auth, Azure Auth and GCP Auth. The README then gives per-method setup instructions, and the presence of the AWS, Azure and GCP options is the reason the Go dependency list includes the AWS SDK v2, the Azure MSI library, the Google API client and the cloud metadata packages.

The recommended method is the Kubernetes auth method, and the reasoning is worth following. It requires a service account for communication between Vault and the operator, and if you installed the operator via Helm that service account is created for you, named vault-secrets-operator.

Configuring it means telling Vault how to reach the cluster and how to map a service account to a policy. The environment variables are gathered from the cluster first:

```sh
export VAULT_SECRETS_OPERATOR_NAMESPACE=$(kubectl get sa vault-secrets-operator -o jsonpath="{.metadata.namespace}")
export VAULT_SECRET_NAME=$(kubectl get secret vault-secrets-operator -o jsonpath="{.metadata.name}")
export SA_JWT_TOKEN=$(kubectl get secret $VAULT_SECRET_NAME -o jsonpath="{.data.token}" | base64 --decode; echo)
export SA_CA_CRT=$(kubectl get secret $VAULT_SECRET_NAME -o jsonpath="{.data['ca\.crt']}" | base64 --decode; echo)
export K8S_HOST=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
```

Then Vault is configured with the issuer, the token reviewer JWT, the API server address and the CA certificate, and a role is created that binds the service account name and namespace to the policy with a 24 hour TTL:

```sh
vault auth enable kubernetes

vault write auth/kubernetes/config \
  issuer="https://kubernetes.default.svc.cluster.local" \
  token_reviewer_jwt="$SA_JWT_TOKEN" \
  kubernetes_host="$K8S_HOST" \
  kubernetes_ca_cert="$SA_CA_CRT"

vault write auth/kubernetes/role/vault-secrets-operator \
  bound_service_account_names="vault-secrets-operator" \
  bound_service_account_namespaces="$VAULT_SECRETS_OPERATOR_NAMESPACE" \
  policies=vault-secrets-operator \
  ttl=24h
```

The README also notes that in modern versions of Vault the token, host and CA cert are fetched automatically, and that if you run Vault inside Kubernetes you can exec into a Vault pod and configure it there instead. The token reviewer JWT is the part people get wrong: Vault needs a credential it can use to ask the Kubernetes API to validate a token, and the service account's own token is what the setup pulls.

## Token auth means a secret inside the cluster holding a token

The token auth method is the simplest and the one that shows the trade-off most clearly. A token is created with the policy from the previous section and a 24 hour period:

```sh
vault token create -period=24h -policy=vault-secrets-operator
```

That token has to reach the operator, and for security reasons the operator only supports passing environment variables via a Kubernetes secret. The secret is expected to carry the keys VAULT_TOKEN and VAULT_TOKEN_LEASE_DURATION, with the optional VAULT_TOKEN_RENEWAL_INTERVAL and VAULT_TOKEN_RENEWAL_RETRY_INTERVAL to control the timings for token renewals, and VAULT_RENEW_TOKEN which can be set to "false" to disable the operator's token renew loop.

The secret is created with kubectl apply from a heredoc, with the values base64 encoded on the way in:

```sh
export VAULT_TOKEN=
export VAULT_TOKEN_LEASE_DURATION=86400

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: vault-secrets-operator
type: Opaque
data:
  VAULT_TOKEN: $(echo -n "$VAULT_TOKEN" | base64)
  VAULT_TOKEN_LEASE_DURATION: $(echo -n "$VAULT_TOKEN_LEASE_DURATION" | base64)
EOF
```

The chart then has to be told to read it, through the environmentVars list, with each variable sourced from a secret key reference:

```yaml
environmentVars:
  - name: VAULT_TOKEN
    valueFrom:
      secretKeyRef:
        name: vault-secrets-operator
        key: VAULT_TOKEN
  - name: VAULT_TOKEN_LEASE_DURATION
    valueFrom:
      secretKeyRef:
        name: vault-secrets-operator
        key: VAULT_TOKEN_LEASE_DURATION
```

This is the cost of token auth. You have replaced a secret that would have been committed to git with a long-lived credential that lives in the cluster instead, and the whole point of the operator was to keep credentials out of the cluster's declarative state. The Kubernetes auth method exists precisely to avoid this, since the operator can then obtain a short-lived token for itself.

## Where this operator is the wrong tool

The first limitation is the dependency on Vault. If you do not already run Vault, this operator solves nothing, and the effort of standing up a secrets service, its policies, its auth method and its storage backend is the real project. The README is a Vault setup guide as much as it is an operator guide.

The second limitation is scope. There is one backend and one target. If your secrets live across a cloud secret manager, an external key manager, or a file encrypted on disk, this operator does not help. The README positions it as a Sealed Secrets replacement, and that is the honest size of the ambition: one mechanism for getting a value from Vault into a Kubernetes secret object.

The third limitation is that the operator does not remove the need for a secret in the cluster. It moves the secret from your git repository to the cluster, and it derives the Kubernetes secret from Vault rather than from the custom resource, which is an improvement. But a running application still reads its credentials from a Kubernetes secret object, so the exposure surface of a compromised cluster is unchanged. The operator improves how the secret gets there, not what is there afterwards.

The real alternative is Sealed Secrets, and the difference in approach is the point. Sealed Secrets encrypts the value with a key held only in the cluster, so the encrypted blob is safe to commit to git and the controller decrypts it in place. There is no Vault, no policies, no auth method, and no network dependency at decryption time. The cost is that you need a second key pair to manage and rotate, and the ciphertext is only decryptable in that cluster. Vault Secrets Operator takes the opposite trade: no extra key pair, values rotate centrally in Vault, and the operator needs working credentials and a reachable Vault to produce anything.

For a team that already has Vault, or that wants one place to rotate credentials across many systems, the operator is the better fit. For a team that just wants to stop committing secrets to git, Sealed Secrets is fewer moving parts, and this operator is the wrong answer for that job.

## Conclusion

Adopt the Vault Secrets Operator if you already run Vault and want Kubernetes secrets to be derived from it under a GitOps workflow, and if you are willing to give the operator read access to specific Vault paths. Do not adopt it if you have no Vault, if you want a general purpose secret synchroniser across many backends, or if you would rather have one controller sealing values with its own key than a component talking to a secrets service. Before you install, decide which auth method you will use, since the Kubernetes method is the recommended one and needs a service account, a Vault role and a reviewer JWT, and check that the Vault address the chart assumes matches your deployment.

## FAQ

### What does the Vault Secrets Operator do?

It creates Kubernetes secrets from Vault so that you can manage secrets in a cluster through a GitOps workflow. You commit a custom resource that references the secret, and when you apply it to the cluster the operator looks up the real value in Vault and creates the corresponding Kubernetes secret.

### How do I install the Vault Secrets Operator?

With Helm, using the OCI chart: helm upgrade --install vault-secrets-operator oci://ghcr.io/ricoberger/charts/vault-secrets-operator --version <VERSION>. The chart assumes a Vault server at http://vault:8200, which you can override with --set vault.address=https://vault.example.com.

### Which Vault auth methods does the operator support?

Seven: Token, Kubernetes, AppRole, Username and Password, AWS, Azure and GCP. The README recommends the Kubernetes auth method, which needs a service account named vault-secrets-operator, created for you by the Helm chart, plus a Vault role binding that service account to a policy with a 24 hour TTL.

### How do I set the Vault policy for the operator?

The operator only needs read access to the paths you use. For KV version 1 the path pattern is kvv1/*, while for version 2 the data lives under a data subpath so the pattern is kvv2/data/*. The policy is written with a heredoc through vault policy write, with capabilities set to read.

### Is the Vault Secrets Operator still maintained?

The repository is not archived and the last push was on 2026-10-01. Recent releases are v1.28.0 from 2026-08-13, v1.27.3 from 2026-06-21 and v1.27.2 from 2026-02-18, and the project is MIT licensed with the chart published as an OCI artifact to ghcr.io.

## Sources

- [Issues](https://github.com/ricoberger/vault-secrets-operator/issues)
- [License: MIT](https://github.com/ricoberger/vault-secrets-operator/blob/main/LICENSE)
- [README](https://github.com/ricoberger/vault-secrets-operator/blob/main/README.md)
- [Releases](https://github.com/ricoberger/vault-secrets-operator/releases)
- [ricoberger/vault-secrets-operator on GitHub](https://github.com/ricoberger/vault-secrets-operator)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ricoberger-vault-secrets-operator
