Skip to content

Notes13-kubectl and JSONPath

kubectl and JSONPath

How to ask the API for exactly one field: JSONPath, custom columns, sort, label and field selectors, and kubectl explain.

Updated Oct 1, 2026

kubectl and JSONPath Quick Notes





Why JSONPath

You often need one buried field: a Pod IP, a node kubelet version, a Secret key, the reason a container died. -o yaml prints the whole object. -o jsonpath and -o custom-columns print only the field.

Everything kubectl prints with -o json is the object you are walking. kubectl get pod nginx -o json | less once is the map. The path uses the same keys.


Output modes

Flag

Result

-o wide

Extra columns the resource already defines (node, IP)

-o yaml / -o json

Full object

-o name

pod/nginx, useful in a loop

-o jsonpath='…'

One expression

-o custom-columns=…

A table you define

-o custom-columns-file=cols.txt

The same table, from a file

--sort-by=.metadata.creationTimestamp

Ordered list. The path is JSONPath without {} in some versions; with a leading dot is the form below

JSONPath shape

  • {.field} one object. kubectl get pod nginx -o jsonpath='{.status.podIP}'
  • {.items[*].field} a list call (get pods). items is the collection.
  • {range …}{end} walks a list and lets you print separators.
  • {"\n"} and {"\t"} are literal strings inside the expression.
  • Quotes: the whole jsonpath is in single quotes so the shell does not eat $, {, or \.



bash
kubectl get pod nginx -o jsonpath='{.status.podIP}{"\n"}'

kubectl get pods -o jsonpath='{.items[*].metadata.name}{"\n"}'

kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.phase}{"\n"}{end}'

kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.nodeInfo.kubeletVersion}{"\n"}{end}'


Nested containers:

kubectl get pod nginx -o jsonpath='{range .spec.containers[*]}{.name}{" "}{.image}{"\n"}{end}'

A missing key prints nothing. It does not error. If the output is blank, the path is wrong or the field is unset. Compare with -o json.


Filters inside the path


JSONPath can keep only matching elements:

kubectl get pods -o jsonpath='{.items[?(@.status.phase=="Running")].metadata.name}{"\n"}'

?() is a filter. @ is the current element. This is fussier than --field-selector and it is evaluated by kubectl on the objects already returned. Prefer a field selector when one exists (it is sent to the API). Use the filter when the test is on a nested field the API cannot select, such as an image name.


custom-columns

bash
kubectl get pods -o custom-columns=NAME:.metadata.name,STATUS:.status.phase,IP:.status.podIP

kubectl get nodes -o custom-columns=NAME:.metadata.name,OS:.status.nodeInfo.operatingSystem,KUBELET:.status.nodeInfo.kubeletVersion


  • Header and path are separated by a colon.
  • No { } around the path.
  • NAME is any label you want printed.

A file form, when the columns should be reused:


bash
NAME          STATUS
metadata.name status.phase

kubectl get pods -o custom-columns-file=./cols.txt


The first row is headers. The next row is paths without a leading dot and without braces. The -o custom-columns= flag form is the one that uses .metadata.name.


Sort


bash
kubectl get pods --sort-by=.metadata.creationTimestamp
kubectl get pv --sort-by=.spec.capacity.storage
kubectl get nodes --sort-by=.status.nodeInfo.kubeletVersion


--sort-by takes a path with a leading dot. Sort is client-side. Combine it with -o custom-columns when you need a ranked table.


Selectors that hit the API

Labels (-l):


bash
kubectl get pods -l app=nginx
kubectl get pods -l 'env in (prod,stage)'
kubectl get pods -l app=nginx,tier!=cache
kubectl get pods --show-labels


Fields (--field-selector): only fields the apiserver indexes for that resource.


bash
kubectl get pods --field-selector status.phase=Running
kubectl get pods --field-selector spec.nodeName=node01
kubectl get pods -A --field-selector spec.nodeName=node01
kubectl get events --field-selector involvedObject.name=nginx
kubectl get pv --field-selector status.phase=Available


A field selector on a non-indexed field returns an error from the API, not an empty list. Fall back to jsonpath when that happens.

Namespace is not a field selector. It is -n or --all-namespaces / -A.


Explain instead of guessing the key


bash
kubectl explain pod
kubectl explain pod.spec.containers
kubectl explain deployment.spec.strategy.rollingUpdate
kubectl explain networkpolicy.spec.ingress.from --api-version=networking.k8s.io/v1
kubectl explain csr.spec --api-version=certificates.k8s.io/v1


kubectl explain reads the OpenAPI schema from the apiserver you are connected to. The field descriptions match this cluster’s version. Recursive dump: kubectl explain pod.spec --recursive.


Dry-run skeletons


bash
kubectl create deployment web --image=nginx --dry-run=client -o yaml
kubectl create service clusterip web --tcp=80:80 --dry-run=client -o yaml
kubectl create role pod-reader --verb=get,list --resource=pods --dry-run=client -o yaml
kubectl create rolebinding read --role=pod-reader --user=alice --dry-run=client -o yaml
kubectl create clusterrole node-reader --verb=get,list --resource=nodes --dry-run=client -o yaml


--dry-run=client does not talk to admission. It will happily emit a YAML the apiserver might still reject. Apply it, then kubectl describe if it fails.


Useful one-liners

bash
# every Pod IP in a namespace
kubectl get pods -n shop -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.podIP}{"\n"}{end}'

# which node runs which Pod
kubectl get pods -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,NODE:.spec.nodeName,STATUS:.status.phase

# image of every container
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{"\n"}{range .spec.containers[*]}{"  "}{.image}{"\n"}{end}{end}'

# decode a Secret key
kubectl get secret db -n shop -o jsonpath='{.data.password}' | base64 -d; echo

# nodes that are not Ready
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.conditions[?(@.type=="Ready")].status}{"\n"}{end}'


Secret data values are base64. jsonpath prints the encoded string. base64 -d is the decode step. stringData never appears on a retrieved object; the apiserver stores data.


Loops when one object needs a follow-up


bash
kubectl get pods -n shop -o name | while read -r p; do
  kubectl delete -n shop "$p"
done


-o name prints pod/foo, which kubectl delete accepts. Prefer a label selector (kubectl delete pods -l app=web -n shop) when the set is yours to define. The loop is for “do something the selector cannot express.”


Official docs