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

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.
Doppler
Doppler integrates with popular CI/CD tools
and frameworks, making it easy to automate secrets
management within your development workflow.
Fast guide over platform
-
Create a project
Adding new project is very simple, and easy.
Just click on+
button near to theProjects
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 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
-
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.