Skip to content

Sealed Secrets

GitOps means your cluster's desired state lives in Git, but you cannot commit plain Kubernetes Secret objects there; they are only base64-encoded, not encrypted. We recommend Sealed Secrets for this. It encrypts a secret so it can be safely committed to a Git repository, and only your cluster can decrypt it.

How it works

A controller in your cluster holds a private key. You encrypt secrets against the matching public key using the kubeseal CLI, producing a SealedSecret resource. ArgoCD applies the SealedSecret like any other manifest, and the controller decrypts it in-cluster into a regular Secret that your pods consume. The plaintext never leaves your machine in a form anyone but your cluster can read.

Workflow

The Sealed Secrets controller publishes its current public certificate through the Kubernetes API. Fetch it with the portal-issued kubeconfig configured in Connecting to your cluster:

kubeseal \
  --controller-name sealed-secrets-controller \
  --controller-namespace sealed-secrets \
  --fetch-cert > sealed-secrets-cert.pem

The certificate is public and contains no secret key material. Your kubeconfig has narrowly scoped access to fetch it; it does not grant access to the controller's private sealing keys.

Use the certificate from the destination cluster

Each cluster has its own sealing keys. A Sealed Secret encrypted for one cluster cannot be decrypted by another cluster.

Write a normal Secret manifest (do not commit this file), then seal it with the downloaded certificate:

# Create a Secret manifest locally (not committed)
kubectl create secret generic db-credentials \
  --namespace my-app \
  --from-literal=password='s3cr3t' \
  --dry-run=client -o yaml > secret.yaml

# Seal it against the cluster's public key
kubeseal --cert sealed-secrets-cert.pem --format yaml \
  < secret.yaml > sealed-secret.yaml

Commit sealed-secret.yaml to your repository and let ArgoCD deploy it. The resulting Secret appears in the my-app namespace for your pods to mount or reference as environment variables.

Using --cert makes sealing an offline operation: after fetching the public certificate, kubeseal does not need further cluster access. Re-fetch the certificate before creating new Sealed Secrets if SUNET announces a sealing-key rotation.

A SealedSecret is encrypted for a specific namespace and name by default, so move or rename it only by re-sealing.