Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool

JSON-based Configuration for Kubernetes Deployments

Yes, you can deploy Kubernetes workloads from JSON files directly. kubectl apply accepts JSON and YAML, and the Kubernetes API ultimately works with JSON over HTTP. The practical rule is simple: JSON is excellent when manifests are generated, transformed, or validated by tooling; YAML is usually easier when humans are hand-editing files every day.

For most searchers, the real question is not whether JSON works, but how to use it without creating brittle Deployments. That means choosing the right labels, keeping runtime configuration out of the Deployment itself, validating against the API server before rollout, and avoiding stale guidance such as treatingkubectl convert like a built-in command on every machine.

Quick Takeaways

  • Use apps/v1 for Deployments and set .spec.selector explicitly.
  • Make sure .spec.selector.matchLabels matches .spec.template.metadata.labels, because Kubernetes rejects mismatches and the selector is immutable after creation in apps/v1.
  • Put environment-specific values in ConfigMaps and Secrets, then reference them from the Deployment.
  • Validate with kubectl apply --dry-run=server --validate=strict before a real apply.
  • Keep JSON for automation and APIs; keep YAML for manual authoring unless your team has a strong reason to standardize on JSON.

A Practical Deployment JSON Example

The example below is closer to what a real application Deployment looks like today. It uses stable labels, explicit selector matching, resource requests and limits, health probes, and references to external configuration instead of hard-coding everything into the manifest.

{
  "apiVersion": "apps/v1",
  "kind": "Deployment",
  "metadata": {
    "name": "payments-api",
    "namespace": "production",
    "labels": {
      "app.kubernetes.io/name": "payments-api",
      "app.kubernetes.io/instance": "payments-api-prod",
      "app.kubernetes.io/component": "api",
      "app.kubernetes.io/managed-by": "kubectl"
    }
  },
  "spec": {
    "replicas": 3,
    "revisionHistoryLimit": 10,
    "selector": {
      "matchLabels": {
        "app.kubernetes.io/name": "payments-api"
      }
    },
    "strategy": {
      "type": "RollingUpdate",
      "rollingUpdate": {
        "maxUnavailable": "25%",
        "maxSurge": "25%"
      }
    },
    "template": {
      "metadata": {
        "labels": {
          "app.kubernetes.io/name": "payments-api",
          "app.kubernetes.io/instance": "payments-api-prod",
          "app.kubernetes.io/component": "api"
        }
      },
      "spec": {
        "securityContext": {
          "runAsNonRoot": true,
          "seccompProfile": {
            "type": "RuntimeDefault"
          }
        },
        "containers": [
          {
            "name": "api",
            "image": "ghcr.io/example/payments-api:v2.3.1",
            "imagePullPolicy": "IfNotPresent",
            "ports": [
              {
                "name": "http",
                "containerPort": 8080
              }
            ],
            "envFrom": [
              {
                "configMapRef": {
                  "name": "payments-api-config"
                }
              },
              {
                "secretRef": {
                  "name": "payments-api-secrets"
                }
              }
            ],
            "resources": {
              "requests": {
                "cpu": "100m",
                "memory": "128Mi"
              },
              "limits": {
                "cpu": "500m",
                "memory": "512Mi"
              }
            },
            "readinessProbe": {
              "httpGet": {
                "path": "/ready",
                "port": 8080
              },
              "initialDelaySeconds": 5,
              "periodSeconds": 10
            },
            "livenessProbe": {
              "httpGet": {
                "path": "/health",
                "port": 8080
              },
              "initialDelaySeconds": 15,
              "periodSeconds": 20
            },
            "securityContext": {
              "allowPrivilegeEscalation": false,
              "readOnlyRootFilesystem": true
            }
          }
        ]
      }
    }
  }
}

Why these fields matter

  • apiVersion: apps/v1 is the current stable Deployment API you should author directly.
  • .spec.selector.matchLabels and the Pod template labels must line up exactly. If they do not, the API rejects the Deployment.
  • The selector should be treated as permanent. In apps/v1, changing it later is not a safe refactor path.
  • Shared labels under app.kubernetes.io/* improve discoverability in dashboards, CLIs, and automation without changing how the Deployment works.
  • Resource requests and limits make scheduling and capacity behavior predictable instead of leaving it to cluster defaults.
  • Readiness and liveness probes help Kubernetes distinguish between a Pod that is starting up and a Pod that is unhealthy.
  • The image is pinned to a version tag instead of :latest, which makes rollouts and rollbacks auditable.

Put App Configuration in ConfigMaps and Secrets

A Deployment should describe how Pods run, not carry every environment-specific value inline. In practice, JSON-based configuration for Kubernetes deployments works best when the Deployment references separate ConfigMaps for non-secret settings and Secrets for credentials or tokens.

ConfigMap Reference

{
  "configMapRef": {
    "name": "payments-api-config"
  }
}

Secret Reference

{
  "secretRef": {
    "name": "payments-api-secrets"
  }
}
  • Use ConfigMaps for ports, feature flags, log levels, and other non-sensitive runtime settings.
  • Use Secrets for database passwords, API keys, and tokens. Do not hard-code them inside the Deployment JSON.
  • If your deployment pipeline generates JSON, generate these references too, rather than flattening all configuration into a single giant Deployment object.

Validate and Apply JSON Safely

The most useful kubectl workflow is to validate first, then apply, then inspect the live object in JSON if you need to debug exactly what the API accepted.

kubectl apply --dry-run=server --validate=strict -f deployment.json
kubectl apply --server-side -f deployment.json
kubectl get deployment payments-api -n production -o json
  • kubectl apply -f deployment.json works natively because kubectl apply accepts JSON and YAML.
  • --dry-run=server sends the request to the API server without persisting it, which catches schema and admission problems that client-only checks can miss.
  • --validate=strict helps catch unknown or duplicate fields before they quietly turn into confusing rollout bugs.
  • --server-side is worth considering when multiple tools or controllers may touch the same object, because field ownership is tracked on the server.
  • -o json is the easiest way to compare your intended manifest with the live object that Kubernetes is actually running.

About kubectl convert

Older guides often mention kubectl convert as if it ships with every kubectlinstall. Current Kubernetes documentation explicitly notes that the tool is not installed by default. For most teams, the better path is to author new Deployment manifests directly in apps/v1instead of building a workflow around conversion.

Common JSON Deployment Mistakes

  • Selector mismatch: if .spec.selector.matchLabels does not match the Pod template labels, the API rejects the object.
  • Trying to rename the selector later: in apps/v1, the Deployment selector is immutable after creation, so treat it as part of the object's identity.
  • Using live output as source without cleanup: JSON from kubectl get -o jsonincludes fields such as status, resourceVersion, and managedFieldsthat do not belong in clean desired-state manifests.
  • Embedding secrets directly in the Deployment: it makes rotation harder and increases the blast radius of every manifest consumer.
  • Relying on JSON by hand without formatting: missing commas, unquoted keys, or trailing characters break the entire document. Format and validate JSON before the apply step.
  • Using floating image tags: :latest makes rollbacks and incident analysis harder than pinned versions or digests.

When JSON Is the Right Choice

Good fit for JSON

  • Code-generated manifests in CI/CD pipelines
  • Direct Kubernetes API integrations and client libraries
  • Systems that already store configuration as JSON
  • Debugging with exact live object output from the cluster

Usually better in YAML

  • Hand-maintained app manifests in Git
  • Reviews where humans need to scan nested objects quickly
  • Files that benefit from inline comments and lighter syntax
  • Teams without a strong tooling reason to standardize on JSON

If you do keep Deployment manifests in JSON, a formatter becomes part of the workflow, not an optional cleanup step. Consistent indentation and ordering make code review easier and reduce trivial syntax mistakes before kubectl ever sees the file.

Conclusion

JSON-based configuration for Kubernetes Deployments is fully supported and genuinely useful when manifests come from software instead of a human text editor. The safe pattern is to author Deployments inapps/v1, keep selectors stable, reference ConfigMaps and Secrets for runtime data, and validate against the API server before rollout.

If you only remember one thing, remember this: JSON is the machine-friendly format Kubernetes already speaks, but the quality of the Deployment still depends on structure, validation, and operational habits, not on the braces alone.

Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool