Configure Kubernetes secrets management with Sealed Secrets for secure Helm values

Intermediate 45 min Apr 21, 2026 106 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Learn to implement Sealed Secrets controller for encrypting Kubernetes secrets in Git repositories, enabling secure GitOps workflows with encrypted Helm values and automated secret management.

Prerequisites

  • Kubernetes cluster with admin access
  • Basic understanding of Kubernetes secrets
  • Git repository for GitOps workflows

What this solves

Kubernetes secrets contain sensitive data like passwords, API keys, and certificates, but storing them in Git repositories poses security risks. Sealed Secrets encrypts your secrets using asymmetric cryptography, allowing you to safely commit encrypted secrets to Git while the controller automatically decrypts them in your cluster. This enables secure GitOps workflows where all configuration, including sensitive data, lives in version control without compromising security.

Step-by-step installation

Install kubectl and verify cluster access

Ensure you have kubectl installed and configured to access your Kubernetes cluster before proceeding with Sealed Secrets installation.

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
kubectl version --client
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
kubectl version --client
kubectl cluster-info
kubectl get nodes

Install Helm package manager

Sealed Secrets controller can be installed via Helm charts, which simplifies deployment and configuration management.

curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
sudo apt-get install apt-transport-https --yes
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install helm
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh
helm version

Add Sealed Secrets Helm repository

Add the official Sealed Secrets Helm repository and update the repository index to get the latest chart versions.

helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm repo update
helm search repo sealed-secrets

Create dedicated namespace for Sealed Secrets

Deploy the Sealed Secrets controller in its own namespace to isolate it from application workloads and simplify RBAC management.

kubectl create namespace sealed-secrets-system
kubectl get namespaces

Install Sealed Secrets controller with Helm

Deploy the controller with recommended security settings including resource limits and security contexts.

helm install sealed-secrets sealed-secrets/sealed-secrets \
  --namespace sealed-secrets-system \
  --set-string fullnameOverride=sealed-secrets-controller \
  --set resources.limits.cpu=1000m \
  --set resources.limits.memory=1Gi \
  --set resources.requests.cpu=100m \
  --set resources.requests.memory=128Mi

Verify controller deployment

Check that the Sealed Secrets controller pod is running and the service is accessible within the cluster.

kubectl get pods -n sealed-secrets-system
kubectl get svc -n sealed-secrets-system
kubectl logs -n sealed-secrets-system deployment/sealed-secrets-controller

Install kubeseal CLI tool

The kubeseal client encrypts your secrets locally before committing them to Git repositories.

KUBESEAL_VERSION='0.24.0'
wget "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz"
tar -xvzf kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz kubeseal
sudo install -o root -g root -m 0755 kubeseal /usr/local/bin/kubeseal
KUBESEAL_VERSION='0.24.0'
wget "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz"
tar -xvzf kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz kubeseal
sudo install -o root -g root -m 0755 kubeseal /usr/local/bin/kubeseal
kubeseal --version

Fetch controller public key

Retrieve the public key from the controller to encrypt secrets locally. This key is automatically generated when the controller starts.

kubeseal --fetch-cert --controller-name=sealed-secrets-controller --controller-namespace=sealed-secrets-system > mykey.pem
cat mykey.pem

Configure encrypted secrets for applications

Create a test application namespace

Create a namespace where you'll deploy applications that use encrypted secrets.

kubectl create namespace myapp
kubectl get namespaces

Create a regular Kubernetes secret

Start by creating a standard Kubernetes secret that contains your sensitive data. This will be encrypted using kubeseal.

kubectl create secret generic database-credentials \
  --from-literal=username=admin \
  --from-literal=password=super-secret-password \
  --from-literal=host=postgresql.example.com \
  --namespace=myapp \
  --dry-run=client -o yaml > database-secret.yaml
cat database-secret.yaml

Encrypt the secret with kubeseal

Transform the regular secret into a SealedSecret that can be safely stored in Git repositories.

kubeseal --format=yaml --cert=mykey.pem < database-secret.yaml > database-sealed-secret.yaml
cat database-sealed-secret.yaml
Note: The SealedSecret contains encrypted data that can only be decrypted by the controller in your specific cluster.

Apply the encrypted secret

Deploy the SealedSecret to your cluster. The controller will automatically decrypt it and create the corresponding Secret.

kubectl apply -f database-sealed-secret.yaml
kubectl get sealedsecrets -n myapp
kubectl get secrets -n myapp

Verify secret decryption

Check that the controller successfully decrypted the SealedSecret and created the regular Kubernetes Secret.

kubectl describe secret database-credentials -n myapp
kubectl get secret database-credentials -n myapp -o jsonpath='{.data.username}' | base64 -d
echo
kubectl get secret database-credentials -n myapp -o jsonpath='{.data.password}' | base64 -d

Integrate with Helm charts

Create Helm chart with sealed secrets

Set up a sample Helm chart that uses SealedSecrets for managing sensitive configuration values.

helm create myapp-chart
cd myapp-chart

Configure Helm templates for SealedSecrets

Create a template that generates SealedSecret resources from Helm values.

{{- if .Values.sealedSecrets.enabled }}
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: {{ include "myapp-chart.fullname" . }}-secrets
  namespace: {{ .Release.Namespace }}
spec:
  encryptedData:
{{- range $key, $value := .Values.sealedSecrets.encryptedData }}
    {{ $key }}: {{ $value }}
{{- end }}
  template:
    metadata:
      name: {{ include "myapp-chart.fullname" . }}-secrets
      namespace: {{ .Release.Namespace }}
{{- end }}

Update Helm values with encrypted data

Configure the values file to include encrypted secret data that was generated with kubeseal.

sealedSecrets:
  enabled: true
  encryptedData:
    database-url: AgBy3i4OJSWK+PiTySYZZA9rO5QtQyqQ7MAYRNxgqhXNr...
    api-key: AgBybHRQF2XP4QnZBJYA8hW6N0iyOuOG2JXyB7QaT8Tc9...
Note: Replace the encrypted values with actual output from your kubeseal commands.

Deploy application with encrypted secrets

Install the Helm chart which will create the SealedSecret and automatically decrypt it.

helm install myapp . --namespace myapp
kubectl get sealedsecrets -n myapp
kubectl get secrets -n myapp

Automate GitOps workflows

Set up Git repository structure

Organize your GitOps repository to separate plain configuration from encrypted secrets.

mkdir -p gitops-repo/{applications,sealed-secrets,helm-charts}
cd gitops-repo

Create automation script for secret encryption

Build a script that automates the process of encrypting secrets and updating SealedSecret manifests.

#!/bin/bash
set -e

NAMESPACE=${1:-default}
SECRET_NAME=${2:-app-secrets}
CERT_FILE=${3:-controller.pem}

if [ ! -f "$CERT_FILE" ]; then
    echo "Fetching controller certificate..."
    kubeseal --fetch-cert --controller-name=sealed-secrets-controller \
        --controller-namespace=sealed-secrets-system > "$CERT_FILE"
fi

echo "Creating temporary secret..."
kubectl create secret generic "$SECRET_NAME" \
    --from-env-file=secrets.env \
    --namespace="$NAMESPACE" \
    --dry-run=client -o yaml > temp-secret.yaml

echo "Encrypting secret..."
kubeseal --format=yaml --cert="$CERT_FILE" < temp-secret.yaml > "sealed-secrets/${NAMESPACE}-${SECRET_NAME}.yaml"

echo "Cleaning up..."
rm temp-secret.yaml

echo "SealedSecret created: sealed-secrets/${NAMESPACE}-${SECRET_NAME}.yaml"
chmod +x scripts/encrypt-secrets.sh

Create environment-specific sealed secrets

Generate encrypted secrets for different environments while maintaining the same workflow.

DATABASE_URL=postgresql://admin:password@postgres.production.example.com:5432/myapp
API_SECRET_KEY=prod-secret-key-here
REDIS_PASSWORD=redis-production-password
./scripts/encrypt-secrets.sh production app-config
ls -la sealed-secrets/

Configure CI/CD pipeline integration

Set up a CI/CD workflow that automatically encrypts secrets and updates your GitOps repository.

name: Update Sealed Secrets

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Target environment'
        required: true
        default: 'staging'
        type: choice
        options:
        - staging
        - production

jobs:
  encrypt-secrets:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Install kubeseal
      run: |
        KUBESEAL_VERSION='0.24.0'
        wget "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz"
        tar -xvzf kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz kubeseal
        sudo install -o root -g root -m 0755 kubeseal /usr/local/bin/kubeseal
    
    - name: Configure kubectl
      run: |
        echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > ~/.kube/config
    
    - name: Update sealed secrets
      run: |
        echo "${{ secrets[format('{0}_SECRETS', github.event.inputs.environment)] }}" > secrets.env
        ./scripts/encrypt-secrets.sh ${{ github.event.inputs.environment }} app-config
    
    - name: Commit changes
      run: |
        git config user.name github-actions
        git config user.email github-actions@github.com
        git add sealed-secrets/
        git commit -m "Update sealed secrets for ${{ github.event.inputs.environment }}"
        git push

Security and key management

Backup controller private key

Export the controller's private key for disaster recovery purposes. Store this key securely outside the cluster.

kubectl get secret sealed-secrets-key -n sealed-secrets-system -o yaml > sealed-secrets-master-key.yaml
Critical: Store this key backup in a secure location. Anyone with this key can decrypt all your sealed secrets.

Configure key rotation policy

Set up automated key rotation by configuring the controller to generate new keys periodically.

keyRotation:
  enabled: true
  schedule: "0 0 1  "  # Monthly rotation
  retainOldKeys: 3
helm upgrade sealed-secrets sealed-secrets/sealed-secrets \
  --namespace sealed-secrets-system \
  --values key-rotation-values.yaml

Monitor sealed secrets operations

Set up monitoring and alerting for sealed secrets controller operations and failures.

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: sealed-secrets-controller
  namespace: sealed-secrets-system
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: sealed-secrets
  endpoints:
  - port: http
    interval: 30s
    path: /metrics
kubectl apply -f monitoring.yaml

Verify your setup

# Check controller status
kubectl get pods -n sealed-secrets-system
kubectl get svc -n sealed-secrets-system

Verify kubeseal connectivity

kubeseal --version kubeseal --fetch-cert --controller-name=sealed-secrets-controller --controller-namespace=sealed-secrets-system

Check sealed secrets in applications

kubectl get sealedsecrets --all-namespaces kubectl get secrets --all-namespaces | grep -v default-token

Test secret decryption

echo "test-value" | kubectl create secret generic test-secret --dry-run=client --from-file=/dev/stdin -o yaml | kubeseal --format=yaml --cert=mykey.pem | kubectl apply -f - kubectl get secret test-secret -o jsonpath='{.data.stdin}' | base64 -d

Common issues

SymptomCauseFix
kubeseal: cannot fetch certificateController not running or wrong namespacekubectl get pods -n sealed-secrets-system and verify controller name
SealedSecret stuck in pendingController can't decrypt or wrong scopeCheck kubectl logs -n sealed-secrets-system deployment/sealed-secrets-controller
Secret not created after applying SealedSecretRBAC issues or controller errorsVerify controller has permissions: kubectl auth can-i create secrets --as=system:serviceaccount:sealed-secrets-system:sealed-secrets-controller
Encryption fails with certificate errorUsing wrong public key or expired certificateRe-fetch certificate: kubeseal --fetch-cert > mykey.pem
Helm chart deployment failsSealedSecret CRD not installedCheck CRDs: kubectl get crd sealedsecrets.bitnami.com

Next steps

Running this in production?

Want this handled for you? Setting this up once is straightforward. Keeping it patched, monitored, backed up and tuned across environments is the harder part. See how we run infrastructure like this for European SaaS and e-commerce teams.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle managed devops services for businesses that depend on uptime. From initial setup to ongoing operations.