All posts
Muhtalip Dede profile photoMuhtalip Dede · Founder of kprompt

Kubernetes in CI/CD: gating cluster changes with plan JSON before apply

How to use kprompt PlanResult JSON in CI/CD pipelines to review Kubernetes scale, deploy, and rollback plans before apply — with jq gates, GitHub Actions patterns, and production safety rules.

CI/CD pipelines love deterministic steps: build, test, scan, deploy. Kubernetes breaks the fantasy the moment someone runs kubectl apply -f or helm upgrade from a GitHub Action without a human reading the diff. The pipeline goes green; the cluster goes red. Platform teams respond with policy engines, admission webhooks, and mandatory reviews — all necessary, all heavy.

kprompt adds a lighter pattern for natural-language or ticket-driven changes: emit a structured PlanResult as JSON, gate it in CI with jq or policy checks, then apply in a separate step only if the plan passes. This post shows how to wire that loop for Kubernetes scale, deploy, and rollback — without treating the LLM as an autorun root user.

Why plan-before-apply belongs in CI

Continuous delivery to Kubernetes should separate intent from execution. Intent might come from a PR label, a Slack slash command translated to a prompt, or an operator-maintained runbook string. Execution should be kubectl-compatible commands you can diff, log, and reject.

  • Plans are inspectable — actions, namespace, risk level, denied flag
  • JSON is machine-readable — jq, OPA, or custom gates without parsing shell text
  • Apply is optional and distinct — same prompt, second invocation with --approve
  • Secrets stay out of stdout — manifests and API keys are never in PlanResult
  • Human UI goes to stderr in JSON mode — logs stay clean for artifacts

PlanResult JSON in one minute

Run kprompt with --output json or -o json. stdout is a single PlanResult document (apiVersion kprompt.io/v1, kind PlanResult). Key fields for pipelines:

  • plan.intent — scale, deploy, rollback, get, explain, …
  • plan.actions — ordered ops (no raw YAML blobs)
  • risk.level — low / medium / high / denied
  • risk.denied — hard deny; pipeline should fail fast
  • applied — whether a mutation actually ran (false on plan-only runs)

Emit a plan

kprompt "scale api to 10" -n prod -o json > plan.json
cat plan.json | jq '.plan.actions, .risk'

Two-stage pipeline: gate, then apply

The safest default is two jobs or steps: (1) generate and validate JSON; (2) apply only on main branch or after manual approval, reusing the same prompt with --approve --wait. Never combine loose jq gates with --approve on production in the same unreviewed script.

Bash gate script

#!/usr/bin/env bash
set -euo pipefail

PROMPT='scale api to 10'
NS=prod

json="$(kprompt "$PROMPT" -n "$NS" -o json)"

# Hard deny — stop immediately
echo "$json" | jq -e '.risk.denied == false' >/dev/null

# Intent must match expectation
echo "$json" | jq -e '.plan.intent == "scale"' >/dev/null

# Reject high-risk in automated staging gates
echo "$json" | jq -e '.risk.level != "high"' >/dev/null

# Reject delete ops in this pipeline
echo "$json" | jq -e '[.plan.actions[].op] | index("delete") | not' >/dev/null

echo "$json" > "plan-$(date +%s).json"
echo "Plan passed gates."

Apply step (after gate + human approval)

# Same prompt — explicit approve + wait for rollout
kprompt "scale api to 10" -n prod --approve --wait --timeout 10m

GitHub Actions pattern

Store kubeconfig and LLM keys in GitHub Secrets. Use environment protection rules so the apply job requires reviewer approval. Plan job runs on every PR; apply job only on workflow_dispatch or merge to main.

Sketch workflow

# .github/workflows/kprompt-plan.yml
jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install kprompt
        run: curl -fsSL https://kprompt.ai/install | bash
      - name: Plan scale
        env:
          KPROMPT_GEMINI_API_KEY: ${{ secrets.KPROMPT_GEMINI_API_KEY }}
          KUBECONFIG: ${{ secrets.KUBECONFIG_STAGING }}
        run: |
          json=$(kprompt "scale api to 3" -n staging -o json)
          echo "$json" | jq -e '.risk.denied == false'
          echo "$json" | jq -e '.plan.intent == "scale"'
          echo "$json" > plan.json
      - uses: actions/upload-artifact@v4
        with:
          name: kprompt-plan
          path: plan.json

  apply:
    needs: plan
    if: github.ref == 'refs/heads/main'
    environment: staging-k8s   # required reviewers
    runs-on: ubuntu-latest
    steps:
      - name: Apply (approved)
        env:
          KPROMPT_GEMINI_API_KEY: ${{ secrets.KPROMPT_GEMINI_API_KEY }}
          KUBECONFIG: ${{ secrets.KUBECONFIG_STAGING }}
        run: |
          kprompt "scale api to 3" -n staging --approve --wait

Common jq gates for Kubernetes pipelines

  • .risk.denied == false — mandatory baseline
  • .plan.intent == "scale" — ticket says scale, plan must say scale
  • .risk.level != "high" — block auto-path for high blast radius
  • No delete op in plan.actions — read-only or scale-only pipelines
  • Namespace label in metadata — reject cross-namespace surprises (when exposed in schema)

Extra jq examples

# Fail if plan includes rollback (use dedicated workflow instead)
echo "$json" | jq -e '.plan.intent != "rollback"'

# Pretty-print for human review in CI logs
echo "$json" | jq '{ intent: .plan.intent, risk: .risk, actions: .plan.actions }'

Where this fits in GitOps

GitOps (Argo CD, Flux) keeps desired state in Git — CI builds images and updates manifests. kprompt is not a replacement for GitOps; it's complementary for operational prompts that don't belong in a repo: scale for a drill, rollback during an incident, explain why staging is red before you merge. Use PlanResult JSON to gate those operational paths the same way you gate manifest diffs.

  • GitOps — declarative desired state, PR review on YAML
  • kprompt CI — imperative day-2 ops with structured plan artifacts
  • Together — Git for steady state, gated prompts for break-glass and capacity

Anti-patterns to avoid

  • Single step with -o json --approve on production — skips human review entirely
  • Loose jq (only checking .applied) — doesn't validate intent or risk
  • Shared kubeconfig with cluster-admin in CI — scope ServiceAccounts per pipeline
  • Storing LLM or kube secrets in plan artifacts — PlanResult excludes them; keep artifacts clean
  • Assuming experimental CLI is production-hardened — start on staging, tune gates over time

Read-only checks in CI (no apply)

Not every pipeline step mutates. Use get, list, and explain in JSON mode for smoke tests after deploy — verify workloads exist, pods ready, no denied risk because reads don't mutate.

Post-deploy smoke

json=$(kprompt "list deployments" -n staging -o json)
echo "$json" | jq -e '.plan.intent == "get" or .plan.intent == "list"'
echo "$json" | jq '.result'

History and audit

Locally, kprompt history stores recent prompts and plan summaries in ~/.kprompt/history.jsonl — useful for correlating CI prompts with incident timelines. In CI, upload plan.json artifacts and retain them with your build logs. schemaVersion in PlanResult is stable at 1; bump-aware parsers keep pipelines working across CLI upgrades.

Try it on staging first

Install kprompt in a branch pipeline, emit JSON for a harmless list or describe prompt, then progress to scale on staging with two-stage gate + environment approval. Full schema and jq helpers: kprompt.ai/docs/ci.

Quick start

curl -fsSL https://kprompt.ai/install | bash
export KPROMPT_GEMINI_API_KEY="..."
kprompt "scale api to 2" -n staging -o json | jq .
kprompt "scale api to 2" -n staging --approve --wait