In the previous post we used Helm to package and manage our resources. Helm is great when you need templating and a rich release lifecycle, but it isn’t the only option. For many teams a lighter, template-free approach is enough — and that’s exactly what Kustomize gives us. In this post we’ll learn how to structure our manifests with Kustomize and deploy them with ArgoCD.

What is Kustomize?

Kustomize is a configuration management tool that lets us customize raw, template-free YAML files for multiple purposes, leaving the original files untouched. Instead of parameterizing everything with {{ }} like Helm, Kustomize takes a set of base manifests and applies overlays (patches) on top of them.

The key idea is base + overlays:

  • base — the common resources shared by every environment.
  • overlays — per-environment folders (dev, staging, prod) that patch the base.

The best part: Kustomize is built into kubectl, so there’s nothing extra to install.

kubectl kustomize ./overlays/prod        # render the manifests
kubectl apply -k ./overlays/prod          # render + apply

Structuring the Book Info app with Kustomize

Let’s restructure the Book Info application from the earlier posts into a base and two overlays — dev and prod.

book-info/
├── base/
│   ├── kustomization.yaml
│   ├── deployment.yaml
│   └── service.yaml
└── overlays/
    ├── dev/
    │   └── kustomization.yaml
    └── prod/
        ├── kustomization.yaml
        └── replicas-patch.yaml

The base/kustomization.yaml simply lists the resources that make up the app:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - deployment.yaml
  - service.yaml

commonLabels:
  app: productpage

A trimmed base/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: productpage
spec:
  replicas: 1
  selector:
    matchLabels:
      app: productpage
  template:
    metadata:
      labels:
        app: productpage
    spec:
      containers:
        - name: productpage
          image: docker.io/istio/examples-bookinfo-productpage-v1:1.20.3
          ports:
            - containerPort: 9080

Overlays

The dev overlay only needs to point at the base and tweak a couple of small things — here we add a dev- name prefix and a namespace:

# overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: book-info-dev
namePrefix: dev-

resources:
  - ../../base

The prod overlay reuses the same base but bumps the replica count with a strategic merge patch:

# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: book-info-prod
namePrefix: prod-

resources:
  - ../../base

patches:
  - path: replicas-patch.yaml
# overlays/prod/replicas-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: productpage
spec:
  replicas: 3

Notice how the base is never modified — each environment layers its own changes on top. Preview the rendered prod manifest to confirm the replica count is 3:

kubectl kustomize ./overlays/prod

Common Kustomize transformers

Beyond patches, Kustomize ships a set of handy transformers you’ll use every day:

  • namePrefix / nameSuffix — prefix or suffix every resource name.
  • namespace — place all resources into a namespace.
  • commonLabels / commonAnnotations — add labels/annotations to everything.
  • images — override an image name or tag without editing the deployment.
  • configMapGenerator / secretGenerator — generate ConfigMaps and Secrets from files or literals, with an automatic content hash suffix that triggers rolling updates.

For example, pinning the image tag per environment is as simple as:

# overlays/prod/kustomization.yaml
images:
  - name: docker.io/istio/examples-bookinfo-productpage-v1
    newTag: 1.20.3

And generating a ConfigMap from literals:

configMapGenerator:
  - name: productpage-config
    literals:
      - LOG_LEVEL=info
      - FEATURE_REVIEWS=true

Deploying an overlay with ArgoCD

ArgoCD has first-class support for Kustomize. When ArgoCD finds a kustomization.yaml in the path you point it to, it renders it automatically — no extra configuration needed. We just point each Application at the overlay folder we want.

An Application for the prod overlay:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: book-info-prod
  namespace: argocd
spec:
  project: default
  source:
    repoURL: 'https://github.com/VersusControl/devops-vn-blog'
    targetRevision: HEAD
    path: '_resource/argocd-series/06-kustomize/book-info/overlays/prod'
  destination:
    server: 'https://kubernetes.default.svc'
    namespace: book-info-prod
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

Run apply:

kubectl apply -f app-prod.yaml

Create a second Application pointing at overlays/dev and you now have two environments deployed from a single base, each managed through Git.

If you need to override a Kustomize parameter directly from the Application (handy for CI-driven image bumps), use the kustomize block in the source:

  source:
    repoURL: 'https://github.com/VersusControl/devops-vn-blog'
    targetRevision: HEAD
    path: '_resource/argocd-series/06-kustomize/book-info/overlays/prod'
    kustomize:
      images:
        - docker.io/istio/examples-bookinfo-productpage-v1:1.20.3

See 06-kustomize for the full example.

Helm or Kustomize?

Both are excellent, and ArgoCD supports each equally well:

  • Reach for Helm when you need templating, conditionals, and a package you can version and share (third-party charts like Redis, Postgres, etc.).
  • Reach for Kustomize when you want plain, reviewable YAML with small, declarative per-environment differences and zero templating language to learn.

Many teams even combine them — rendering a Helm chart and then applying Kustomize patches on top.

In the next post, we’ll learn about Resource Hooks — how to run tasks like database migrations at the right moment during a sync.