Skip to content

How to Use Reloader with the Doppler Kubernetes Operator#

This guide shows how to combine the Doppler Kubernetes operator with Reloader to automatically restart pods when secrets change in Doppler.

The Doppler operator syncs secrets from Doppler into Kubernetes Secret objects. Reloader watches those Secrets and triggers rolling restarts when their data changes. This pattern supports all Reloader-compatible workload types — including StatefulSets, Daemonsets, and Argo Rollouts — which the Doppler operator's built-in reload does not cover.

The Doppler documentation recommends Stakater Reloader for workload types beyond Deployments.


How it works#

sequenceDiagram
    actor Dev as Developer
    participant DOP as Doppler
    participant OP as Doppler Operator
    participant K8s as Kubernetes Secret
    participant RL as Reloader
    participant Pod as Application Pod

    Dev->>DOP: Update secret value
    loop Every resyncSeconds (default: 60s)
        OP->>DOP: Fetch latest secrets
        DOP-->>OP: Updated secret value
        OP->>K8s: Update managed Kubernetes Secret
    end
    K8s-->>RL: Watch event (Secret changed)
    RL->>Pod: Trigger rolling restart
    Note over Pod: New pod starts with updated secret

Prerequisites#

  • Kubernetes cluster (v1.19+)
  • Helm v3+
  • A Doppler account with a project and config (environment) set up
  • A Doppler service token for the target config
  • Stakater Reloader installed

Step 1 — Install the Doppler Kubernetes operator#

helm repo add doppler https://helm.doppler.com
helm repo update
helm install --generate-name doppler/doppler-kubernetes-operator

The operator installs into the doppler-operator-system namespace by default.

Verify it is running:

kubectl get pods -n doppler-operator-system

Step 2 — Install Reloader#

helm repo add stakater https://stakater.github.io/stakater-charts
helm repo update
helm install reloader stakater/reloader \
  --namespace reloader \
  --create-namespace

Step 3 — Create the Doppler service token secret#

Create a Kubernetes Secret containing the Doppler service token. The operator reads this to authenticate with the Doppler API.

kubectl create secret generic doppler-token-secret \
  --namespace doppler-operator-system \
  --from-literal=serviceToken=dp.st.prd.XXXX

The service token must have at least read access to the target project and config. Create a dedicated service token in the Doppler dashboard under AccessService Tokens.


Step 4 — Create the DopplerSecret resource#

The DopplerSecret CRD tells the operator which Doppler project and config to sync, and which Kubernetes Secret to write the result into.

apiVersion: secrets.doppler.com/v1alpha1
kind: DopplerSecret
metadata:
  name: app-doppler-secret
  namespace: doppler-operator-system
spec:
  tokenSecret:
    name: doppler-token-secret
  project: my-project
  config: prd
  managedSecret:
    name: app-secrets
    namespace: default

Apply:

kubectl apply -f doppler-secret.yaml

Key fields:

Field Description
spec.tokenSecret.name Name of the Kubernetes Secret holding the Doppler service token
spec.project Doppler project name
spec.config Doppler config (environment) name — for example, prd, stg, dev
spec.managedSecret.name Name of the Kubernetes Secret the operator creates and keeps in sync
spec.managedSecret.namespace Namespace where the managed Secret is created
spec.resyncSeconds How often the operator polls Doppler (default: 60)

The operator creates the Kubernetes Secret app-secrets in the default namespace and syncs all secrets from the Doppler config into it.

Verify the Secret was created:

kubectl get secret app-secrets -n default
kubectl describe dopplersecret app-doppler-secret -n doppler-operator-system

Step 5 — Annotate workloads for Reloader#

Add the Reloader annotation to each workload that should restart when the Secret changes. Do not use the Doppler operator's built-in secrets.doppler.com/reload: "true" annotation on the same workload — both mechanisms would fire on the same Secret update, causing two successive restarts.

Watch the specific Secret by name (recommended):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: default
  annotations:
    secret.reloader.stakater.com/reload: "app-secrets"
spec:
  replicas: 2
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: app
          image: myapp:latest
          env:
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: app-secrets
                  key: DB_PASSWORD
            - name: API_KEY
              valueFrom:
                secretKeyRef:
                  name: app-secrets
                  key: API_KEY

Apply:

kubectl apply -f deployment.yaml

StatefulSets#

The Doppler operator's built-in reload does not support StatefulSets. Reloader does.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: myapp
  namespace: default
  annotations:
    secret.reloader.stakater.com/reload: "app-secrets"
spec:
  serviceName: myapp
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: app
          image: myapp:latest
          env:
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: app-secrets
                  key: DB_PASSWORD

Argo Rollouts#

The Doppler operator's built-in reload does not support Argo Rollouts. Reloader does, with one extra Helm value.

Enable Argo Rollouts support in Reloader:

reloader:
  isArgoRollouts: true

Then annotate the Rollout:

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: myapp
  namespace: default
  annotations:
    secret.reloader.stakater.com/reload: "app-secrets"
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: app
          image: myapp:latest
          env:
            - name: API_KEY
              valueFrom:
                secretKeyRef:
                  name: app-secrets
                  key: API_KEY

Step 6 — Test secret rotation#

Update a secret value in the Doppler dashboard, then wait for the next sync interval (default: 60 seconds).

Confirm the Kubernetes Secret was updated:

kubectl get secret app-secrets -n default -o jsonpath='{.data.DB_PASSWORD}' | base64 -d

Confirm pods were restarted:

kubectl rollout status deployment/myapp -n default
kubectl get pods -n default -l app=myapp

Check Reloader logs to confirm the reload was triggered:

kubectl logs -n reloader -l app=reloader-reloader --tail=20

Expected output:

Changes detected in 'app-secrets' of type 'Secret' in namespace 'default'
Updated 'myapp' of type 'Deployment' in namespace 'default'

Reloader annotations reference#

Annotation Placed on Effect
secret.reloader.stakater.com/reload: "app-secrets" Any workload Restart when the named Secret changes
reloader.stakater.com/auto: "true" Any workload Restart when any Secret or ConfigMap referenced in the pod spec changes
reloader.stakater.com/search: "true" Workload Restart when any Secret annotated with reloader.stakater.com/match: "true" changes

Faster sync with resyncSeconds#

To reduce the delay between a Doppler change and a pod restart, lower resyncSeconds:

spec:
  resyncSeconds: 15

The Doppler documentation recommends the default of 60 seconds for most use cases. Lower values increase API request volume against the Doppler API.


Cooldown between reloads#

To prevent rapid successive restarts when multiple secrets are updated at once:

metadata:
  annotations:
    secret.reloader.stakater.com/reload: "app-secrets"
    deployment.reloader.stakater.com/pause-period: "2m"

During the pause period, additional Secret changes do not trigger another restart.