External Secret Operator with Doppler

Last time we configured our cluster step by step maybe without public code yet, but someday I will publish it. Probably when it will be smooth enough to share. Nevertheless, we have a working cluster. Today I will focus on connecting the External Secret Operator with Doppler. So let's introduce today's stars. External Secret Operator External Secrets Operator is a Kubernetes operator that Integrates external secret management systems like AWS Secrets Manager, HashiCorp Vault, Google Secrets Manager, Azure Key Vault, IBM Cloud Secrets Manager, CyberArk Conjur, Pulumi ESC and many more. The operator reads information from external APIs and automatically inject the values into a Kubernetes Secret. Link Doppler Doppler integrates with popular CI/CD tools and frameworks, making it easy to automate secrets management within your development workflow. Link Fast guide over platform Create a project Adding new project is very simple, and easy. Just click on + button near to the Projects text. Manage stages Here we can add, delete, or modify stages. At the beginning, I would like to recommend using only one stage. Adding secrets Then after clicking on the stage name, we can add next secret with two-click. Getting a token The last action to do is token generation. To archive it, just go to *Access tab, then generate a token, that will have access to particular config. How to use this duet First, we need to understand how it works. In my cluster, it looks as shown in the picture below: As you may see, we need to have one "static secret" in our cluster, we can make it from CLI directly or with the usage of sops, however, the effect will be the same, where we need to start. I decided that my workstation terminal session is a good place to create the main secret. When we have our token, we can call Doppler and retrieve needed secrets. In my case, I'm using one Doppler's project for everything and all namespaces have access to my token, but we can restrict it on the RBAC level if needed. Or not, that is why we're using ESO. Retrieving secrets In my opinion, we have at least 2 modes of getting credentials, that should meet all our needs. Getting all prefixed secrets. --- apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: hello namespace: hello labels: app.kubernetes.io/component: externalsecret app.kubernetes.io/part-of: hello app.kubernetes.io/managed-by: Kustomize spec: refreshInterval: 6h secretStoreRef: name: doppler-auth-argocd kind: ClusterSecretStore dataFrom: - find: path: HELLO_ With this code, we're fetching all secrets with prefix HELLO_, into a single secret with the name hello. And that's it. If you want to check the result of your integration just run: kubectl get secrets hello \ -o jsonpath="{.data.HELLO_SECRET}" \ -n hello | base64 --decode Getting particular secret and putting them into a specific order --- apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: hello namespace: hello labels: app.kubernetes.io/component: externalsecret app.kubernetes.io/part-of: hello app.kubernetes.io/managed-by: Kustomize spec: refreshInterval: 6h secretStoreRef: name: doppler-auth-argocd kind: ClusterSecretStore target: name: hello-secret creationPolicy: Owner template: data: APP_SECRET: "{{ .HELLO_SECRET }}" INIT_PASSWORD: "{{ .HELLO_INIT_PASSWORD }}" dataFrom: - find: path: HELLO_ This will create secret/hello-secret with a specific object structure. When it could be helpful? I will show you 3 interesting examples, that you could use in your setup. Adding Cloudflare tunnel config file Cloudflare tunnels require providing data in a specific format. It's a JSON string, looking similar to: { "AccountTag": "f3cc889ae810", "TunnelSecret": "SVD/O0sU+VtDlH023aBcToG0dMk=", "TunnelID": "8127899-c332-482c-91aa-d1210a479658" } And you need to use exactly this as a file-based secret. Then you have two option get your prefixed secrets into one object, then credentials.json into second, or use templates. --- apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: hello namespace: hello labels: app.kubernetes.io/component: externalsecret app.kubernetes.io/part-of: hello app.kubernetes.io/managed-by: Kustomize spec: refreshInterval: 6h secretStoreRef: name: doppler-auth-argocd kind: ClusterSecretStore target: name: hello-secret creationPolicy: Owner template: data: credentials.json: "{{ .HELLO_TUNNEL }}" APP_SECRET: "{{ .HELLO_APP_SECRET }}" DB_SECRET: "{{ .HELLO_DB_SECRET }}" DATABASE_URL: "{{ .HELLO_DB_URL }}" dataFrom: - find: path: HELLO_ --- Building ArgoCD repository secret. If you have worked with A

Apr 15, 2025 - 08:49
 0
External Secret Operator with Doppler

Last time we configured our cluster step by step maybe without public code yet, but someday I will publish it. Probably when it will be smooth enough to share. Nevertheless, we have a working cluster. Today I will focus on connecting the External Secret Operator with Doppler. So let's introduce today's stars.

External Secret Operator

External Secrets Operator is a Kubernetes operator that
Integrates external secret management systems like AWS Secrets Manager,
HashiCorp Vault, Google Secrets Manager, Azure Key Vault,
IBM Cloud Secrets Manager, CyberArk Conjur,
Pulumi ESC and many more. The operator reads information from
external APIs and automatically inject the values into a Kubernetes Secret.

Link

Doppler

Doppler integrates with popular CI/CD tools
and frameworks, making it easy to automate secrets
management within your development workflow.

Link

Fast guide over platform

  1. Create a project

    Adding new project is very simple, and easy.
    Just click on + button near to the Projects text.

    View of initial dashboard without projects

  2. Manage stages

    Here we can add, delete, or modify stages. At the beginning, I would like to recommend using only one stage.

    View on default stages' dashboard

  3. Adding secrets

    Then after clicking on the stage name, we can add next secret with two-click.

    Regular secret view

  4. Getting a token

    The last action to do is token generation. To archive it, just go to *Access tab, then generate a token, that will have access to particular config.

    View of access tab

How to use this duet

First, we need to understand how it works. In my cluster, it looks
as shown in the picture below:

Flow diagram of ESO integration with Doppler and cluster

As you may see, we need to have one "static secret" in our cluster, we can make it from CLI directly or with the usage of sops, however, the effect will be the same, where we need to start. I decided that my workstation terminal session is a good place to create the main secret.

When we have our token, we can call Doppler and retrieve needed secrets. In my case, I'm using one Doppler's project for everything and all namespaces have access to my token, but we can restrict it on the RBAC level if needed. Or not, that is why we're using ESO.

Retrieving secrets

In my opinion, we have at least 2 modes of getting credentials, that should meet all our needs.

  1. Getting all prefixed secrets.

    ---
    apiVersion: external-secrets.io/v1beta1
    kind: ExternalSecret
    metadata:
      name: hello
      namespace: hello
      labels:
        app.kubernetes.io/component: externalsecret
        app.kubernetes.io/part-of: hello
        app.kubernetes.io/managed-by: Kustomize
    spec:
      refreshInterval: 6h
      secretStoreRef:
        name: doppler-auth-argocd 
        kind: ClusterSecretStore
      dataFrom:
        - find:
            path: HELLO_
    

    With this code, we're fetching all secrets with prefix HELLO_, into a single secret with the name hello. And that's it. If you want to check the result of your integration just run:

    kubectl get secrets hello \
        -o jsonpath="{.data.HELLO_SECRET}" \ 
        -n hello | base64 --decode
    
  2. Getting particular secret and putting them into a specific order

    ---
    apiVersion: external-secrets.io/v1beta1
    kind: ExternalSecret
    metadata:
      name: hello
      namespace: hello
      labels:
        app.kubernetes.io/component: externalsecret
        app.kubernetes.io/part-of: hello
        app.kubernetes.io/managed-by: Kustomize
    spec:
      refreshInterval: 6h
      secretStoreRef:
        name: doppler-auth-argocd 
        kind: ClusterSecretStore
      target:
        name: hello-secret
        creationPolicy: Owner
        template:
          data:
            APP_SECRET: "{{ .HELLO_SECRET }}"
            INIT_PASSWORD: "{{ .HELLO_INIT_PASSWORD }}"
      dataFrom:
        - find:
            path: HELLO_
    

    This will create secret/hello-secret with a specific object structure. When it could be helpful? I will show you 3 interesting examples, that you could use in your setup.

    1. Adding Cloudflare tunnel config file

      Cloudflare tunnels require providing data in a specific format.
      It's a JSON string, looking similar to:

      {
        "AccountTag": "f3cc889ae810",
        "TunnelSecret": "SVD/O0sU+VtDlH023aBcToG0dMk=",
        "TunnelID": "8127899-c332-482c-91aa-d1210a479658"
      }
      

      And you need to use exactly this as a file-based secret. Then you have two option get your prefixed secrets into one object, then credentials.json into second, or use templates.

      ---
      apiVersion: external-secrets.io/v1beta1
      kind: ExternalSecret
      metadata:
        name: hello
        namespace: hello
        labels:
          app.kubernetes.io/component: externalsecret
          app.kubernetes.io/part-of: hello
          app.kubernetes.io/managed-by: Kustomize
      spec:
        refreshInterval: 6h
        secretStoreRef:
          name: doppler-auth-argocd 
          kind: ClusterSecretStore
        target:
          name: hello-secret
          creationPolicy: Owner
          template:
            data:
              credentials.json: "{{ .HELLO_TUNNEL }}"
              APP_SECRET: "{{ .HELLO_APP_SECRET }}"
              DB_SECRET: "{{ .HELLO_DB_SECRET }}"
              DATABASE_URL: "{{ .HELLO_DB_URL }}"
        dataFrom:
          - find:
              path: HELLO_
      ---
      
    2. Building ArgoCD repository secret.

      If you have worked with ArgoCD in the past, there is a chance, that you know that problem is not trivial. You need to use build object with type secret, with repository URL, user, and secret. As you can't have a nested secret (secret ref in another secret), you are forced to keep the whole file secret. Fortunately, you can use ESO templates.

      ---
      apiVersion: external-secrets.io/v1beta1
      kind: ExternalSecret
      metadata:
        name: gitops-with-argo-secret
        namespace: argocd
      spec:
        refreshInterval: 6h
        secretStoreRef:
          name: doppler-auth-argocd 
          kind: ClusterSecretStore
        target:
          name: gitops-with-argo
          creationPolicy: Owner
          template:
            type: Opaque
            metadata:
              labels:
                argocd.argoproj.io/secret-type: repository
            data:
              type: git
              url: https://github.com/3sky/argocd-for-home
              username: 3sky
              password: "{{ .password }}"
        data:
          - secretKey: password
            remoteRef:
              key: GITHUB_TOKEN
      
      
    3. At the end, simple, direct Tailscale mapping

      ---
      apiVersion: external-secrets.io/v1beta1
      kind: ExternalSecret
      metadata:
        name: operator-oauth-secret
        namespace: tailscale
      spec:
        refreshInterval: 6h
        secretStoreRef:
          name: doppler-auth-argocd 
          kind: ClusterSecretStore
        target:
          name: operator-oauth
          creationPolicy: Owner
          template:
            data:
              client_id: "{{ .client_id }}"
              client_secret: "{{ .client_secret}}"
        data:
          - secretKey: client_id
            remoteRef:
              key: TAILSCALE_CLIENT_ID
          - secretKey: client_secret
            remoteRef:
              key: TAILSCALE_CLIENT_SECRET
      

Summary

Based on my testing and usage of this integration, so far so good. Stable, not very resource-consuming, we can limit sync intervals. Doppler as mentioned at the beginning is free for small projects, or individual users. However, using ESO with popular enterprise solutions like Hashicorp Vault should be very similar. Maybe I will check it someday.

I hope you like this kind of article, smaller, focused on a particular problem or integration. If not, let me know.