> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Demo EncryptDecrypt Secret using Bitnami Sealed Secrets

> Converting Kubernetes plain-text Secrets in Git to Bitnami SealedSecrets and using Flux to apply encrypted secrets so clusters receive decrypted Secrets at apply-time

In this lesson you'll convert a plain-text Kubernetes Secret stored in Git into a SealedSecret using Bitnami Sealed Secrets, and then use Flux to apply the encrypted secret to the cluster. This keeps sensitive values encrypted at rest in your repository while allowing the cluster to receive a decrypted Secret at apply-time.

Goals

* Inspect the existing Flux Kustomization and the plain Secret file in Git.
* Demonstrate Flux reconciliation re-creating a deleted Secret.
* Suspend reconciliation, delete the in-cluster plain Secret, and show the workload failing.
* Create a SealedSecret using the `kubeseal` CLI and the Sealed Secrets controller public certificate.
* Replace the plain Secret in Git with the SealedSecret and resume reconciliation.
* Verify the Secret is decrypted in-cluster and the workload recovers.

Overview of resources

| Resource Type | Purpose | Example |
| - | - | - |
| Kustomization | Flux component that applies manifests from a Git repo to a target namespace | See the Kustomization manifest below |
| Secret | Standard Kubernetes Secret (avoid storing plain-text in Git) | `apiVersion: v1\nkind: Secret` (see example) |
| SealedSecret | Encrypted secret safe to store in Git; controller decrypts to a Secret in-cluster | `apiVersion: bitnami.com/v1alpha1\nkind: SealedSecret` (see example) |

***

Kustomization in the infrastructure repository (applies manifests in the `database` namespace)

```yaml theme={null}
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
  name: infra-database-kustomize-git-mysql
  namespace: flux-system
spec:
  interval: 10s
  path: ./database
  prune: true
  sourceRef:
    kind: GitRepository
    name: infra-source-git
  targetNamespace: database
```

Plain Secret stored in the repository (contains a plain-text password — not recommended)

```yaml theme={null}
apiVersion: v1
kind: Secret
metadata:
  name: secret-mysql
  namespace: database
stringData:
  password: mysql-password-0123456789
```

Note: never keep plain-text secrets in Git. We will replace this file with a SealedSecret.

***

How Flux reconciliation behaves (short demonstration)

Flux constantly reconciles Git to the cluster. If you delete a resource in-cluster, Flux will likely re-create it when it next reconciles. Example session (cleaned for readability):

```bash theme={null}
# Show pod and secret in the database namespace
kubectl -n database get po,secret

# Output (example)
# NAME                             READY   STATUS    RESTARTS   AGE
# pod/mysql-7d89476576-m7z12       1/1     Running   0          5h12m
#
# NAME                     TYPE    DATA   AGE
# Delete the secret
kubectl -n database delete secret secret-mysql
# Output:
# After a short time, Flux re-reconciles and re-creates the secret (because the Kustomization is active)
kubectl -n database get secret secret-mysql -o wide
# Output shows the secret exists again, age ~28s
```

Because the Kustomization above uses `interval: 10s`, Flux checks and applies changes frequently.

***

Suspend reconciliation so we can perform the SealedSecret transition safely

Temporarily suspend the Kustomization so Flux does not immediately re-create the plain Secret while you prepare the SealedSecret:

```bash theme={null}
# Suspend the Kustomization
flux suspend kustomization infra-database-kustomize-git-mysql
# Output:
# suspending kustomization infra-database-kustomize-git-mysql in flux-system namespace
# Verify suspended
flux get kustomizations infra-database-kustomize-git-mysql
# Output includes: SUSPENDED  True
```

Delete the in-cluster plain Secret and restart the workload to show the pod failing when the Secret is missing:

```bash theme={null}
kubectl -n database delete secret secret-mysql
kubectl -n database rollout restart deployment mysql
kubectl -n database get po,deploy

# Example output after restart:
# NAME                             READY   STATUS                      RESTARTS   AGE
# pod/mysql-7dcff584fc-9bwq1       0/1     CreateContainerConfigError  0          2s
#
# NAME                             READY   UP-TO-DATE   AVAILABLE   AGE
# deployment.apps/mysql            0/1     1            0           5h15m
```

Describe the failing pod to see the container error: the pod will report CreateContainerConfigError due to the missing Secret.

***

Create a SealedSecret using the kubeseal CLI

We will convert the plain Secret YAML into a SealedSecret using the Sealed Secrets controller public certificate and the `kubeseal` CLI. The SealedSecret preserves key names (like `password`) but encrypts values so the file is safe to store in Git.

1. Ensure the plain Secret manifest (the file that previously lived in Git) is present locally as `secret-mysql.yaml`.

2. Run kubeseal with the controller public certificate:

```bash theme={null}
kubeseal -o yaml --scope cluster-wide --cert ../../sealed-secret.pub < secret-mysql.yaml > sealed-secret-mysql.yaml
```

Flags explained:

* `-o yaml` — output a SealedSecret resource as YAML.
* `--scope cluster-wide` — annotate the SealedSecret for cluster-wide usage (be explicit about scope).
* `--cert ../../sealed-secret.pub` — use the Sealed Secrets controller public certificate. Adjust the path to your cert file.
* `< secret-mysql.yaml` — read the original Secret from stdin.
* `> sealed-secret-mysql.yaml` — write the SealedSecret YAML to a new file.

Example generated SealedSecret (truncated encryptedData value):

```yaml theme={null}
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  annotations:
    sealedsecrets.bitnami.com/cluster-wide: "true"
  name: secret-mysql
  namespace: database
spec:
  encryptedData:
    password: AgAU95okhHvK4NMdTn... (encrypted blob)
  template:
    metadata:
      annotations:
        sealedsecrets.bitnami.com/cluster-wide: "true"
      name: secret-mysql
      namespace: database
```

Only values are encrypted; the key `password` remains visible so the manifest stays clear.

Before committing the SealedSecret to Git, remove or rename the plain Secret manifest so Flux will no longer reconcile the plain file:

```bash theme={null}
# Move the plain file to a backup name (so it's no longer recognized by Flux)
mv secret-mysql.yaml secret-mysql.yaml.bak
```

Commit and push the new `sealed-secret-mysql.yaml` to your repository in the same location where the plain Secret previously lived.

Do not keep plain-text secret files in Git. Use SealedSecrets (or another secret management solution) to store secrets safely in your repository.

***

Resume Flux reconciliation and let the SealedSecrets controller decrypt and apply

After pushing the SealedSecret to Git, resume the Kustomization so Flux fetches the latest revision and applies the SealedSecret:

```bash theme={null}
# Optionally reconcile the Git source to speed up fetch (uncomment if needed)
# Resume the Kustomization
flux resume kustomization infra-database-kustomize-git-mysql
# Output:
# ✔ kustomization resumed
```

When Flux applies the SealedSecret manifest, the Sealed Secrets controller in-cluster uses its private key to decrypt the `encryptedData` and create a standard Kubernetes Secret in the `database` namespace.

Verify the Secret exists and decode the password to confirm the value:

```bash theme={null}
# Check that the secret exists
kubectl -n database get secret secret-mysql
# Example output:
# NAME            TYPE     DATA  AGE
# Inspect and decode the password field
kubectl -n database get secret secret-mysql -o json | jq .data.password -r | base64 -d
# Output:
# mysql-password-0123456789
```

Tools used above:

* `jq` for JSON querying: [https://stedolan.github.io/jq/](https://stedolan.github.io/jq/)
* `base64` for decoding: [https://man7.org/linux/man-pages/man1/base64.1.html](https://man7.org/linux/man-pages/man1/base64.1.html)

Restart or watch the deployment recover. The pod that previously failed due to the missing Secret should start successfully once the Secret is present:

```bash theme={null}
kubectl -n database get po,deploy
# Example output:
# NAME                         READY  STATUS   RESTARTS  AGE
# pod/mysql-7d89476576-wt5qb   1/1    Running  0         74s
#
# NAME                         READY  UP-TO-DATE  AVAILABLE  AGE
# deployment.apps/mysql        1/1    1           1          5h15m
```

***

How it works (high level)

* `kubeseal` encrypts secret values using the Sealed Secrets controller public key and produces a SealedSecret manifest safe to store in Git.
* When Flux applies the SealedSecret, the Sealed Secrets controller (running in-cluster) intercepts it and decrypts the data using its private key, creating a standard Kubernetes Secret in the desired namespace.
* This workflow lets you keep encrypted secrets in Git while enabling the cluster to receive decrypted Secrets at apply-time, improving security in GitOps workflows.

Common commands quick reference

| Action | Command |
| - | - |
| Suspend Kustomization | `flux suspend kustomization infra-database-kustomize-git-mysql` |
| Resume Kustomization | `flux resume kustomization infra-database-kustomize-git-mysql` |
| Seal a Secret | `kubeseal -o yaml --scope cluster-wide --cert <path-to-pub> < secret.yaml > sealed-secret.yaml` |
| Decode secret value | `kubectl -n database get secret secret-mysql -o json \| jq .data.password -r \| base64 -d` |
| Delete in-cluster secret | `kubectl -n database delete secret secret-mysql` |

***

Links and References

* Bitnami Sealed Secrets: [https://github.com/bitnami-labs/sealed-secrets](https://github.com/bitnami-labs/sealed-secrets)
* kubeseal CLI: [https://github.com/bitnami-labs/sealed-secrets#kubeseal-cli](https://github.com/bitnami-labs/sealed-secrets#kubeseal-cli)
* Sealed Secrets controller: [https://github.com/bitnami-labs/sealed-secrets#controller](https://github.com/bitnami-labs/sealed-secrets#controller)
* FluxCD: [https://fluxcd.io/](https://fluxcd.io/)
* Flux Kustomization docs: [https://toolkit.fluxcd.io/components/kustomize/kustomization/](https://toolkit.fluxcd.io/components/kustomize/kustomization/)
* jq: [https://stedolan.github.io/jq/](https://stedolan.github.io/jq/)
* base64 man page: [https://man7.org/linux/man-pages/man1/base64.1.html](https://man7.org/linux/man-pages/man1/base64.1.html)

That completes the end-to-end workflow for encrypting and decrypting secrets using Bitnami Sealed Secrets with Flux.

> **[Watch Video](https://learn.kodekloud.com/user/courses/gitops-with-fluxcd/module/c8ad2608-2804-4413-9041-5e8dc9126d53/lesson/ab6dc091-5ddd-40ce-935b-09d5f0f091c6)**

> **[Practice Lab](https://learn.kodekloud.com/user/courses/gitops-with-fluxcd/module/c8ad2608-2804-4413-9041-5e8dc9126d53/lesson/05fbab53-e949-4d5f-aadc-8ba1950fbc5c)**


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.