Kubernetes JSON: Manifests, kubectl, ConfigMap, and JSON Patch

Last updated:

The Kubernetes API is JSON-native. YAML manifests are a human-friendly layer on top — the API server converts them to JSON internally. Understanding how JSON works throughout Kubernetes (manifests, kubectl output, patches, ConfigMaps) makes automation, debugging, and CI/CD pipelines significantly easier. This guide covers the full stack.

Kubernetes JSON vs YAML Manifests

Every Kubernetes YAML manifest has a 1:1 JSON equivalent. The following Deployment is shown in both formats — they produce identical API objects.

YAML version:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-api
  labels:
    app: my-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-api
  template:
    metadata:
      labels:
        app: my-api
    spec:
      containers:
      - name: my-api
        image: my-api:1.2.0
        ports:
        - containerPort: 8080
        env:
        - name: LOG_LEVEL
          value: "info"
        resources:
          requests:
            cpu: "100m"
            memory: "128Mi"
          limits:
            cpu: "500m"
            memory: "256Mi"

JSON equivalent:

{
  "apiVersion": "apps/v1",
  "kind": "Deployment",
  "metadata": {
    "name": "my-api",
    "labels": { "app": "my-api" }
  },
  "spec": {
    "replicas": 3,
    "selector": { "matchLabels": { "app": "my-api" } },
    "template": {
      "metadata": { "labels": { "app": "my-api" } },
      "spec": {
        "containers": [{
          "name": "my-api",
          "image": "my-api:1.2.0",
          "ports": [{ "containerPort": 8080 }],
          "env": [{ "name": "LOG_LEVEL", "value": "info" }],
          "resources": {
            "requests": { "cpu": "100m", "memory": "128Mi" },
            "limits": { "cpu": "500m", "memory": "256Mi" }
          }
        }]
      }
    }
  }
}

Apply either file with kubectl apply -f manifest.json or kubectl apply -f manifest.yaml. Use YAML for manifests you author by hand; use JSON for manifests generated by scripts or CI/CD tools where indentation sensitivity is a liability.

kubectl JSON Output and JSONPath Queries

# Get full JSON spec of a pod
kubectl get pod my-pod -o json

# Extract specific field with JSONPath
kubectl get pod my-pod -o jsonpath='{.status.podIP}'
kubectl get pod my-pod -o jsonpath='{.spec.containers[0].image}'

# Get multiple fields
kubectl get pod my-pod -o jsonpath='{.metadata.name}{"	"}{.status.phase}{"
"}'

# List all pod images across a namespace
kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"	"}{.spec.containers[0].image}{"
"}{end}'

# Custom columns (tabular output derived from JSONPath)
kubectl get pods -o custom-columns=  'NAME:.metadata.name,STATUS:.status.phase,IMAGE:.spec.containers[0].image'

# Sort by field
kubectl get pods --sort-by='.metadata.creationTimestamp'

# Filter with field selector (server-side)
kubectl get pods --field-selector=status.phase=Running

JSON Patch with kubectl patch (RFC 6902)

# JSON Patch (RFC 6902) — precise array/object operations
kubectl patch deployment my-api --type='json' -p='[
  {"op": "replace", "path": "/spec/replicas", "value": 5},
  {"op": "replace", "path": "/spec/template/spec/containers/0/image", "value": "my-api:1.3.0"},
  {"op": "add", "path": "/spec/template/spec/containers/0/env/-", "value": {"name": "DEBUG", "value": "false"}}
]'

# Merge Patch (RFC 7396) — replace/delete by key
kubectl patch deployment my-api --type='merge' -p='
{
  "spec": {
    "replicas": 5,
    "template": {
      "spec": {
        "containers": [{"name": "my-api", "image": "my-api:1.3.0"}]
      }
    }
  }
}'

# Strategic Merge Patch (default) — Kubernetes-specific, aware of list strategies
kubectl patch deployment my-api -p='
{"spec":{"template":{"spec":{"containers":[{"name":"my-api","resources":{"limits":{"cpu":"1"}}}]}}}}'
TypeRFCList behaviorBest for
strategicK8s-specificMerge by nameMost updates
mergeRFC 7396Replace entire listRemove items
jsonRFC 6902Index-basedPrecise edits

ConfigMap with JSON Values

# Create ConfigMap from JSON file
kubectl create configmap app-config \
  --from-file=config.json=./config.json

# Or embed JSON as a string literal
kubectl create configmap app-config \
  --from-literal=database='{"host":"db.example.com","port":5432,"name":"mydb"}'

# View ConfigMap JSON
kubectl get configmap app-config -o json
# ConfigMap manifest — JSON string as a value
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  config.json: |
    {
      "database": {"host": "db.example.com", "port": 5432},
      "cache": {"ttl": 300, "maxSize": 1000},
      "features": {"darkMode": true, "betaAccess": false}
    }
  log_level: "info"
# In-application: read ConfigMap JSON from mounted file
import json, os

config_path = '/config/config.json'   # mounted from ConfigMap
with open(config_path) as f:
    config = json.load(f)

db_host = config['database']['host']

Secrets with JSON Values (Base64 encoded)

# Create Secret from JSON (base64-encoded automatically)
kubectl create secret generic db-creds \
  --from-literal=credentials=$(echo '{"username":"admin","password":"s3cr3t"}' | base64)

# Decode in app (mounted as env var)
import base64, json, os

raw = os.environ.get('DB_CREDENTIALS', '')
creds = json.loads(base64.b64decode(raw).decode('utf-8'))
# Secret manifest — base64-encoded JSON
apiVersion: v1
kind: Secret
metadata:
  name: db-creds
type: Opaque
data:
  credentials: eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJzM2NyM3QifQ==
  # echo '{"username":"admin","password":"s3cr3t"}' | base64

Programmatic JSON Manifest Generation

# Python: generate Deployment JSON programmatically
import json

def make_deployment(name: str, image: str, replicas: int = 2) -> dict:
    return {
        "apiVersion": "apps/v1",
        "kind": "Deployment",
        "metadata": {"name": name, "labels": {"app": name}},
        "spec": {
            "replicas": replicas,
            "selector": {"matchLabels": {"app": name}},
            "template": {
                "metadata": {"labels": {"app": name}},
                "spec": {
                    "containers": [{
                        "name": name,
                        "image": image,
                        "ports": [{"containerPort": 8080}],
                    }]
                }
            }
        }
    }

deployment = make_deployment("my-api", "my-api:1.3.0", replicas=3)
with open("deployment.json", "w") as f:
    json.dump(deployment, f, indent=2)

# Then apply: subprocess.run(["kubectl", "apply", "-f", "deployment.json"])

JSON in Helm Templates and Kustomize

# Helm: pass JSON as a values.yaml block
# values.yaml
config:
  database:
    host: "db.example.com"
    port: 5432

# In template: toJson converts YAML value to JSON string for ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-config
data:
  config.json: {{ .Values.config | toJson | quote }}
# Kustomize: JSON Patch via patches field
# kustomization.yaml
patches:
  - target:
      kind: Deployment
      name: my-api
    patch: |
      - op: replace
        path: /spec/replicas
        value: 5
      - op: replace
        path: /spec/template/spec/containers/0/image
        value: my-api:2.0.0

Definitions

manifest
A JSON or YAML file declaring the desired state of a Kubernetes object (Deployment, Service, ConfigMap); applied with kubectl apply -f.
kubectl JSONPath
A subset of JSONPath used in kubectl get -o jsonpath='{...}' to extract specific fields from JSON API responses; uses dot notation for nested fields and bracket notation for arrays.
Strategic Merge Patch
A Kubernetes-specific patch format that extends RFC 7396 by understanding how to merge named list items (e.g., containers merged by name, not replaced by index).
ConfigMap
A Kubernetes object for storing non-sensitive configuration as key-value pairs; values are always strings; JSON objects must be embedded as string values.
JSON Patch (RFC 6902)
A JSON document format for describing changes to a JSON document using operations: add, remove, replace, move, copy, test.

FAQ

Can I write Kubernetes manifests in JSON instead of YAML?

Yes — kubectl apply, kubectl create, and kubectl replace all accept JSON files. JSON is actually the native format of the Kubernetes API; YAML manifests are converted to JSON internally before being processed by the API server. JSON is often easier to generate programmatically in CI/CD pipelines because there is no indentation sensitivity. Use YAML for manifests you write by hand (more readable) and JSON for manifests generated by scripts or tools. For complex deployments, both formats coexist peacefully in the same repo — they are fully interchangeable, and every valid YAML manifest has an exact JSON equivalent.

How do I get JSON output from kubectl?

Add -o json to any kubectl get command: kubectl get pod mypod -o json returns the full resource spec. For specific fields, use -o jsonpath='{.field.nested}' — for example, kubectl get pod mypod -o jsonpath='{.status.podIP}'. For complex queries, pipe -o json to jq, which supports full JSONPath and filter syntax. Use -o custom-columns for tabular output derived from JSON fields, and -o name for resource names only.

What is the difference between JSON Patch, Merge Patch, and Strategic Merge Patch in Kubernetes?

JSON Patch (RFC 6902) provides precise operations — add, remove, replace, move, copy — at specific paths; it is index-based for arrays. Merge Patch (RFC 7396) is a shallow key-based merge: you provide only the fields you want to change, but it replaces entire arrays rather than merging them. Strategic Merge Patch is a Kubernetes extension that understands how to merge named list items — containers in a Pod spec are merged by container name, not replaced by index. kubectl patch defaults to Strategic Merge Patch. Use JSON Patch for precise index-based array edits, Merge Patch to remove items from arrays, and Strategic Merge Patch for most routine updates.

How do I store JSON configuration in a Kubernetes ConfigMap?

ConfigMap.data values are always strings, so JSON must be embedded as a string. Two approaches: (1) mount as a file — set a key like config.json with the JSON content as the value, mount the ConfigMap as a volume, and read /config/config.json in the container with json.load(); (2) inject as an environment variable — set a key to a JSON string and call JSON.parse() in the application. File mounting is preferred for nested JSON configs because environment variable values have size limits and escaping issues. Use kubectl create configmap myconfig --from-file=config.json=./config.json to create from a local file.

How do I use JSONPath with kubectl?

The syntax is kubectl get <resource> -o jsonpath='{expression}'. Use dot notation for nested fields: {.metadata.name}. Use bracket notation for arrays: {.spec.containers[0].image}. Iterate with range: {range .items[*]}{.metadata.name}{end}. Filter arrays with [?(@.field=='value')]. Note that kubectl JSONPath is a subset of the full spec — it does not support all operators. For complex queries, pipe -o json to jq, which supports full expression syntax including map, select, and recursive descent.

How do I programmatically generate Kubernetes JSON manifests?

For Python, build a plain dict and use json.dump() — no dependency required. For more features, use the official Kubernetes Python client which provides typed classes. For Go, use encoding/json with the k8s.io/api types. For TypeScript/JavaScript, use JSON.stringify() or the cdk8s library which generates manifests from TypeScript classes. The benefit over YAML templating: no indentation sensitivity, native data structures, easy to test. Popular higher-level tools include Pulumi (TypeScript/Python/Go), cdk8s, and Crossplane. In CI/CD, generate manifest JSON on the fly and pipe directly to kubectl apply -f -.

How do I patch a Kubernetes Secret with JSON?

Secret data values must be base64-encoded strings. When using kubectl patch with --type=json, provide base64-encoded values in the replace operations. For small updates, kubectl patch works well. For full replacement, kubectl apply with an updated manifest file is cleaner. Never commit plaintext Secret manifests to version control — use Sealed Secrets (encrypts the Secret for safe git storage), External Secrets Operator (syncs from AWS Secrets Manager, Vault, or GCP Secret Manager), or SOPS for encrypting Secret values before committing.

How do I extract a nested value from kubectl JSON output?

For a single scalar value: kubectl get pod mypod -o jsonpath='{.status.podIP}'. For an array element: kubectl get pod mypod -o jsonpath='{.spec.containers[0].image}'. For all items: kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{" "}{end}'. For complex queries (filters, conditionals, multiple joins), pipe -o json to jq — it has much more powerful syntax: kubectl get pods -o json | jq '.items[] | select(.status.phase=="Running") | .metadata.name'.

Further reading and primary sources

  • Kubernetes API ReferenceComplete reference for all Kubernetes API resources and their JSON field schemas
  • kubectl JSONPathOfficial documentation for JSONPath expressions supported by kubectl -o jsonpath
  • RFC 6902 JSON PatchThe JSON Patch specification: add, remove, replace, move, copy, and test operations