SOPS-encrypted secrets don’t actually get decrypted by Flux; Flux enables other tools to decrypt them, and it’s usually a Kubernetes controller watching for changes.

Let’s see SOPS and Flux play together. Imagine you’ve got a Secret object in Kubernetes that you want to keep encrypted in Git. You’re using Mozilla SOPS for that, and Flux for GitOps.

Here’s a typical secret.yaml you’d commit:

apiVersion: v1
kind: Secret
metadata:
  name: my-encrypted-secret
  namespace: default
stringData:
  api-key: ENC[PKCS7,MIIF...etc.]
  password: ENC[PKCS7,MIIB...etc.]
type: Opaque

When Flux synchronizes this from your Git repository, it doesn’t magically know how to decrypt ENC[PKCS7,...]. That’s where the SOPS integration comes in. Flux itself doesn’t decrypt. Instead, it relies on a component that does know how to decrypt SOPS-encrypted files.

The most common way this happens is with the Flux KustomizeController (or its predecessor, the SourceController and KustomizeController in older Flux versions). When Flux is configured to manage a Kustomization resource that points to a directory containing SOPS-encrypted files, it needs a way to decrypt them before applying them to the cluster.

Here’s how the mental model builds:

  1. Git Repo: You commit SOPS-encrypted manifests (like the secret.yaml above) to your Git repository.
  2. Flux Synchronization: Flux’s SourceController fetches the latest Git commit.
  3. Kustomization Application: Flux’s KustomizeController processes the fetched manifests. If it encounters a SOPS-encrypted file (identified by the ENC[...] markers), it needs to decrypt it.
  4. Decryption Mechanism: For this to work, the Flux controller (running inside your cluster) must have access to the decryption keys. This is the crucial part. SOPS supports various backends: AWS KMS, GCP KMS, Azure Key Vault, age, PGP. The Flux controller needs to be configured with credentials or access to whichever backend you’re using.
  5. Decrypted Secret Creation: Once decrypted, the api-key and password fields are no longer ENC[...]. The KustomizeController then creates or updates the actual Kubernetes Secret object in your cluster with the plain text values.

Let’s look at a Flux Kustomization resource that might manage this:

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: my-app-config
  namespace: flux-system
spec:
  interval: 5m
  path: ./clusters/my-cluster/apps/my-app # Path to your Git repo where secret.yaml lives
  prune: true
  sourceRef:
    kind: GitRepository
    name: my-git-repo
  decryption:
    # This tells Flux to use SOPS decryption
    provider: sops
    # If using KMS, you might need to specify the region
    # sops:
    #   kms:
    #     - arn:aws:kms:us-west-2:111122223333:key/your-kms-key-id
    #     - arn:aws:kms:us-east-1:444455556666:key/another-kms-key-id
    # Or if using age:
    #   age:
    #     privateKey: |
    #       -----BEGIN AGE ENCRYPTED FILE-----
    #       ...your age private key...
    #       -----END AGE ENCRYPTED FILE-----

The decryption block is where Flux is told how to handle encrypted files. The provider: sops line is the key indicator. Flux then looks at the contents of the file. If it sees ENC[...], it knows to invoke SOPS logic.

The actual decryption happens within the Flux controller’s process. When the KustomizeController is about to apply a Kustomization, it intercepts files. If a file is identified as SOPS-encrypted, it invokes the sops binary (or its library equivalent) with the appropriate decryption configuration.

For example, if you’re using AWS KMS, the Flux controller needs IAM permissions to call the KMS API. If your Flux controller runs on an EC2 instance, it might assume an IAM role. If it’s in EKS, it might use IAM roles for service accounts (IRSA). The controller effectively runs a command like sops --decrypt --kms <kms-key-arn> secret.yaml in a temporary environment where it has the necessary cloud credentials.

This means the Secret object itself, as it’s being constructed by Flux for Kubernetes, will contain the plain text values.

# This is what gets applied to the cluster *after* Flux decrypts it
apiVersion: v1
kind: Secret
metadata:
  name: my-encrypted-secret
  namespace: default
data:
  api-key: bXlhcGlrZXk= # Base64 encoded plain text
  password: cGFzc3dvcmQ= # Base64 encoded plain text
type: Opaque

Notice stringData is gone, replaced by data with base64 encoded values, as is standard for Kubernetes Secrets.

The one thing most people don’t realize is that Flux doesn’t manage the SOPS keys directly. Flux is configured with access to the decryption mechanism (KMS ARN, PGP key, age private key). The actual SOPS keys (e.g., the KMS key ID, the PGP private key) are managed outside of Flux, in their respective services (AWS KMS, your GPG keyring, etc.). Flux just needs the credentials or permissions to use them.

The next hurdle you’ll likely face is ensuring the Flux controller has the correct permissions to access your KMS key or decrypt using your chosen SOPS backend.

Want structured learning?

Take the full Flux course →