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:

name: myapp-prod

labels:
  app: myapp-prod

patches:
  # deployment
  - target:
      kind: Deployment
      name: myapp
    patches:
      - op: replace
        path: /spec/replicas
        value: 1

      - 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
  - target:
      kind: Service
      name: myapp
    patches:
      - op: add
        path: /spec/ports/0/nodePort
        value: 30266
Enter fullscreen mode Exit fullscreen mode

A patches/dev.yaml might look like:

name: myapp-dev

labels:
  app: myapp-dev

patches:
  # deployment
  - target:
      kind: Deployment
      name: myapp
    patches:
      - op: add
        path: /spec/template/spec/containers/0/env
        value:
          - name: RESTAPIAPP_VERSION
            value: dev
          - name: LOG_LEVEL
            value: debug

  # service
  - target:
      kind: Service
      name: myapp
    patches:
      - 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)