CloudOpsGuide
helm

Helm Secrets: Managing Sensitive Data

Intermediate
15 minutes
October 2026
CloudOpsGuide Team

Helm Secrets: Managing Sensitive Data

Securely manage secrets in Helm charts — from Helm Secrets plugin and SOPS to Azure Key Vault and External Secrets Operator.

Table of Contents

The Problem

Helm values files are plain text — storing password: secret123 in values-prod.yaml means secrets live in Git forever. Even with .gitignore, the file is still on disk.

You need one of these approaches:

ApproachHowProsCons
Helm Secrets + SOPSEncrypt values filesGit-safe, offlineExtra tooling
External SecretsSync from cloud vaultsNative secrets, auto-rotationRequires operator
Sealed SecretsEncrypt into git-safe CRDsDeclarative, GitOps-friendlyController required
Manualkubectl create secret before deploySimpleManual, error-prone

Helm Secrets + SOPS

Encrypt secrets in values files using SOPS (by Mozilla, now maintained at getsops).

Setup

# Install Helm Secrets plugin
helm plugin install https://github.com/jkroepke/helm-secrets

# Install SOPS
brew install sops    # or download from GitHub releases

Create Encrypted Values

# Create a SOPS-encrypted file
sops secrets.prod.yaml

This opens your editor with the decrypted content — saves encrypted.

# secrets.prod.yaml — encrypted on disk
db:
  password: ENC[AES256_GCM,data:abc123...,type:str]
api:
  key: ENC[AES256_GCM,data:def456...,type:str]

Deploy with Encrypted Secrets

# Helm Secrets decrypts on the fly during template/install
helm secrets upgrade myapp ./chart \
  -f secrets.prod.yaml \
  --namespace prod

In CI/CD

# Decrypt with a key stored in CI secrets
export SOPS_AGE_KEY=$SOPS_AGE_KEY    # from CI secret store
sops -d secrets.prod.yaml > decrypted.yaml
helm upgrade myapp ./chart -f decrypted.yaml
rm decrypted.yaml

External Secrets Operator

Sync secrets from cloud vaults (AWS Secrets Manager, Azure Key Vault, HashiCorp Vault, GCP Secret Manager) into Kubernetes Secrets.

Install

helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets \
  -n external-secrets-system \
  --set installCRDs=true

Configure SecretStore

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: azure-keyvault
spec:
  provider:
    azurekv:
      tenantId: <tenant-id>
      vaultUrl: https://my-vault.vault.azure.net/
      authSecretRef:
        clientId:
          name: azure-creds
          key: ClientID
        clientSecret:
          name: azure-creds
          key: ClientSecret

ExternalSecret

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-secret
spec:
  refreshInterval: 15s
  secretStoreRef:
    name: azure-keyvault
    kind: SecretStore
  target:
    name: db-secret
    creationPolicy: Owner
  data:
  - secretKey: password
    remoteRef:
      key: database-password
  - secretKey: apiKey
    remoteRef:
      key: api-key

Creates a Kubernetes Secret named db-secret synced from Key Vault every 15 seconds.

Sealed Secrets

Encrypt secrets into a SealedSecret CRD that can be safely committed to Git.

Install

kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.27.0/controller.yaml

Seal a Secret

# Install kubeseal CLI
brew install kubeseal

# Create a sealed secret
kubectl create secret generic db-secret \
  --from-literal=password=mysecret \
  --dry-run=client -o yaml | \
  kubeseal --format yaml > sealed-secret.yaml
# sealed-secret.yaml — safe to commit
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: db-secret
spec:
  encryptedData:
    password: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEQ...    # encrypted
# Apply — controller creates the real Secret
kubectl apply -f sealed-secret.yaml
kubectl get secret db-secret    # now exists

Azure Key Vault

Using Azure Key Vault CSI Driver

helm install csi-secrets-store-provider-azure \
  https://azure.github.io/secrets-store-csi-driver-provider-azure/charts/csi-secrets-store-provider-azure.tgz \
  --namespace kube-system
# SecretProviderClass — mounts Key Vault as volume
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: azure-kv
spec:
  provider: azure
  parameters:
    usePodIdentity: "false"
    useVMManagedIdentity: "true"
    userAssignedIdentityID: <identity-client-id>
    keyvaultName: my-vault
    objects: |
      array:
        - |
          objectName: db-password
          objectType: secret
        - |
          objectName: api-key
          objectType: secret
# Pod mounts it
spec:
  containers:
    - name: app
      volumeMounts:
        - name: secrets-store
          mountPath: /mnt/secrets
          readOnly: true
  volumes:
    - name: secrets-store
      csi:
        driver: secrets-store.csi.k8s.io
        readOnly: true
        volumeAttributes:
          secretProviderClass: azure-kv

Best Practices

1. Never Commit Plain Secrets

# Bad — values-prod.yaml in git
db:
  password: supersecret123

# Good — values-prod.yaml references a pre-existing secret
db:
  password: {{ .Values.db.password }}    # from secrets store at deploy time

2. Separate Secret Management from App Deployment

# Step 1: Create secrets (by admin or CI with elevated perms)
kubectl create secret generic db-secret \
  --from-literal=password=$DB_PASSWORD \
  -n production

# Step 2: Deploy app referencing existing secret
helm upgrade myapp ./chart \
  --set db.existingSecret=db-secret \
  -n production

3. Use existingSecret Pattern in Charts

# In your chart's deployment.yaml
env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: {{ .Values.db.existingSecret }}
        key: password

4. Rotate Secrets Regularly

# With External Secrets — rotate in the vault, auto-syncs
# With Helm Secrets — re-encrypt with new values
# With Sealed Secrets — re-seal and re-apply

Quick Comparison

MethodGit-SafeAuto-RotationSetup ComplexityBest For
Helm Secrets + SOPS✅❌MediumSmall teams, simple needs
External Secrets✅✅MediumProduction, cloud-native
Sealed Secrets✅❌LowGitOps workflows
Azure KV CSI✅✅MediumAzure AKS clusters
Manual kubectl❌❌NoneQuick testing only

Related Articles


Last Updated: October 2026
Author: CloudOpsGuide Team
Difficulty: Intermediate
Estimated Reading Time: 15 minutes