Meet kubepatch — a simple tool for deploying Kubernetes manifests using a patch-based approach. kubepatch kubepatch Unlike tools that embed logic into YAML or require custom template languages, kubepatch keeps your base manifests clean and idiomatic. kubepatch 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 Simple: No templates, DSLs, or logic in YAML, zero magic Simple Predictable: No string substitutions or regex hacks Predictable Safe: Only native Kubernetes YAML manifests - readable, valid, untouched Safe Layered: Patch logic is externalized and explicit via JSON Patch (RFC 6902) Layered Declarative: Cross-environment deployment with predictable, understandable changes Declarative 🛠 Example Given a base set of manifests for deploy a basic microservicesee examples see examples 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" --- 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" A patches/prod.yamlmight look like: patches/prod.yaml 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 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 A patches/dev.yamlmight look like: patches/dev.yaml 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 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 Apply the appropriate patch set based on the target environment. kubepatch patch -f base/ -p patches/dev.yaml | kubectl apply -f - kubepatch patch -f base/ -p patches/dev.yaml | kubectl apply -f - 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 --- 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 Installation Manual Installation Download the latest binary for your platform from the Releases page. Place the binary in your system's PATH (e.g., /usr/local/bin). Download the latest binary for your platform from the Releases page. Releases page Releases page Place the binary in your system's PATH (e.g., /usr/local/bin). PATH /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 ) ( 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 ) 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 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 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 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 ✨ Key Features JSON Patch Only Patches are applied using JSON Patch: JSON Patch JSON Patch - op: replace path: /spec/replicas value: 1 - op: replace path: /spec/replicas value: 1 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. Thisensures: Easy editing Compatibility with other tools Clean Git diffs Easy editing Compatibility with other tools Clean Git diffs Cross-Environment Deploys Deploy to dev, staging, or prodjust by selecting the right set of patches. All logic lives in patch files, notyour base manifests. dev staging prod Common Labels Support Inject common labels (like env, team, app), including deep paths like pod templates and selectors. env team app 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} - op: add path: /spec/template/spec/containers/0/env value: - name: PGPASSWORD value: ${IAM_SERVICE_PGPASS} 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! open an issue open an issue