A Unix-style pipe for filtering, mutating, and exploring streams of Kubernetes YAML manifests.
k8q reads YAML from stdin, applies transformations, and writes the result to stdout. It understands Kubernetes semantics — kind matching, API groups, workload pod templates — so you can manipulate manifests safely without breaking formatting or losing comments.
Output is automatically formatted: fields are reordered to follow Kubernetes conventions (apiVersion, kind, metadata, spec, status), and colorized when writing to a terminal.
Built on kustomize/kyaml for lossless AST-based YAML manipulation.
go install github.com/tobiash/k8q@latestFilters the stream to keep only manifests matching the given criteria. All provided criteria must match (AND semantics).
# Get all ConfigMaps
helm template my-chart | k8q get --kind ConfigMap
# Get a specific deployment by name
helm template my-chart | k8q get --kind Deployment --name my-app
# Filter by API group
helm template my-chart | k8q get --group apps
# Filter by label selector
helm template my-chart | k8q get -l app=webFilters the stream to remove manifests matching the given criteria. Manifests matching ANY provided criterion are dropped (OR semantics).
# Remove all Flux CD resources by API group
kustomize build . | k8q drop --group toolkit.fluxcd.io
# Remove resources by multiple criteria (OR)
helm template my-chart | k8q drop --group cert-manager.io --kind ConfigMap
# Use label selectors
kustomize build . | k8q drop -l 'tier in (frontend,backend)'Replaces ${VAR} references in the raw YAML stream using values from a .env file, before parsing.
# manifest.yaml contains ${DB_HOST} and ${DB_PORT}
cat manifest.yaml | k8q subst --env-file .env
# Works with multi-document streams
helm template my-chart | k8q subst --env-file production.envAdds a label to metadata.labels on matching manifests. For workload kinds (Deployment, DaemonSet, StatefulSet, Job), the label is also injected into spec.template.metadata.labels.
# Label everything
helm template my-chart | k8q label app.kubernetes.io/managed-by=k8q
# Label only Deployments
helm template my-chart | k8q label app=web --kind DeploymentAdds an annotation to metadata.annotations on matching manifests.
# Inject an annotation
kustomize build . | k8q annotate reloader.stakater.com/auto=true --kind DeploymentUpdates container images in matching manifests (Pods, Deployments, StatefulSets, DaemonSets, Jobs, CronJobs).
# Update a specific container image
helm template my-chart | k8q set-image my-app=my-registry.io/app:v2.0.0Sets metadata.namespace on matching manifests.
# Move resources from 'default' to 'production'
helm template my-chart | k8q set-namespace production --namespace defaultDeep-merges a YAML snippet into matching manifests.
# Add a nodeSelector to all StatefulSets
k8q patch 'spec: { template: { spec: { nodeSelector: { disk: ssd } } } }' --kind StatefulSetDeletes a field from matching manifests using a dot-separated path.
# Remove clusterIP from Services
k8q remove spec.clusterIP --kind ServiceUpdates spec.replicas for matching manifests.
# Scale down everything in a namespace
k8q scale 0 --namespace devModifies metadata.name by adding a prefix or suffix.
# Add a suffix to all resources
k8q rename --suffix "-v2"Compares two sets of Kubernetes manifests by identity (apiVersion + kind + namespace + name). Document order and known top-level/metadata field ordering are normalized. Nested mapping order, comments, and other YAML representation differences can still produce changes; this is not Kubernetes defaulting or API-version conversion.
Duplicate identities and invalid resources are errors, not silently overwritten entries. The Go diff API leaves its input nodes unchanged and orders results by full identity.
# Compare two files
k8q diff before.yaml after.yaml
# Pipe as "after", file as "before"
helm template my-chart | k8q diff --base before.yaml
# Summary mode
k8q diff before.yaml after.yaml --summaryExit codes: 0 = identical, 1 = differences found, 2+ = error.
Instead of piping YAML through stdin, you can read directly from a file:
k8q get --kind Deployment --file manifest.yaml
k8q diff before.yaml after.yamlAll commands that produce YAML or reports support --output json for machine-readable, agent-friendly output:
k8q get --kind ConfigMap --output json
k8q count --group-by-kind --output json
k8q diff before.yaml after.yaml --output json
k8q sum --output jsonJSON output follows Kubernetes API conventions:
- Lists are wrapped in a
v1/Listenvelope withapiVersion,kind, anditems - Resource references use
ObjectRef(apiVersion,kind,name,namespace) - Quantities are rendered as Kubernetes quantity strings (e.g.,
"200m","512Mi") - Empty resource/change collections are arrays, not
null dropandsubstproduce the samev1/Listenvelope as other manifest transforms- Failed
sumassertions retain totals andassertions.passed: falsein a single JSON result; invalid inputs instead produce a failure envelope
k8q uses semantic exit codes for reliable automation:
| Code | Meaning |
|---|---|
| 0 | Success (no differences for diff) |
| 1 | Differences found (diff) or failed resource assertions (sum) |
| 2 | Invalid input or an operational failure, including read/write errors |
JSON failures are emitted once, including argument-parsing errors. serve is an exception to the table: it propagates a completed child command's exit status.
The describe command emits a JSON description of the CLI for agent consumption:
k8q describeThis includes command names, positional arguments, local/global flags, types, defaults, required status, descriptions, idempotency, and side-effect metadata. Names and arguments come from the CLI parser. Stream transformations do not write back to source files; serve is side-effecting and can execute arbitrary code. Metadata describes behavior, not an authorization boundary.
Starts an in-process mock Kubernetes API server that serves piped-in manifests over HTTPS. Writes a kubeconfig pointing to the server, then optionally executes a command with KUBECONFIG set. This lets cluster scanning tools connect to k8q as if it were a real cluster.
# Run kubectl against piped manifests
helm template my-chart | k8q serve -- kubectl get deployments
# Run popeye against a Helm chart
helm template my-chart | k8q serve -- popeye
# Run kubent against a Kustomize build
kustomize build . | k8q serve -- kubent --cluster
# Run trivy against manifests
cat manifests.yaml | k8q serve -- trivy k8s --context k8q
# Interactive mode — prints kubeconfig path, waits for Ctrl+C
cat manifests.yaml | k8q serveNo cluster required. Pipe in manifests, run the tool, done.
Flags:
| Flag | Shorthand | Description |
|---|---|---|
--port |
-p |
Port to bind (default: random ephemeral) |
-- |
Separator; everything after is the command to exec |
k8q exits with the child command's exit code. In interactive mode, press Ctrl+C to stop.
Shutdown removes the temporary kubeconfig. On Unix, cancellation or completion of the main child also terminates descendants remaining in its process group; other platforms cancel the immediate child. Foreground commands can read the controlling terminal, and k8q restores terminal ownership afterward using a short-lived /bin/sh helper. Background invocations do not take foreground ownership.
This is not a sandbox: child commands inherit the environment and can access other credentials or networks. The mock API does not implement admission, reconciliation, scheduling, or a complete Kubernetes API surface.
Analyzers provide insights about the stream. They typically terminate the pipeline by printing a report instead of YAML.
Counts matching manifests.
# Count everything
k8q count
# Count only Deployments
k8q count --kind Deployment
# Group counts by kind
k8q count --group-by-kindEstimates CPU and Memory requests/limits for matching manifests (looking in ordinary containers in Pods and Pod templates). Accounts for spec.replicas, defaulting to one when absent. Invalid quantities, replicas, and assertion thresholds fail instead of producing a passing budget check. Quoted quantities are supported.
Workloads using metadata.generateName instead of metadata.name are supported. Their prefix is included in diagnostics, but is not treated as a resource name for matching. Unlike aggregation, diff still requires a concrete resource name.
These totals are not scheduler-equivalent: init-container requirements, Pod overhead, and DaemonSet node counts are not modeled.
# Sum resources for all workloads
k8q sum
# Sum resources for a specific namespace
k8q sum --namespace production
# Fail if any container is missing requests or limits
k8q sum --require-requests --require-limits
# Assert total resources are within bounds (exits 1 if exceeded)
k8q sum --max-cpu-requests 2000m --max-mem-limits 10GiBoth filtering (get, drop) and mutation commands support the same matching filters:
| Flag | Shorthand | Description |
|---|---|---|
--kind |
Kubernetes Kind (case-insensitive) | |
--name |
Resource name (exact) | |
--namespace |
-n |
Resource namespace (exact) |
--group |
-g |
API group (substring match) |
--selector |
-l |
Kubernetes label selector |
| (positional) | kind, kind/name, or api-group |
For get, label, annotate, etc., multiple criteria are combined with AND.
For drop, multiple criteria are combined with OR.
Matching is optional for mutators (they match everything by default) but required for filters.
get and drop support Kubernetes-style label selectors via the -l / --selector flag. The syntax matches kubectl:
Commands compose naturally through pipes:
# Get a deployment, labeled, in the right namespace
helm template my-chart \
| k8q get --kind Deployment --name my-app \
| k8q label app.kubernetes.io/managed-by=k8q \
| k8q set-namespace production
# Strip Flux resources, substitute env vars, and apply
kustomize build . \
| k8q drop --group toolkit.fluxcd.io \
| k8q subst --env-file .secrets \
| kubectl apply -f -yq is a general-purpose YAML processor. k8q is purpose-built for Kubernetes manifests:
- Kind-aware filtering --
k8q get --kind Deploymentknows what a Deployment is. - Label selectors --
-l app=web,env!=stagingwith full Kubernetes selector syntax. - Workload-aware labeling -- automatically propagates labels to pod templates.
- API group matching --
k8q drop --group toolkit.fluxcd.iofilters by group, not string matching. - Canonical field ordering -- output is automatically sorted: apiVersion, kind, metadata, spec, status.
- Colorized output -- syntax-highlighted YAML in the terminal, disabled when piped.
- Formatting preservation -- uses kyaml's AST, so comments survive.
| Component | Library |
|---|---|
| CLI framework | alecthomas/kong |
| YAML engine | kustomize/kyaml |
| API server | net/http + k8s.io/apimachinery (zero additional dependencies) |
| Env substitution | drone/envsubst |
To enable shell completion, add the following to your shell profile (.bashrc or .zshrc):
Bash:
source <(k8q completion -c bash)Zsh:
source <(k8q completion -c zsh)See LICENSE.