Secrets Management

Overview

GitOps requires secrets to be declarative without storing plaintext credentials in Git. Choose one approach and use it consistently.

Option Best fit Trade-off
SOPS + age Small self-hosted cluster Simple and Git-native; protect the age private key separately
External Secrets Operator Existing Vault/cloud secret manager Central rotation; depends on an external backend
Sealed Secrets Kubernetes-only workflow Easy to adopt; ciphertext is tied to the controller key

For one VPS, SOPS + age has the smallest operational footprint. However, Argo CD needs a securely managed decryption plugin. Use External Secrets instead when a secret backend already exists.

External Secrets Pattern

Install External Secrets Operator as a platform Application, configure one SecretStore or ClusterSecretStore, then commit only references:

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: example-api
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: production
    kind: ClusterSecretStore
  target:
    name: example-api
  data:
    - secretKey: DATABASE_URL
      remoteRef:
        key: example-api/database-url

Verification

kubectl get externalsecret,secretstore,clustersecretstore -A
kubectl describe externalsecret example-api

Use separate application credentials, rotate bootstrap passwords, and prevent secrets from appearing in Helm values, shell history, Argo CD parameters, or logs.