Stage 4 · Provision
GitOps Workflows
Secrets in GitOps
SealedSecrets, External Secrets Operator, and SOPS — storing encrypted secrets in Git.
The Secrets Problem
GitOps requires storing desired state in Git. But Kubernetes secrets are base64-encoded, not encrypted. Storing them in Git exposes credentials to anyone with repository access. GitOps needs a way to store encrypted secrets that only the cluster can decrypt.
There are three primary approaches: encrypt the secret before committing (SealedSecrets, SOPS), store the secret externally and sync it at runtime (External Secrets Operator), or use a dedicated secrets manager (Vault).
SealedSecrets
SealedSecrets is a Kubernetes controller that encrypts secrets using a cluster-specific key. You encrypt secrets on your workstation, commit the encrypted version to Git, and the controller decrypts it inside the cluster.
$ kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
# Encrypt a secret
$ kubeseal --format yaml < secret.yaml > sealed-secret.yaml
# The sealed-secret.yaml is safe to commit to Git
$ cat sealed-secret.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: my-secret
spec:
encryptedData:
password: AgBy3i4OJSWK+PiTySYZZA9rO...kubeseal uses the cluster's public key to encrypt the secret. Only the SealedSecrets controller in the target cluster can decrypt it. The encrypted secret is safe to store in Git.
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-credentials
namespace: production
spec:
encryptedData:
username: AgBy3i4OJSWK+PiTySYZZA9rO...
password: AgC4P8b2ZqYv7k3x9m1N...
template:
metadata:
name: db-credentials
namespace: production
type: OpaqueThe SealedSecret looks like a regular Kubernetes secret but with encrypted values. The controller detects it, decrypts it, and creates a regular Secret in the same namespace.
Mozilla SOPS
SOPS (Secrets OPerationS) is a tool for encrypting files. It encrypts values in YAML, JSON, or ENV files while leaving keys and structure unencrypted. SOPS supports AWS KMS, GCP KMS, Azure Key Vault, and PGP.
$ sops --encrypt --in-place secrets.yaml
$ sops --decrypt secrets.yaml > decrypted.yaml
# Encrypt with AWS KMS
$ sops --encrypt --kms arn:aws:kms:us-east-1:123456789:key/id secrets.yamlSOPS encrypts values but leaves keys readable. This means you can see the structure of the secret (database, password) without seeing the actual values. The encrypted file is safe to commit.
database:
host: ENC[AES256_GCM,data:abc123...]
password: ENC[AES256_GCM,data:def456...]
sops:
kms:
- arn: arn:aws:kms:us-east-1:123456789:key/id
enc: ENC[AES256_GCM,data:ghi789...]
lastmodified: "2024-01-15T10:00:00Z"
version: 3.8.0The encrypted values are clearly marked with ENC[]. The sops metadata tracks which keys were used and when the file was last modified. SOPS can be integrated with Flux for automatic decryption during reconciliation.
External Secrets Operator
The External Secrets Operator syncs secrets from external managers (AWS Secrets Manager, Azure Key Vault, HashiCorp Vault, GCP Secret Manager) into Kubernetes. Secrets are never stored in Git — only the reference is.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: db-credentials
data:
- secretKey: password
remoteRef:
key: prod/database/credentials
property: passwordThe ExternalSecret references a secret in AWS Secrets Manager. The operator fetches it at the specified interval and creates a Kubernetes Secret. The secret is never stored in Git — only the reference is.
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-manager
namespace: production
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
secretRef:
accessKeyIDSecretRef:
name: aws-credentials
key: access-key-id
secretAccessKeySecretRef:
name: aws-credentials
key: secret-access-keyThe SecretStore configures how to connect to the external secrets manager. The auth section references a Kubernetes secret with AWS credentials. The ExternalSecret uses this store to fetch secrets.
Comparing Approaches
| Approach | Secret Storage | Rotation | Complexity |
|---|---|---|---|
| SealedSecrets | Encrypted in Git | Manual re-seal | Low |
| SOPS | Encrypted in Git | Manual re-encrypt | Medium |
| External Secrets | External manager | Automatic | Medium |
| Vault Provider | Vault server | Dynamic/automatic | High |
Best Practices
- Never store plaintext secrets in Git — even in private repositories.
- Use External Secrets Operator for automatic rotation.
- Rotate encryption keys periodically for SealedSecrets.
- Restrict Git repository access to authorized personnel only.
- Audit secret access through the secrets manager, not Git logs.
- Use namespace-scoped secrets to limit exposure.
Flux natively supports SOPS decryption. Developers encrypt secrets with SOPS, commit to Git, and Flux decrypts automatically during reconciliation. No controllers or CRDs needed inside the cluster.
SealedSecrets use a single encryption key per cluster. Anyone with access to the cluster can read the key and decrypt any secret. For zero-trust, use External Secrets Operator or Vault with short-lived credentials.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.