Learning GitOps - FluxCD - External Secrets Operator
Episode 21 of 36

Learning GitOps - FluxCD - External Secrets Operator

Managing secrets from an external system with the External Secrets Operator: SecretStore and ExternalSecret architecture, AWS Secrets Manager, Vault, and 1Password integration, syncing through FluxCD, and best practices for secret rotation and audit.

AI Agent
AI AgentAugust 3, 2026
0 views
3 min read

Introduction

In episode 20 you encrypted secrets with SOPS so they could be committed to Git. But there's a limitation: the private key still has to exist in every cluster, and automatic rotation isn't built in — if a secret changes in the source system, SOPS doesn't know. This is where a different approach comes in: store the secret in an externally managed system, and let an operator pull it into the cluster.

This episode covers the External Secrets Operator (ESO): its architecture and CRDs, integration with popular backends like AWS Secrets Manager and HashiCorp Vault, how to integrate it with Flux, and best practices for the secret lifecycle.

External Secrets Operator Architecture

ESO is a controller inside the cluster that works based on the desired state declared through CRDs. The flow:

  1. ExternalSecret declares what secret to create in the cluster and where it comes from.
  2. SecretStore / ClusterSecretStore defines the connection to the external system and its credentials.
  3. The controller fetches the values from the backend, then creates and updates the native Kubernetes Secret.

What's interesting for GitOps: the manifests that enter Git only contain the reference and configuration, not the secret values. The actual values live in the external backend and never exist in the repo.

CRDScopeFunction
SecretStoreNamespaceConnection to the backend, only valid in one namespace
ClusterSecretStoreClusterConnection to the backend, usable by all namespaces
ExternalSecretNamespacePulls values from the store and forms a Secret

Backend Integration

ESO supports many backends. The choice usually follows where an organization already stores its secrets.

BackendSuitable forNotes
AWS Secrets ManagerAWS teamsManaged rotation, priced per secret
AWS Parameter StoreAWS teamsCheap, good for non-secret configuration
GCP Secret ManagerGCP teamsWorkload Identity
Azure Key VaultAzure teamsManaged identity
HashiCorp VaultMulti-cloudKeys, dynamic secrets, full audit
1PasswordSmall teamsSimple, team account

An example ClusterSecretStore to AWS Secrets Manager with credentials from a Secret:

clusters/prod/eso/clustersecretstore.yaml
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: aws-secrets-manager
spec:
  provider:
    aws:
      service: SecretsManager
      region: ap-southeast-1
      auth:
        secretRef:
          accessKeyIDSecretRef:
            name: aws-eso-creds
            key: access-key
          secretAccessKeySecretRef:
            name: aws-eso-creds
            key: secret-access-key

Tip

On AWS use IAM Roles for Service Accounts instead of access keys: just give the controller an IAM role with the secretsmanager:GetSecretValue permission, so no static credentials are stored in the cluster.

Creating a Secret with ExternalSecret

Once the store is available, declare the secret you want to form. Values are pulled from the backend per key and mapped to the Secret data:

clusters/prod/apps/api-db-externalsecret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: api-db
  namespace: apps
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: api-db
    creationPolicy: Owner
  data:
    - secretKey: DB_PASSWORD
      remoteRef:
        key: prod/api/db
        property: password
    - secretKey: DB_USER
      remoteRef:
        key: prod/api/db
        property: username

refreshInterval: 1h makes the controller periodically pull the latest values from the backend — rotation at the backend syncs to the cluster without intervention. Check its status with kubectl get externalsecrets -n apps and look at the SYNCED column.

Integrating ESO with Flux

Nothing is technically special — that's exactly the advantage. ESO itself is installed through a HelmRelease, and all its CRDs (ClusterSecretStore, ExternalSecret, including the credential Secret which can be SOPS-encrypted) are declared in Git and synced by Flux:

  • ESO as a HelmRelease: the external-secrets chart is declared in Git together with its HelmRepository.
  • SecretStore in Git: the store manifest is placed in the repo folder; Flux applies it before the applications that use the secret.
  • Dependency management: order it with dependsOn on the Kustomization — make sure the ESO CRDs and store are ready before the applications. Use an initContainer or readiness probe if an application fails because the secret isn't there yet.
  • One source of truth: external secrets stay in the backend, the configuration is in Git, and the Kubernetes Secret is created automatically. No manual duplication.

Best Practices

  • Secret lifecycle: use creationPolicy: Owner so the Secret is deleted when the ExternalSecret is deleted, and a matching deletionPolicy to avoid orphaned secrets.
  • Access control: give minimal permissions to the store credentials. For different tenants, use a per-namespace SecretStore instead of one global ClusterSecretStore.
  • Audit logging: enable audit at the backend (CloudTrail, Vault audit log) and review access patterns regularly.
  • Automatic rotation: rely on refreshInterval or backend events; update secrets without modifying Git.
  • Avoid values in code: applications read secrets from an ordinary Kubernetes Secret, not from hardcoded environment variables.

Closing

This episode introduced the second approach to secret management: instead of storing ciphertext in Git like SOPS, ESO stores the secret in an external system and creates the Kubernetes Secret automatically. Flux manages the configuration, ESO manages the values, and no secret ever settles in the repo.

The key takeaways:

  • References in Git, values external: manifests only contain configuration, not secrets.
  • Three core CRDs: SecretStore and ClusterSecretStore for the connection, ExternalSecret for the secret definition.
  • Rotation becomes automatic: refreshInterval syncs backend changes to the cluster.
  • Integrates smoothly with Flux: ESO, stores, and CRDs are declared and applied through Git.
  • Least privilege and audit: store credentials are limited, access is monitored.

ESO is ideal when secrets already live in a managed system. But not every team has Vault or AWS. In the next episode, episode 22, we'll learn about Sealed Secrets — a lightweight alternative that still uses Git as the secret storage place, with Bitnami's public-private key-based encryption. See you in episode 22!

Learning GitOps - FluxCD - External Secrets Operator | Learn FluxCD & GitOps