Skip to main content

Overview

Sealed Secrets allows you to encrypt Kubernetes Secret resources so they can be safely stored in Git repositories. The controller decrypts them into regular Secrets when deployed to the cluster.

How It Works

1

Encrypt locally

Use kubeseal CLI to encrypt a Secret using the cluster’s public key
2

Store in Git

Commit the encrypted SealedSecret to your Git repository
3

Deploy with GitOps

Flux applies the SealedSecret to the cluster
4

Automatic decryption

The sealed-secrets controller decrypts it into a regular Secret

HelmRelease Configuration

Sealed Secrets is deployed using Flux with the following HelmRelease:
helm-release.yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: sealed-secrets
spec:
  chart:
    spec:
      chart: sealed-secrets
      sourceRef:
        kind: HelmRepository
        name: sealed-secrets
      version: "=2.17.3"
      # https://github.com/bitnami-labs/sealed-secrets/releases
  interval: 24h
  releaseName: sealed-secrets-controller
  install:
    crds: Create
  upgrade:
    crds: CreateReplace
  # https://github.com/bitnami-labs/sealed-secrets/blob/main/helm/sealed-secrets/values.yaml
  values:
    metrics:
      serviceMonitor:
        enabled: false
        namespace: sealed-secrets

Configuration Parameters

spec.chart.spec.version
string
required
Sealed Secrets chart version. Currently using 2.17.3
spec.interval
string
default:"24h"
How often Flux checks for chart updates
spec.values.metrics.serviceMonitor.enabled
boolean
default:"false"
Enable Prometheus ServiceMonitor for metrics collection

Installing kubeseal CLI

brew install kubeseal

Creating Sealed Secrets

Method 1: From existing Secret

1

Create a regular Secret

secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: database-credentials
  namespace: production
type: Opaque
stringData:
  username: admin
  password: super-secret-password
2

Seal the Secret

kubeseal -f secret.yaml -w sealed-secret.yaml
This creates an encrypted SealedSecret resource
3

Commit to Git

git add sealed-secret.yaml
git commit -m "Add database credentials"
git push
Never commit the unencrypted secret.yaml to Git! Delete it after sealing.

Method 2: Direct command

Create a SealedSecret directly without intermediate files:
kubectl create secret generic database-credentials \
  --dry-run=client \
  --from-literal=username=admin \
  --from-literal=password=super-secret-password \
  -o yaml | \
kubeseal -o yaml > sealed-secret.yaml

Method 3: From file

Seal secrets from files:
kubectl create secret generic tls-cert \
  --dry-run=client \
  --from-file=tls.crt=cert.pem \
  --from-file=tls.key=key.pem \
  -o yaml | \
kubeseal -o yaml > sealed-tls-secret.yaml

SealedSecret Example

A SealedSecret looks like this:
sealed-secret.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: database-credentials
  namespace: production
spec:
  encryptedData:
    username: AgBvXm1k5F3d...(encrypted data)...==
    password: AgCQRv8Hj6Kw...(encrypted data)...==
  template:
    metadata:
      name: database-credentials
      namespace: production
    type: Opaque
When applied to the cluster, the controller automatically creates:
apiVersion: v1
kind: Secret
metadata:
  name: database-credentials
  namespace: production
type: Opaque
data:
  username: YWRtaW4=
  password: c3VwZXItc2VjcmV0LXBhc3N3b3Jk

Scopes

Sealed Secrets support different scopes controlling where they can be decrypted:
Sealed secret is bound to the exact namespace and name:
kubeseal --scope strict -f secret.yaml -w sealed-secret.yaml
Characteristics:
  • Most secure
  • Cannot rename the secret
  • Cannot move to different namespace
Use case: Production secrets with strict access control

Key Management

Fetch the public key

Download the cluster’s public key for offline sealing:
kubeseal --fetch-cert > public-key.pem
Then use it to seal secrets without cluster access:
kubeseal --cert public-key.pem -f secret.yaml -w sealed-secret.yaml
Store the public key in your repository so team members can create sealed secrets without cluster access.

Backup encryption keys

Backup the controller’s private keys:
kubectl get secret -n sealed-secrets -l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml > sealed-secrets-keys.yaml
Store this backup securely! If you lose the encryption keys, you cannot decrypt existing SealedSecrets.

Restore encryption keys

To restore keys on a new cluster:
kubectl apply -f sealed-secrets-keys.yaml
kubectl delete pod -n sealed-secrets -l app.kubernetes.io/name=sealed-secrets

Using Sealed Secrets in Applications

Once decrypted, use the Secret like any other Kubernetes Secret:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
        - name: app
          image: my-app:latest
          env:
            - name: DB_USERNAME
              valueFrom:
                secretKeyRef:
                  name: database-credentials
                  key: username
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: database-credentials
                  key: password

Verifying Sealed Secrets

Check controller status

kubectl get pods -n sealed-secrets
Expected output:
NAME                                    READY   STATUS    RESTARTS   AGE
sealed-secrets-controller-xxxxx-xxxxx   1/1     Running   0          5d

View controller logs

kubectl logs -n sealed-secrets -l app.kubernetes.io/name=sealed-secrets

Check if Secret was created

kubectl get secret database-credentials -n production

Verify Secret content (base64 decoded)

kubectl get secret database-credentials -n production -o jsonpath='{.data.username}' | base64 -d

Troubleshooting

SealedSecret not decrypting

1

Check SealedSecret exists

kubectl get sealedsecrets -A
2

Check controller logs

kubectl logs -n sealed-secrets -l app.kubernetes.io/name=sealed-secrets
Look for decryption errors
3

Verify namespace and name match

With strict scope, namespace and name must exactly match the original
4

Check public/private key match

kubeseal --fetch-cert | openssl x509 -noout -fingerprint

Rotating secrets

To update a sealed secret:
  1. Create new Secret with updated values
  2. Seal it with kubeseal
  3. Apply the new SealedSecret (it will replace the old one)
  4. The controller automatically updates the decrypted Secret
# Create updated secret
kubectl create secret generic database-credentials \
  --dry-run=client \
  --from-literal=username=admin \
  --from-literal=password=new-password \
  -o yaml | \
kubeseal -o yaml > sealed-secret-updated.yaml

# Apply the update
kubectl apply -f sealed-secret-updated.yaml

GitOps Workflow

Typical workflow with Flux:
# 1. Create and seal secret
kubectl create secret generic app-config \
  --dry-run=client \
  --from-literal=api-key=abc123 \
  -n production \
  -o yaml | kubeseal -o yaml > overlays/production/app-config.sealed.yaml

# 2. Add to kustomization
cat >> overlays/production/kustomization.yaml <<EOF
resources:
  - app-config.sealed.yaml
EOF

# 3. Commit and push
git add overlays/production/
git commit -m "Add app configuration secret"
git push

# 4. Flux automatically applies the SealedSecret
# 5. Controller decrypts it into a regular Secret
# 6. Your application can use the Secret

Best Practices

  • Use strict scope for production secrets
  • Never commit unencrypted secrets to Git
  • Backup sealed-secrets encryption keys securely
  • Store the public key in your repository for team access
  • Use descriptive names for secrets
  • Document what each sealed secret contains in your repository
  • Rotate secrets regularly
  • Use separate SealedSecrets per environment (dev, staging, prod)

Resources

Build docs developers (and LLMs) love