Managing Wiz Secrets with Doppler and ESO
Managing Wiz Kubernetes Secrets with Doppler and External Secrets Operator
Deploying security tooling across multiple Kubernetes clusters is the easy part.
Managing secrets correctly across those clusters is where things get interesting.
When I rolled out the Wiz Kubernetes integration in my lab, I wanted it to reflect how I would approach this in a production environment. That meant I refused to:
- Hardcode credentials in Helm values
- Commit plaintext secrets to Git
- Manually recreate secrets cluster by cluster
Instead, I built a repeatable GitOps pattern using Doppler, External Secrets Operator (ESO), and ArgoCD. I really like using Doppler since they are built for teams to collaborate and really provide a GitOps framework. They also offer a full featured free tier for a dev environment
The Problem
The Wiz Kubernetes integration requires two distinct secret types.
1. Wiz Credentials
clientId and clientToken used by the connector and broker to authenticate with the Wiz platform.
2. Image Pull Credentials
A dockerconfigjson secret so the sensor can pull images from Wiz's private registry.
These secrets must:
- Exist before the Helm chart deploys
- Be present on every cluster
- Stay synchronized
- Avoid manual drift
That combination is where most "quick setups" start to break down.
If secret handling is not intentional from day one, it becomes fragile very quickly.
The Architecture
I approached this with GitOps as the control plane. The goal was simple: after initial bootstrap, no human should ever touch a secret again. The only manual step is a one-time bootstrap per cluster.
Doppler as the Source of Truth
Doppler acts as the single source of truth for all secret material. I split secrets into separate projects:
wiz-tokenfor Wiz credentialssensor-imagefor registry credentials
Each concern gets its own boundary. This keeps blast radius small and permissions tight.
Cluster Bootstrap
On each cluster:
- A bootstrap secret is created manually once. This is the Doppler service token that ESO uses to authenticate. It is the only secret that lives outside of Git.
From there:
- A
SecretStoreconnects ESO to the appropriate Doppler project. ExternalSecretresources map Doppler keys into the exact Kubernetes Secret format the Helm chart expects.- ArgoCD ApplicationSets deploy everything across clusters with per-cluster overlays.
No kubectl loops. No manual patches. No hidden credentials floating around.
Example: SecretStore
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: doppler-wiz-token
namespace: wiz-system
spec:
provider:
doppler:
auth:
secretRef:
dopplerToken:
name: doppler-bootstrap
key: dopplerToken
project: wiz-token
config: prdExample: ExternalSecret for Wiz Credentials
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: wiz-credentials
namespace: wiz-system
spec:
refreshInterval: 1h
secretStoreRef:
name: doppler-wiz-token
kind: SecretStore
target:
name: wiz-credentials
creationPolicy: Owner
data:
- secretKey: clientId
remoteRef:
key: WIZ_CLIENT_ID
- secretKey: clientToken
remoteRef:
key: WIZ_CLIENT_TOKENExample: ExternalSecret for Sensor Image Pull Secret
The sensor requires a Kubernetes secret of type:
type: kubernetes.io/dockerconfigjsonESO handles this cleanly through templating:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: wiz-sensor-registry
namespace: wiz-system
spec:
refreshInterval: 1h
secretStoreRef:
name: doppler-sensor-image
kind: SecretStore
target:
name: wiz-sensor-registry
creationPolicy: Owner
template:
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: "{{ .dockerconfigjson }}"
data:
- secretKey: dockerconfigjson
remoteRef:
key: SENSOR_IMAGENo manual base64 encoding. No copy-paste gymnastics. No guessing at secret formats.
Key Design Decisions
Separate Doppler Projects Per Secret Domain
Wiz credentials and sensor registry credentials are isolated. Each project has its own service token and SecretStore. This enforces least privilege structurally, not just conceptually.
GitOps as the Control Plane
ArgoCD ApplicationSets deploy everything across clusters.
Adding a new cluster requires:
- Creating one bootstrap secret
- Adding the cluster to the ApplicationSet generator
That's it.
No rework. No secret recreation.
Kustomize Overlays for Cluster Behavior
Production runs the full stack, including the sensor. My dev cluster does not. A single overlay value controls that behavior. This keeps environments intentional instead of accidental.
The Result
Secrets refresh automatically every hour.
No credentials live in Git.
No manual secret recreation beyond the initial bootstrap.
Full visibility into sync status through ESO and ArgoCD.
The entire Wiz stack - connector, broker, admission controller, and sensor - deploys consistently across clusters with zero manual secret handling after the first setup.
Closing Thoughts
This is what GitOps should feel like.
Predictable. Auditable. Boring in the best possible way.
If your secret management process feels fragile, it probably is. Design the boundaries intentionally.