Secrets in Helm
A comprehensive guide to managing secrets in Helm covering sealed secrets, helm-secrets plugin, external secrets management, and best practices for secure Helm deployments.
Managing secrets securely is one of the most critical aspects of Helm deployments. Secrets include passwords, API keys, TLS certificates, and other sensitive data. Improper handling can lead to:
- Data Breaches: Exposed secrets in Git repositories
- Unauthorized Access: Compromised credentials
- Compliance Violations: Failing audits and regulations
- Financial Loss: Cloud resource abuse
- Reputation Damage: Loss of customer trust
# BAD PRACTICE: Secrets in values.yaml
# These are stored in plaintext in Git and Helm release secrets!
database:
username: admin
password: supersecretpassword # EXPOSED!
host: db.example.com
apiKey: sk-1234567890abcdef # EXPOSED!
tlsKey: |
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQ...
-----END PRIVATE KEY----- # EXPOSED!
# BAD PRACTICE: Secrets in --set
# Visible in:
# - Process list (ps aux)
# - Shell history (~/.bash_history)
# - CI/CD logs
# - Helm release secrets
helm install my-release ./my-chart \
--set database.password=supersecret \
--set apiKey=sk-1234567890abcdef
# BAD PRACTICE: Secrets in chart templates
# Hardcoded secrets in templates are visible to anyone with chart access
apiVersion: v1
kind: Secret
metadata:
name: my-secret
type: Opaque
data:
password: c3VwZXJzZWNyZXQ= # EXPOSED!
apiKey: c2stMTIzNDU2Nzg5MA== # EXPOSED!
# What happens to secrets in Helm:
# 1. Helm stores release info as Kubernetes Secrets
# 2. Secrets in values are stored in plaintext
# 3. Anyone with access to the namespace can read them
kubectl get secret sh.helm.release.v1.my-release.v1 -o yaml
# The release secret contains all values, including secrets!
Sealed Secrets
helm-secrets Plugin
External Secrets Operator
HashiCorp Vault
Sealed Secrets allow you to encrypt secrets so they can be safely stored in Git. The Sealed Secrets controller in the cluster decrypts them into regular Kubernetes Secrets.
# Install Sealed Secrets Controller
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm repo update
helm install sealed-secrets sealed-secrets/sealed-secrets \
--namespace kube-system \
--set fullnameOverride=sealed-secrets-controller
# Install kubeseal CLI
# macOS
brew install kubeseal
# Linux
wget https://github.com/bitnami-labs/sealed-secrets/releases/latest/download/kubeseal-linux-amd64
chmod +x kubeseal-linux-amd64
sudo mv kubeseal-linux-amd64 /usr/local/bin/kubeseal
# Create a Kubernetes Secret (plaintext)
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password=supersecret \
--dry-run=client -o yaml > secret.yaml
# Seal the secret
kubeseal --format yaml < secret.yaml > sealed-secret.yaml
# The sealed secret is safe to commit to Git
cat sealed-secret.yaml
# apiVersion: bitnami.com/v1alpha1
# kind: SealedSecret
# metadata:
# name: db-credentials
# spec:
# encryptedData:
# username: AgBy... (encrypted)
# password: AgBy... (encrypted)
# Apply the sealed secret
kubectl apply -f sealed-secret.yaml
# The controller decrypts it into a regular Secret
kubectl get secret db-credentials
# Use sealed secrets in Helm
# 1. Create sealed secret template
# templates/sealed-secret.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: {{ include "my-app.fullname" . }}-db-credentials
namespace: {{ .Release.Namespace }}
spec:
encryptedData:
username: {{ .Values.secrets.username }}
password: {{ .Values.secrets.password }}
template:
metadata:
name: {{ include "my-app.fullname" . }}-db-credentials
type: Opaque
# 2. Use in Helm values (encrypted values)
secrets:
username: AgBy... # Encrypted with kubeseal
password: AgBy... # Encrypted with kubeseal
# 3. Install with Helm
helm install my-release ./my-chart -f values-sealed.yaml
# 4. Use the secret in your deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-app.fullname" . }}
spec:
template:
spec:
containers:
- name: app
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: {{ include "my-app.fullname" . }}-db-credentials
key: username
- name: DB_PASS
valueFrom:
secretKeyRef:
name: {{ include "my-app.fullname" . }}-db-credentials
key: password
- Commit sealed secrets to Git
- Never commit unsealed secrets
- Back up the controller's private key
- Use namespace-scoped sealed secrets
- Rotate sealing keys periodically
- Use with ArgoCD or Flux for GitOps
The helm-secrets plugin uses SOPS (Secrets OPerationS) to encrypt values files. It supports AWS KMS, GCP KMS, Azure Key Vault, and PGP keys.
# Install helm-secrets plugin
helm plugin install https://github.com/jkroepke/helm-secrets
# Install SOPS
# macOS
brew install sops
# Linux
wget https://github.com/getsops/sops/releases/latest/download/sops-v3.8.1.linux.amd64
chmod +x sops-v3.8.1.linux.amd64
sudo mv sops-v3.8.1.linux.amd64 /usr/local/bin/sops
# Generate a PGP key (or use AWS KMS, GCP KMS, etc.)
gpg --full-generate-key
# Get the fingerprint
gpg --list-secret-keys --fingerprint
# Create .sops.yaml configuration
cat > .sops.yaml < secrets/prod.yaml <
- AWS KMS: Use AWS Key Management Service
- GCP KMS: Use Google Cloud KMS
- Azure Key Vault: Use Azure Key Vault
- PGP: Use PGP/GPG keys
- Age: Use age encryption
- Use .sops.yaml for configuration
- Commit encrypted files to Git
- Never commit decrypted files
- Use cloud KMS for production
- Rotate encryption keys regularly
- Use with CI/CD pipelines
External Secrets Operator (ESO) syncs secrets from external providers into Kubernetes Secrets. It supports Vault, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, and more.
# Install External Secrets Operator
helm repo add external-secrets https://charts.external-secrets.io
helm repo update
helm install external-secrets external-secrets/external-secrets \
--namespace external-secrets \
--create-namespace
# Create a SecretStore (Vault)
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-store
namespace: default
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "my-app-role"
serviceAccountRef:
name: my-app-sa
# Create an ExternalSecret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: default
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-store
kind: SecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: app/db
property: username
- secretKey: password
remoteRef:
key: app/db
property: password
# Use in Helm chart
# templates/external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: {{ include "my-app.fullname" . }}-db-credentials
namespace: {{ .Release.Namespace }}
spec:
refreshInterval: {{ .Values.externalSecrets.refreshInterval | default "1h" }}
secretStoreRef:
name: {{ .Values.externalSecrets.secretStore }}
kind: SecretStore
target:
name: {{ include "my-app.fullname" . }}-db-credentials
data:
- secretKey: username
remoteRef:
key: {{ .Values.externalSecrets.path }}/db
property: username
- secretKey: password
remoteRef:
key: {{ .Values.externalSecrets.path }}/db
property: password
# Use in deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-app.fullname" . }}
spec:
template:
spec:
containers:
- name: app
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: {{ include "my-app.fullname" . }}-db-credentials
key: username
- name: DB_PASS
valueFrom:
secretKeyRef:
name: {{ include "my-app.fullname" . }}-db-credentials
key: password
# Values for External Secrets
externalSecrets:
enabled: true
secretStore: vault-store
refreshInterval: 1h
path: app/db
- Use external secret stores for all secrets
- Configure refresh intervals appropriately
- Use RBAC for ExternalSecret access
- Monitor sync status
- Use with GitOps for automated sync
- Implement secret rotation
| Feature | Sealed Secrets | helm-secrets | External Secrets |
|---|---|---|---|
| Encryption | Asymmetric (public key) | SOPS (KMS/PGP) | External provider |
| GitOps Safe | Yes | Yes | Yes |
| Key Management | Controller manages keys | External KMS/PGP | External provider |
| Rotation | Manual | Manual | Automatic |
| Complexity | Low | Medium | High |
| Best For | Simple GitOps | Encrypted values | Enterprise |
helm upgrade with updated values or secrets.Proper secrets management is essential for secure Helm deployments. Choose the right solution for your needs and never store secrets in plaintext.