DEV Community

Cover image for Patch-based, environment-aware Kubernetes deployments using plain YAML and zero templating
alexey.zh
alexey.zh

Posted on

Patch-based, environment-aware Kubernetes deployments using plain YAML and zero templating

Meet kubepatch — a simple tool for deploying Kubernetes manifests using a patch-based approach.

Unlike tools that embed logic into YAML or require custom template languages, kubepatch keeps your base manifests clean and idiomatic.

  • Simple: No templates, DSLs, or logic in YAML, zero magic
  • Predictable: No string substitutions or regex hacks
  • Safe: Only native Kubernetes YAML manifests - readable, valid, untouched
  • Layered: Patch logic is externalized and explicit via JSON Patch (RFC 6902)
  • Declarative: Cross-environment deployment with predictable, understandable changes

🛠 Example

Given a base set of manifests for deploy a basic microservice
see examples

--- apiVersion: v1 kind: Service metadata: name: myapp labels: app: myapp spec: type: NodePort selector: app: myapp ports: - protocol: TCP port: 8080 targetPort: 8080 --- apiVersion: apps/v1 kind: Deployment metadata: name: myapp labels: app: myapp spec: replicas: 2 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: myapp image: "localhost:5000/restapiapp:latest" 
Enter fullscreen mode Exit fullscreen mode

A patches/prod.yaml might look like:

myapp-prod: deployment/myapp: - op: replace path: /spec/replicas value: 2 - op: replace path: /spec/template/spec/containers/0/image value: "localhost:5000/restapiapp:1.21" - op: add path: /spec/template/spec/containers/0/env value: - name: RESTAPIAPP_VERSION value: prod - name: LOG_LEVEL value: info - op: add path: /spec/template/spec/containers/0/resources value: limits: cpu: "500m" memory: "512Mi" requests: cpu: "64m" memory: "128Mi" service/myapp: - op: add path: /spec/ports/0/nodePort value: 30266 
Enter fullscreen mode Exit fullscreen mode

A patches/dev.yaml might look like:

myapp-dev: deployment/myapp: - op: replace path: /spec/template/spec/containers/0/image value: "localhost:5000/restapiapp:1.22" - op: add path: /spec/template/spec/containers/0/env value: - name: RESTAPIAPP_VERSION value: dev - name: LOG_LEVEL value: debug service/myapp: - op: add path: /spec/ports/0/nodePort value: 30265 
Enter fullscreen mode Exit fullscreen mode

Apply the appropriate patch set based on the target environment.

kubepatch patch -f base/ -p patches/dev.yaml | kubectl apply -f - 
Enter fullscreen mode Exit fullscreen mode

Rendered manifest may look like this (note that all labels are set, as well as all patches are applied)

--- apiVersion: v1 kind: Service metadata: labels: app: myapp-dev name: myapp-dev spec: ports: - nodePort: 30265 port: 8080 protocol: TCP targetPort: 8080 selector: app: myapp-dev type: NodePort --- apiVersion: apps/v1 kind: Deployment metadata: labels: app: myapp-dev name: myapp-dev spec: replicas: 1 selector: matchLabels: app: myapp-dev template: metadata: labels: app: myapp-dev spec: containers: - env: - name: RESTAPIAPP_VERSION value: dev - name: LOG_LEVEL value: debug image: localhost:5000/restapiapp:1.22 name: myapp 
Enter fullscreen mode Exit fullscreen mode

Installation

Manual Installation

  1. Download the latest binary for your platform from the Releases page.
  2. Place the binary in your system's PATH (e.g., /usr/local/bin).

Installation script

( set -euo pipefail OS="$(uname | tr '[:upper:]' '[:lower:]')" ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/\(arm\)\(64\)\?.*/\1\2/' -e 's/aarch64$/arm64/')" TAG="$(curl -s https://api.github.com/repos/kubepatch/kubepatch/releases/latest | jq -r .tag_name)" curl -L "https://github.com/kubepatch/kubepatch/releases/download/${TAG}/kubepatch_${TAG}_${OS}_${ARCH}.tar.gz" | tar -xzf - -C /usr/local/bin && \ chmod +x /usr/local/bin/kubepatch ) 
Enter fullscreen mode Exit fullscreen mode

Package-Based installation (suitable in CI/CD)

Debian

sudo apt update -y && sudo apt install -y curl curl -LO https://github.com/kubepatch/kubepatch/releases/latest/download/kubepatch_linux_amd64.deb sudo dpkg -i kubepatch_linux_amd64.deb 
Enter fullscreen mode Exit fullscreen mode

Alpine Linux

apk update && apk add --no-cache bash curl curl -LO https://github.com/kubepatch/kubepatch/releases/latest/download/kubepatch_linux_amd64.apk apk add kubepatch_linux_amd64.apk --allow-untrusted 
Enter fullscreen mode Exit fullscreen mode

✨ Key Features

JSON Patch Only

Patches are applied using JSON Patch:

- op: replace path: /spec/replicas value: 1 
Enter fullscreen mode Exit fullscreen mode

Every patch is minimal, explicit, and easy to understand. No string manipulation or text templating involved.

Plain Kubernetes YAML Manifests

Your base manifests are 100% pure Kubernetes objects - no logic, no annotations, no overrides, no preprocessing. This
ensures:

  • Easy editing
  • Compatibility with other tools
  • Clean Git diffs

Cross-Environment Deploys

Deploy to dev, staging, or prod just by selecting the right set of patches. All logic lives in patch files, not
your base manifests.

Common Labels Support

Inject common labels (like env, team, app), including deep paths like pod templates and selectors.

Env Var Substitution (in Patch Values Only)

You can inject secrets and configuration values directly into patch files:

- op: add path: /spec/template/spec/containers/0/env value: - name: PGPASSWORD value: ${IAM_SERVICE_PGPASS} 
Enter fullscreen mode Exit fullscreen mode

Strict env-var substitution (prefix-based) is only allowed inside patches - never in base manifests.

Feedback

Have a feature request or issue? Feel free to open an issue or submit a PR!

Top comments (0)