Skip to content

akuity/rendered-manifest-pattern

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

19 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ—‚οΈ Rendered Manifest Pattern

Stop storing abstractions. Start storing truth.

A reference implementation of the Rendered Manifests Pattern β€” a GitOps strategy that eliminates the gap between what you store in Git and what actually runs in your Kubernetes cluster.

Argo CD Kustomize Kargo


The Problem

Popular GitOps engines like Argo CD and Flux support Helm and Kustomize natively β€” you store a chart reference or overlay in Git, and the engine renders it at apply time. This feels convenient, but it creates a critical blind spot:

  • Upgrading a Helm chart or changing a single value can cascade into dozens of manifest changes that are invisible until they hit your cluster.
  • PR reviewers can't see the actual diff β€” only the abstraction that produces the diff.
  • The "source of truth" in Git isn't the truth at all β€” it's a recipe.
  Git (abstractions)        Cluster (rendered reality)
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  chart: v1.2 β”‚  ──??──► β”‚  Deployment, Service,    β”‚
  β”‚  values.yaml β”‚          β”‚  ConfigMap, RBAC, ...    β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         ↑
   What reviewers see       What actually changes

The Solution

The Rendered Manifests Pattern ensures Git always contains fully-hydrated, plain YAML manifests β€” the same YAML that kubectl apply will use. Helm and Kustomize are still used, but only in a CI rendering step before anything reaches Git.

  main branch               env/dev branch           Cluster
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  CI/CD   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  GitOps  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  Kustomize   β”‚ ──────►  β”‚  deployment.yaml     β”‚ ───────► β”‚ Kubernetes β”‚
  β”‚  overlays    β”‚  render  β”‚  service.yaml        β”‚          β”‚            β”‚
  β”‚  values.yaml β”‚          β”‚  configmap.yaml      β”‚          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                   ↑
                       Reviewers see exact YAML diffs

Every change to deployed resources is visible as a plain YAML diff in Git β€” no reasoning required about how an overlay or values change cascades through a chart.


Repository Structure

.
β”œβ”€β”€ main                    # Source branch β€” Kustomize overlays & config
β”‚   β”œβ”€β”€ base/               # Shared base manifests
β”‚   β”œβ”€β”€ overlays/
β”‚   β”‚   β”œβ”€β”€ dev/            # Dev environment overlays
β”‚   β”‚   β”œβ”€β”€ staging/        # Staging environment overlays
β”‚   β”‚   └── prod/           # Production environment overlays
β”‚   └── .github/workflows/  # CI pipeline for rendering
β”‚
β”œβ”€β”€ env/dev                 # Rendered branch β€” plain YAML for dev
β”œβ”€β”€ env/staging             # Rendered branch β€” plain YAML for staging
└── env/prod                # Rendered branch β€” plain YAML for prod

Source configuration lives on main. Rendered manifests are committed to environment-specific branches by CI β€” never by hand.


How It Works

1. Developer opens a PR against main
         β”‚
         β–Ό
2. CI renders manifests (kustomize build / helm template)
         β”‚
         β–Ό
3. Rendered YAML diff is visible in the PR β€” reviewers see exactly
   what will change in the cluster
         β”‚
         β–Ό
4. PR is merged to main
         β”‚
         β–Ό
5. CI commits rendered manifests to env/* branches
         β”‚
         β–Ό
6. Argo CD detects the change in env/* and syncs the cluster

The rendering step is fully automated. Developers never manually touch the rendered branches.


Benefits

Concern Without Rendered Manifests With Rendered Manifests
PR visibility Reviewers see overlays/values, not the actual diff Reviewers see exact YAML changes
Audit log Git history reflects config abstractions Git history is a full audit trail of every deployed resource
Tooling flexibility GitOps engine must support your tool Any tool that outputs YAML works (Helm, Kustomize, Timoni, CUE…)
Drift detection Engine re-renders at sync time Git is the immutable source of truth β€” no runtime surprises
Security Mutations can happen at apply time What's in Git is exactly what runs

Getting Started

Prerequisites

  • kubectl configured against your target cluster
  • kustomize CLI (or helm for Helm-based setups)
  • A Kubernetes GitOps platform: Argo CD or Flux
  • (Optional) Kargo for progressive multi-stage promotion

Fork & Personalize

# 1. Fork this repository, then clone your fork
git clone https://github.com/<your-username>/rendered-manifest-pattern.git
cd rendered-manifest-pattern

# 2. Run the personalization script
./personalize.sh <your-github-username>

# 3. Commit and push
git add -A
git commit -m "chore: personalize manifests"
git push

Connect Argo CD

Point your Argo CD Application resources at the rendered branches, not main:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: guestbook-dev
spec:
  source:
    repoURL: https://github.com/<your-username>/rendered-manifest-pattern
    targetRevision: env/dev          # ← rendered branch, not main
    path: .
  destination:
    server: https://kubernetes.default.svc
    namespace: guestbook-dev

Trigger a Render

Push a change to main to kick off the CI rendering pipeline. GitHub Actions will render the Kustomize overlays and commit plain YAML to each env/* branch automatically.


CI Pipeline

The rendering workflow in .github/workflows/render.yaml runs on every push to main:

jobs:
  render:
    strategy:
      matrix:
        env: [dev, staging, prod]
    steps:
      - name: Render manifests
        run: kustomize build overlays/${{ matrix.env }} > /tmp/rendered.yaml

      - name: Commit to env branch
        run: |
          git switch env/${{ matrix.env }}
          cp /tmp/rendered.yaml .
          git commit -am "render: update from $(git rev-parse --short HEAD)"
          git push

Using Kargo for Progressive Promotion

This pattern pairs naturally with Kargo for controlled, multi-stage promotion. Instead of CI pushing directly to all environment branches simultaneously, Kargo provides a pipeline that gates promotion based on health checks and approvals:

main ──render──► env/dev ──approve──► env/staging ──approve──► env/prod

See the companion kargo-rendered-branches repository for a full Kargo-based example.


Tradeoffs

This pattern is a deliberate tradeoff. Be aware of the following:

Things that change:

  • Helm lifecycle hooks (e.g., pre-install, post-upgrade) are not available because Helm isn't run at apply time. Argo CD sync waves or hooks can often fill this role.
  • Kubernetes Secrets should not be stored as rendered YAML. Use External Secrets Operator or a similar runtime injection approach.
  • Rendered branches accumulate YAML files over time, which can increase repository size and diff noise in long-running projects.

Things that get much better:

  • Every infrastructure change becomes a reviewable, auditable Git commit.
  • Cluster state is always a direct reflection of what's in a Git branch β€” no templating engine stands between them.
  • Any manifest-generating tool can be used without needing GitOps engine support.

Related Projects & Resources


Contributing

Contributions, issues, and feature requests are welcome. Please open an issue to discuss significant changes before submitting a pull request.


License

Apache 2.0 β€” see LICENSE for details.

About

Demonstration for Rendered Manifest Pattern in Argo CD

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors