PushSecret CRD
PushSecret is the reverse of ExternalSecret — it pushes a Kubernetes Secret to an external provider. Use cases include backing up cluster-generated secrets, syncing secrets across clouds, and centralizing secrets created by operators.
# PushSecret: Push a K8s Secret to AWS Secrets Manager
apiVersion: external-secrets.io/v1alpha1
kind: PushSecret
metadata:
name: push-db-creds-to-aws
namespace: production
spec:
# Refresh interval for re-pushing (catches local updates)
refreshInterval: 1h
# Reference to the SecretStore with write permissions
secretStoreRefs:
- name: aws-secretsmanager
kind: SecretStore
# The Kubernetes Secret to push
selector:
secret:
name: generated-db-password
# Mapping: which keys to push and where
data:
- match:
secretKey: password # Key in K8s Secret
remoteRef:
remoteKey: production/db-backup # Path in AWS SM
property: password # JSON property to set
# PushSecret with multiple keys and deletion policy
apiVersion: external-secrets.io/v1alpha1
kind: PushSecret
metadata:
name: push-all-app-secrets
namespace: production
spec:
refreshInterval: 30m
deletionPolicy: Delete # Delete remote secret when PushSecret is deleted
secretStoreRefs:
- name: vault-backend
kind: SecretStore
selector:
secret:
name: app-config
data:
- match:
secretKey: api-key
remoteRef:
remoteKey: production/app/api-key
- match:
secretKey: db-url
remoteRef:
remoteKey: production/app/database-url
- match:
secretKey: redis-url
remoteRef:
remoteKey: production/app/redis-url
Generators
Generators create dynamic secret values without an external provider. ESO generates the value and stores it in a Kubernetes Secret. Useful for bootstrap passwords, correlation IDs, and short-lived tokens.
Password Generator
# Generate a random password and store it as a K8s Secret
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
metadata:
name: db-password-gen
namespace: production
spec:
length: 32
digits: 6
symbols: 4
symbolCharacters: "!@#$%^&*"
noUpper: false
allowRepeat: true
---
# ExternalSecret that uses the generator
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: generated-db-password
namespace: production
spec:
refreshInterval: 0 # Never refresh (generate once)
target:
name: db-password
creationPolicy: Owner
dataFrom:
- sourceRef:
generatorRef:
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
name: db-password-gen
UUID Generator
# Generate a UUID v4 for service identification
apiVersion: generators.external-secrets.io/v1alpha1
kind: UUID
metadata:
name: service-id-gen
namespace: production
spec: {}
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: service-identity
namespace: production
spec:
refreshInterval: 0
target:
name: service-id
creationPolicy: Owner
dataFrom:
- sourceRef:
generatorRef:
apiVersion: generators.external-secrets.io/v1alpha1
kind: UUID
name: service-id-gen
ECR Token Generator
# Generate ECR pull credentials (auto-refreshes before expiry)
apiVersion: generators.external-secrets.io/v1alpha1
kind: ECRAuthorizationToken
metadata:
name: ecr-token
namespace: production
spec:
region: us-east-1
auth:
secretRef:
accessKeyIDSecretRef:
name: aws-credentials
key: access-key-id
secretAccessKeySecretRef:
name: aws-credentials
key: secret-access-key
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: ecr-pull-secret
namespace: production
spec:
refreshInterval: 6h # ECR tokens expire in 12h, refresh at 6h
target:
name: ecr-registry-credentials
creationPolicy: Owner
template:
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: |
{"auths":{"{{ .registry }}":{"username":"AWS","password":"{{ .token }}"}}}}
dataFrom:
- sourceRef:
generatorRef:
apiVersion: generators.external-secrets.io/v1alpha1
kind: ECRAuthorizationToken
name: ecr-token
refreshInterval: 0 for one-time generation (passwords, UUIDs). Use a non-zero interval for tokens that expire (ECR auth tokens, OAuth tokens). The generated value is stored in the K8s Secret and persists across pod restarts.
ClusterExternalSecret
ClusterExternalSecret syncs the same external secret into multiple namespaces simultaneously. Useful for shared configuration (TLS certs, registry credentials, common API keys) that every namespace needs.
# Sync a shared secret to all namespaces with label env=production
apiVersion: external-secrets.io/v1beta1
kind: ClusterExternalSecret
metadata:
name: shared-registry-creds
spec:
# Target namespaces by label selector
namespaceSelector:
matchLabels:
env: production
# The ExternalSecret template to create in each namespace
externalSecretSpec:
refreshInterval: 6h
secretStoreRef:
name: central-vault
kind: ClusterSecretStore
target:
name: registry-credentials
creationPolicy: Owner
template:
type: kubernetes.io/dockerconfigjson
data:
- secretKey: .dockerconfigjson
remoteRef:
key: shared/registry-auth
property: dockerconfig
# Verify ClusterExternalSecret created secrets in multiple namespaces
kubectl get externalsecrets --all-namespaces | grep shared-registry
# production shared-registry-creds central-vault 6h SecretSynced True
# staging shared-registry-creds central-vault 6h SecretSynced True
# monitoring shared-registry-creds central-vault 6h SecretSynced True
# New namespaces with the label automatically get the secret
kubectl create namespace new-service
kubectl label namespace new-service env=production
# After reconciliation:
kubectl get secret registry-credentials -n new-service
# NAME TYPE DATA AGE
# registry-credentials kubernetes.io/dockerconfigjson 1 30s
Templating & Data Transformation
ESO supports Go templates in spec.target.template to transform secret data before creating the Kubernetes Secret. This enables constructing connection strings, encoding formats, and merging multiple remote values.
# Template: construct a database connection string from individual fields
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-connection
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-sm
kind: SecretStore
target:
name: database-url
template:
engineVersion: v2
data:
# Construct full connection string from components
DATABASE_URL: "postgresql://{{ .username }}:{{ .password }}@{{ .host }}:{{ .port }}/{{ .dbname }}?sslmode=require"
# Base64 decode a value from the provider
TLS_CERT: "{{ .cert | b64dec }}"
# JSON config file from multiple secrets
config.json: |
{
"database": {
"host": "{{ .host }}",
"port": {{ .port }},
"username": "{{ .username }}",
"ssl": true
}
}
data:
- secretKey: username
remoteRef:
key: production/rds
property: username
- secretKey: password
remoteRef:
key: production/rds
property: password
- secretKey: host
remoteRef:
key: production/rds
property: host
- secretKey: port
remoteRef:
key: production/rds
property: port
- secretKey: dbname
remoteRef:
key: production/rds
property: database
- secretKey: cert
remoteRef:
key: production/rds-tls
property: ca_cert_b64
# Template: create a .env file from multiple secrets
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-env-file
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-kv
kind: SecretStore
target:
name: app-dotenv
template:
engineVersion: v2
data:
.env: |
# Auto-generated by External Secrets Operator
DB_HOST={{ .db_host }}
DB_PASSWORD={{ .db_password }}
REDIS_URL={{ .redis_url }}
API_KEY={{ .api_key }}
JWT_SECRET={{ .jwt_secret }}
data:
- secretKey: db_host
remoteRef:
key: production/database
property: host
- secretKey: db_password
remoteRef:
key: production/database
property: password
- secretKey: redis_url
remoteRef:
key: production/redis
property: url
- secretKey: api_key
remoteRef:
key: production/api
property: key
- secretKey: jwt_secret
remoteRef:
key: production/auth
property: jwt_signing_key
b64enc/b64dec (base64), upper/lower (case), trim, replace, toJson/fromJson, and Sprig functions. This enables complex transformations without custom scripts.
Exercises
refreshInterval: 0 to generate it once. Then create a PushSecret that pushes this generated password to a Vault KV path (or use the fake provider for testing). Verify the value exists in both the K8s Secret and the external store.
needs-tls=true. Create three namespaces with the label and verify each gets its own copy. Then remove the label from one namespace and verify the secret is cleaned up.
DATABASE_URL key in the format postgresql://user:pass@host:port/db?sslmode=require. Mount the resulting secret as an environment variable in a pod and verify the connection string is correctly assembled.
Key Takeaways
- PushSecret pushes K8s Secrets to external providers — backup, cross-cloud sync, and centralization
- Generators create dynamic values (passwords, UUIDs, ECR tokens) without requiring an external store
- ClusterExternalSecret syncs one secret to multiple namespaces via label selectors — DRY secret management
- Templating transforms secret data with Go templates — construct connection strings, encode/decode, merge values
- Use
refreshInterval: 0for one-time generation; non-zero for tokens that expire - ESO + Vault + Generators provides a complete secrets lifecycle: generate → store → sync → rotate → push