helm-secrets is a Helm plugin that allows you to store encrypted secrets directly within your Helm charts.
Here’s a breakdown of how it works and how to use it:
The Surprising Truth: Your Secrets Aren’t Really In Your Helm Charts
Helm charts are typically version-controlled and shared. If you commit your secrets directly into your chart, they’re exposed to anyone with access to your repository. helm-secrets solves this by encrypting your secrets before they’re committed, and then decrypting them only when Helm needs to deploy them.
Seeing helm-secrets in Action
Let’s imagine you have a simple Helm chart that needs a database password.
1. Install the Plugin
First, you need to install the helm-secrets plugin:
helm plugin install https://github.com/helm/helm-secrets
2. Set up Encryption
helm-secrets uses Mozilla SOPS for encryption. You’ll need to configure SOPS to manage your encryption keys. The simplest way is to use a local KMS (Key Management Service) or a cloud provider’s KMS. For local development, you can use the age backend, which is often the easiest to get started with.
First, install age:
brew install age # On macOS
# Or follow instructions for your OS at https://age-encryption.org/
Then, generate an age keypair:
age-keygen -o ~/.config/sops/age/key.txt
This will create ~/.config/sops/age/key.txt containing your public and private keys. SOPS will automatically pick this up.
3. Create an Encrypted Secret
Let’s create a secrets.yaml file in your Helm chart’s templates/ directory.
# templates/secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: my-db-secrets
type: Opaque
data:
DB_PASSWORD: {{ .Values.dbPassword | b64enc }}
Now, create a values.yaml file to hold your actual secret value (which will be encrypted).
# values.yaml
dbPassword: "supersecretpassword123"
To encrypt this value using helm-secrets, you’ll run:
helm secrets encrypt values.yaml
This command will modify values.yaml to encrypt the dbPassword field. It will look something like this:
# values.yaml (encrypted)
dbPassword: ENC(AgEDQZgQ5h9B5N4L...<long_encrypted_string>...)
4. Deploy with Helm
Now, when you deploy your chart, helm-secrets intercepts the process.
helm install my-release ./my-chart
helm-secrets will:
- Detect the
ENC(...)markers invalues.yaml. - Use SOPS to decrypt the
dbPasswordvalue using your configured key. - Pass the decrypted value to Helm.
- Helm will then create the Kubernetes Secret with the decrypted
DB_PASSWORD.
You can verify the secret in Kubernetes:
kubectl get secret my-db-secrets -o yaml
You’ll see the DB_PASSWORD field is base64 encoded, but the original plaintext password was used during deployment.
The Mental Model: Decryption at the Helm Level
helm-secrets acts as a Helm plugin. When you run helm install, helm upgrade, or helm template with helm-secrets enabled (which it is by default if installed), it hooks into Helm’s lifecycle.
- Pre-processing: Before Helm even looks at your
values.yamlor templates,helm-secretsscans them for encrypted values (marked withENC(...)). - Decryption: It uses SOPS to decrypt these values based on your configured KMS.
- Substitution: The decrypted values are then substituted back into the Helm context.
- Helm Execution: Helm proceeds as normal, using the now-decrypted values to render your templates and create Kubernetes resources.
This means your secrets are never stored in plaintext in your Git repository, but they are available in plaintext during the Helm execution phase for Helm to use.
The core levers you control are:
- Encryption Backend: Which KMS (age, AWS KMS, GCP KMS, Azure Key Vault, etc.) SOPS uses. This determines how your encryption keys are managed and accessed.
- Secret Values: What data you choose to encrypt. Typically, sensitive credentials, API keys, and passwords.
- Access Control: Who has access to the decryption keys. This is your primary security control.
The Counterintuitive Bit: How Helm Actually Gets the Secrets
It’s not that helm-secrets magically bypasses Helm’s value parsing. Instead, it hijacks the value loading process. When Helm expects a plain string for a value, helm-secrets intercepts that expectation. If it sees an ENC(...) marker, it performs the decryption and then returns the decrypted plaintext string to Helm. Helm then processes this plaintext string as if it had been there all along, including base64 encoding it for the Kubernetes Secret’s data field if your template requires it. The key is that Helm itself never sees the encrypted form; it only ever deals with the plaintext during its rendering phase.
The Next Step: Managing Multiple Secrets and Environments
Once you’re comfortable encrypting individual values, the next logical step is to manage multiple secrets for different environments (dev, staging, prod) using SOPS’s ability to encrypt different values with different keys or by using different files for each environment.