Sync Django Secrets Into Kubernetes With External Secrets Operator

Use External Secrets Operator to load Django secrets from AWS Secrets Manager, Google Secret Manager, Azure Key Vault, Vault, or Infisical into Kubernetes Secrets: configure a SecretStore with workload identity, map keys, set refresh intervals, and roll pods on change.

Problem statement

Your Django app on Kubernetes reads settings from a Kubernetes Secret, but creating that Secret by hand or committing it (even base64-encoded) to git is unsafe and drifts from the source of truth in your cloud's secrets manager. External Secrets Operator (ESO) solves this: you declare which external secrets a Kubernetes Secret should contain, and the operator fetches and refreshes them using the cluster's cloud identity.

ESO for DjangoSecrets managerAWS, GCP, Azure, VaultSecretStoreauth via identityExternalSecretkey mappingK8s SecretenvFrom in podsESO copies values from the manager into a Kubernetes Secret on a schedule.
  1. Install ESO with Helm: helm install external-secrets external-secrets/external-secrets -n external-secrets --create-namespace.
  2. Create a SecretStore (namespaced) or ClusterSecretStore for your provider, authenticated with workload identity (IRSA, GKE Workload Identity, Azure workload identity).
  3. Create an ExternalSecret that maps provider keys to Kubernetes Secret keys such as SECRET_KEY and DATABASE_URL.
  4. Reference the resulting Secret in the Django Deployment with envFrom or secretKeyRef.
  5. Set refreshInterval, and restart pods on change with a reloader or a checksum annotation.

Step-by-step solution

1. Install the operator

helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets \
  -n external-secrets --create-namespace

2. Create a SecretStore

AWS Secrets Manager with IRSA (EKS):

apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: aws
  namespace: prod
spec:
  provider:
    aws:
      service: SecretsManager
      region: eu-central-1
      auth:
        jwt:
          serviceAccountRef:
            name: shop-secrets-reader

The service account is annotated with an IAM role allowed to read shop/prod/* secrets. Other providers (gcpsm, azurekv, vault, infisical, doppler) use the same structure with their own auth blocks. Check the API version (v1 or v1beta1) your ESO release serves.

provider and authenticationProvider keyCluster authAWS Secrets ManagerawsIRSA / pod identityGoogle Secret ManagergcpsmWorkload IdentityAzure Key Vaultazurekvworkload identityHashiCorp VaultvaultKubernetes authEvery provider can authenticate without static credentialsdjango-deployment.com
Avoid putting cloud access keys into a Kubernetes Secret just to bootstrap ESO.

3. Declare the ExternalSecret

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: django-env
  namespace: prod
spec:
  refreshInterval: 1h
  secretStoreRef: { name: aws, kind: SecretStore }
  target:
    name: django-env
    creationPolicy: Owner
  data:
    - secretKey: SECRET_KEY
      remoteRef: { key: shop/prod/django, property: SECRET_KEY }
    - secretKey: DATABASE_URL
      remoteRef: { key: shop/prod/django, property: DATABASE_URL }

Or pull every property of a JSON secret with dataFrom: [{ extract: { key: shop/prod/django } }]. ESO creates and owns the django-env Secret.

4. Use it in the Deployment

envFrom:
  - secretRef:
      name: django-env

If the ExternalSecret has not synced yet, pods fail with CreateContainerConfigError. Check kubectl get externalsecret -n prod; the status should be SecretSynced.

5. Roll pods when secrets change

Environment variables are read at container start, so a refreshed Secret does not affect running pods. Use a controller such as Stakater Reloader (annotate the Deployment with reloader.stakater.com/auto: "true"), or include a checksum of the secret's version in a pod template annotation during deploys.

rotation flow1Value changed in managernew version2ESO refresh intervalfetches new value3Kubernetes Secret updatedsame name4Reloader rolls Deploymentnew pods start5Pods read new envold ones drainWithout a rollout, running pods keep the old environmentdjango-deployment.com
Use SECRET_KEY_FALLBACKS when rotating the Django secret key this way.

Explanation

ESO runs controllers that watch ExternalSecret resources, authenticate to the configured provider through a SecretStore, read the referenced values, and write them into a normal Kubernetes Secret. Applications, including Django, consume the Secret as usual. The external manager stays the source of truth, access is controlled by cloud IAM, and git contains only the mapping, never the values.

Edge cases / notes

  • Kubernetes Secret storage. Synced values still live in etcd; enable encryption at rest for Secrets in the cluster.
  • Many environments. Use one SecretStore per namespace with an identity scoped to that environment's secrets.
  • Templates. ESO's target.template can build values such as DATABASE_URL from several remote fields.

Next steps

FAQ

Why not commit Kubernetes Secrets to git?

Base64 is encoding, not encryption. Anyone with repository access can read them. ESO keeps values out of git.

Do pods update automatically when a secret changes?

The Secret updates, but environment variables in running pods do not. Roll the Deployment with a reloader.

Which secrets managers does ESO support?

AWS, Google Cloud, Azure, HashiCorp Vault, and many others, including Doppler and Infisical.

What if ESO cannot reach the provider?

The existing Kubernetes Secret stays as it was, so running pods continue; new syncs fail until access returns.

Keep going