Installing it

The operator is installed once per cluster. Each environment you then want projected is a small resource you apply — see Projecting an environment.

Before you start

You need a cluster you can install into, Helm, and a kyvlt.sh service token for the environment you want. Mint the token from Integrations → Kubernetes, which also generates everything below filled in for you.

  1. Install the operator

    helm install kyvlt-connector oci://ghcr.io/bluepawlabs/charts/kyvlt-connector \
      --version 0.10.0 \
      --namespace kyvlt-operator --create-namespace \
      --set servedNamespaces='{default}' \
      --set reload.enabled=true

    The operator runs in kyvlt-operator and writes each Secret beside the resource that asked for it — in the namespaces named under servedNamespaces, and only those. The command above names the one your resources will live in; see Where it may write for how the grant works.

    ValueDefault
    servedNamespaces[] (the operator's own namespace)The namespaces the operator serves — one Role each, never cluster-wide. Below.
    kyvlt.apiBaseUrlChecked for a supported contract at startup. Each resource still names its own.
    reload.enabledfalseGrants patch on Deployments in the served namespaces. Restarting workloads.
    registry.existingSecretA pull secret you already own, for a private or mirrored registry.
    image.tagthe chart's appVersionPinned by the chart. There is no latest.
    resources50m / 96Mi, 256Mi limit
  2. Give the operator its token

    kubectl create secret generic kyvlt-token \
      --from-literal=token=PASTE_TOKEN_HERE

    Minting from Integrations → Kubernetes hands you this command with the value already in it.

    Minted for thirty days. The operator renews it from fourteen days out and writes the successor into the same Secret, so nothing here is a calendar entry. See Service tokens.

  3. Check the operator is running

    kubectl -n kyvlt-operator get pods

    A running pod means the operator started and understood the kyvlt.sh API it was pointed at. If it refuses to start it names the API version it expected and the one it found, rather than syncing against an API it does not understand.

Letting kyvlt.sh wire projections

With the cluster registered and --set managed.enabled=true (plus kyvlt.apiBaseUrl), projections are assigned in the interface instead of applied by hand — the operator materialises and removes them itself, inside the served namespaces and never beyond. Everything below still applies: the fence, the token path, and hand-authored resources all keep working unchanged.

Where it may write

servedNamespaces is the complete answer. Each entry grants the operator one Role in exactly that namespace; there is no ClusterRole over secrets in any configuration, and a projection always lands in its resource's own namespace — target.namespace is refused at apply time. So "who may put a secret here" is who may create a KyvltSync here, which is your cluster's own RBAC and nobody else's grant.

Empty serves the operator's own namespace alone — the smallest install, and almost never what you want. Name every namespace that will hold a KyvltSync:

helm install ... --set servedNamespaces='{checkout,billing}'
What happens
A KyvltSync in a served namespaceProjects, beside itself
A KyvltSync anywhere elseRefused, NamespaceNotServed, naming the exact helm upgrade remedy
Adding a namespacehelm upgrade by whoever owns the release — a deliberate act, because it is a grant
A named namespace that does not existThe install fails with the API server's own “namespaces not found”

Why not cluster-wide, when Doppler and External Secrets are. Versions 0.8–0.9 shipped that shape and 0.10.0 reversed it: the operator is a workload too, and one holding cluster-wide Secret write is the single credential that defeats every namespace boundary in the cluster if it is ever compromised. The helm upgrade per new namespace is the cost, and it is paid by the person the boundary protects.

A mirror, or an air-gapped registry

The image and the chart are public, so an ordinary install needs no credential. If you pull from somewhere that does, create the pull secret yourself and name it:

kubectl -n kyvlt-operator create secret docker-registry ghcr-pull \
  --docker-server=ghcr.io \
  --docker-username=YOUR_GITHUB_USERNAME \
  --docker-password=YOUR_READ_PACKAGES_TOKEN
helm install ... --set registry.existingSecret=ghcr-pull

The chart accepts no registry credentials and builds no pull secret. One it built for you would be a second credential living in your cluster — stored, rotated and eventually leaked — and naming one you already own keeps it under whatever rotation you already apply.

Verifying what you installed

Every image and chart is signed, and the signature needs no key from us:

cosign verify ghcr.io/bluepawlabs/kyvlt-connector:0.9.0 \
  --certificate-identity-regexp '^https://github.com/bluepawlabs/kyvlt.sh/' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

The same command works for ghcr.io/bluepawlabs/charts/kyvlt-connector.

There is no latest tag. A tag that moves under a running cluster is a version nobody can name during an incident, so every install names one.