Learning GitOps - FluxCD - Variable Substitution & Configuration
Episode 11 of 36

Learning GitOps - FluxCD - Variable Substitution & Configuration

Configuration without duplication: variable substitution syntax, postBuild with substitute and substituteFrom, per-environment and multi-cluster configuration strategies, and SOPS and secret operator integration.

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

Introduction

In episode 10 you learned to control the deploy order. One problem remains: how do we keep a single set of manifests valid for many clusters and environments, without rewriting YAML? The answer is variable substitution.

This episode covers Flux variable syntax, postBuild with substitute and substituteFrom, configuration strategies, secret management integration, and its best practices.

Variable Substitution

Flux provides variables that can be used inside rendered manifests. The syntax: a dollar sign, then the variable name between curly braces. Variable values are defined through the postBuild block on the Kustomization. Example usage in a manifest:

Using variables in a manifest
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
  namespace: webapp
spec:
  replicas: ${replica_count}
  template:
    spec:
      containers:
        - name: webapp
          image: registry.internal.acme.com/webapp:${image_tag}
          env:
            - name: CLUSTER
              value: ${cluster_name}

The variable value sources:

SourceWhere it's definedHow it's used
InlinepostBuild.substituteDirectly on the Kustomization
ConfigMappostBuild.substituteFromPublic values from a ConfigMap
SecretpostBuild.substituteFromSecret values from a Secret

There are also built-in variables available automatically: CLUSTER_NAME contains the cluster name from the clusters/ directory used at bootstrap, plus other values you set during flux bootstrap --cluster-domain or in the substitute block.

Tip

Because values are replaced before they're applied, variables are most useful for values that change between environments: replica counts, image tags, namespace names, domains, and non-secret credentials. Don't use them to change the YAML structure.

Post-Build Variable Substitution

Variable replacement is enabled through the postBuild block on the Kustomization:

substitute and substituteFrom
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: webapp
  namespace: flux-system
spec:
  interval: 10m
  path: ./apps/webapp/overlays/prod
  prune: true
  sourceRef:
    kind: GitRepository
    name: fleet
  postBuild:
    substitute:
      replica_count: "4"
      image_tag: "1.2.0"
      cluster_name: "prod-eks-a"
    substituteFrom:
      - kind: ConfigMap
        name: platform-vars
      - kind: Secret
        name: app-secrets

How postBuild works:

  1. Kustomize renders the manifests (merge, patch, generator).
  2. Variables from substitute are replaced first.
  3. Variables from substituteFrom are replaced following the order of the list.
  4. The final result is applied to the cluster.

Warning

The Secret and ConfigMap for substituteFrom must be in the same namespace as the Kustomization (default flux-system). If a variable is defined in two sources, the one appearing last in the order wins.

Testing Substitution with Kubectl

While writing files, you can test the substitution result without a cluster using kubectl kustomize:

Render the kustomization locally
kubectl kustomize ./apps/webapp/overlays/prod

And to check the final result after Flux applies it:

See the rendered result in the cluster
kubectl get deployment webapp -n webapp -o yaml | grep replicas

Note

Substitution happens on the Flux side, not in the files. Files in the repo keep their placeholders, so always open the kubectl kustomize output to make sure the placeholders are correct before committing.

Configuration Strategies

Variables give freedom, but they need discipline. Four common strategies:

Environment-Specific

One overlays per environment (dev, staging, prod), each with its own substitute. This continues the overlays pattern from episode 7 — variables add flexibility without adding files.

Multi-Cluster

Use the same Kustomization for all clusters, differentiate values through a per-cluster platform-vars ConfigMap:

Per-cluster variable ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: platform-vars
  namespace: flux-system
data:
  region: ap-southeast-1
  domain: cluster-a.internal.acme.com

Different clusters simply choose different values in their own substitute or ConfigMap.

Tenant-Specific

For multi-tenancy, per-tenant variables are stored in the tenant namespace, and the tenant Kustomization uses substituteFrom with the ConfigMap in its namespace. Tenancy details are covered in episode 12.

Feature Flags

Variables can also serve as flags: a true or false value to enable a feature. Set the default value in base, then override it in specific overlays.

Feature flag via variable
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
  namespace: webapp
spec:
  template:
    spec:
      containers:
        - name: webapp
          env:
            - name: NEW_PAYMENT
              value: ${enable_new_payment}

Secret Management Integration

Variables aren't for real secrets. For that, Flux works together with the secret management ecosystem:

ToolHow it works with Flux
SOPS (Mozilla)Encrypts values inside manifest files; Flux decrypts them during apply using age or KMS
Sealed SecretsSecrets are encrypted into SealedSecrets in Git; the Bitnami controller decrypts them in the cluster
External Secrets OperatorPulls Secrets from AWS, GCP, Vault, or HashiCorp, then syncs them to the cluster
VaultESO or a sidecar injects secret values when the pod starts

Example of a Flux Kustomization with SOPS decryption:

Kustomization with SOPS decryption
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: webapp
  namespace: flux-system
spec:
  interval: 10m
  path: ./apps/webapp/overlays/prod
  prune: true
  sourceRef:
    kind: GitRepository
    name: fleet
  decryption:
    provider: sops
    secretRef:
      name: sops-age

Important

The core principle: secrets should never exist in plaintext in Git. SOPS encrypts files, Sealed Secrets encrypts Secrets, ESO moves values directly from the source. Pick one pattern and apply it consistently.

Best Practices

  • Minimal duplication — put shared values in one place, let overlays only override the delta.
  • Clear naming — use descriptive snake_case like image_tag, replica_count, ingress_domain; avoid generic names like value1.
  • Documentation — create a variable list per overlay, for example in a README.md or as comments in kustomization.yaml, so it's easy to track.
  • Validation — check for unsubstituted placeholders before deploying; Flux reports undefined variables as a reconciliation error.
Verify no variables are left
flux get kustomizations
kubectl get deployment webapp -n webapp -o jsonpath='{.spec.replicas}'

Tip

A pattern mature teams often use: the kustomization.yaml in clusters/<cluster>/ holds cluster-specific substitute, while substituteFrom stores shared values. The combination of both balances consistency and flexibility.

Closing

Configuration is now flexible without becoming messy:

  • Variable syntax separates values from manifest structure.
  • postBuild with substitute and substituteFrom manages value sources declaratively.
  • Environment-specific, multi-cluster, tenant, and feature flag strategies cover almost every need.
  • SOPS, Sealed Secrets, External Secrets Operator, and Vault keep secrets safe.
  • Best practices keep the repository readable and auditable.

You now have a complete foundation for single-cluster GitOps. In episode 12 we'll implement multi-tenancy with Flux: the tenancy model, flux create tenant, namespace isolation, and per-tenant service accounts. See you!

Learning GitOps - FluxCD - Variable Substitution & Configuration | Learn FluxCD & GitOps