Need help with your JSON?

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

Immutable JSON Configuration in Container Environments

If you want every container instance to run with exactly the same settings for its entire lifetime, treat configuration as immutable input. In practice, that means your app reads a validated JSON file at startup, the file is mounted read-only, and any config change triggers a new deployment instead of an in-place edit on a running container.

This approach is useful when configuration is too structured for a handful of environment variables: feature flags, service endpoints, rate limits, JSON schema settings, or nested per-tenant behavior. It is especially useful in Kubernetes and other orchestrated environments where reproducibility, clean rollbacks, and low operational surprise matter more than ad hoc runtime tweaks.

What Immutable Configuration Actually Means

Immutable configuration does not mean the source of truth can never change. It means a running container should not see config drift halfway through its lifetime. When you need new settings, you create a new config version and replace the container or pod.

  • Mutable pattern: edit a live file or config object and hope the running process reloads it safely.
  • Immutable pattern: publish a new config version, roll out new containers, and retire the old ones.

That matches the broader container model: build artifacts are disposable, deployments are repeatable, and rollbacks are just a switch back to a known-good image and config combination.

Why JSON Works Well for This

JSON is a good fit when config needs real structure. It handles nesting, arrays, booleans, and numbers without forcing everything into flat strings. That makes it easier to review, validate, diff, and version alongside deployment manifests.

Example production-oriented JSON config

{
  "schemaVersion": 3,
  "http": {
    "port": 8080,
    "requestTimeoutMs": 5000
  },
  "upstreams": {
    "catalogBaseUrl": "https://catalog.internal.example"
  },
  "featureFlags": {
    "auditLogging": true,
    "betaCheckout": false
  },
  "secrets": {
    "dbPasswordFile": "/var/run/secrets/myapp/db-password"
  }
}

The important distinction is that the JSON file should usually contain non-secret structureplus references to secret locations, not raw credentials.

The Recommended Container Pattern Today

For most production workloads, the safest pattern is: keep one reusable image, store structured non-secret JSON outside the image, mount it read-only, keep secrets in a separate secret store, and roll out new pods when config changes.

  1. Create a versioned JSON config file and validate it before deploy.
  2. Store that JSON in a ConfigMap or equivalent non-secret config object.
  3. Mark the config object immutable when your platform supports it.
  4. Mount it read-only into the container filesystem.
  5. Store passwords, tokens, and certificates in Secrets, not in the JSON document.
  6. Deploy a new pod set for every config change instead of mutating live containers.

This is where current Kubernetes behavior matters: mounted ConfigMaps and Secrets can be refreshed in running pods when the underlying object changes, so simply mounting a file does not guarantee runtime immutability by itself. If you want truly immutable runtime behavior, avoid editing live config objects in place. Use versioned names or hash-based names, set immutable: true where supported, and trigger a rollout.

Example Kubernetes ConfigMap and Secret

apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-config-2026-03-11
immutable: true
data:
  application.json: |
    {
      "schemaVersion": 3,
      "http": {
        "port": 8080,
        "requestTimeoutMs": 5000
      },
      "upstreams": {
        "catalogBaseUrl": "https://catalog.internal.example"
      },
      "featureFlags": {
        "auditLogging": true,
        "betaCheckout": false
      },
      "secrets": {
        "dbPasswordFile": "/var/run/secrets/myapp/db-password"
      }
    }
---
apiVersion: v1
kind: Secret
metadata:
  name: myapp-db-credentials-2026-03-11
immutable: true
type: Opaque
stringData:
  db-password: super-secret-value

Example Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: ghcr.io/example/myapp:1.14.2
          volumeMounts:
            - name: app-config
              mountPath: /etc/myapp
              readOnly: true
            - name: app-secrets
              mountPath: /var/run/secrets/myapp
              readOnly: true
          env:
            - name: APP_CONFIG_PATH
              value: /etc/myapp/application.json
      volumes:
        - name: app-config
          configMap:
            name: myapp-config-2026-03-11
        - name: app-secrets
          secret:
            secretName: myapp-db-credentials-2026-03-11

This pattern keeps the image reusable across environments while still giving each deployment an exact, reviewable config version. It also makes rollback straightforward because you roll back both the image tag and the referenced config objects together.

Choosing Between the Main Options

There is no single delivery method for every case. The practical choice usually looks like this:

  • JSON file mounted read-only: best default when config is nested, environment-specific, and reviewed by operators.
  • Environment variables: best for small scalar values, feature switches, and platform-provided metadata. Less pleasant for large or deeply nested JSON because escaping and size become awkward quickly.
  • Baking config into the image: acceptable for fixed defaults or fully self-contained demo images, but weak for normal multi-environment deployment because every config tweak requires a rebuild.

A common compromise is to ship safe defaults in the image, mount one read-only JSON file for environment overrides, and use a few environment variables only for values that truly belong at deploy time.

Secret Handling Rules

Do not put database passwords, API keys, signing keys, or certificates directly into the main JSON config file. Keep secrets separate and inject only references or file paths into the JSON document. That reduces the blast radius of accidental logging, debugging output, git history leaks, and ConfigMap exposure.

  • Use your platform's secret mechanism for secret values. In Kubernetes, Secrets can also be marked immutable.
  • Prefer mounting secrets as read-only files when your app can read from file paths cleanly.
  • Keep secret rotation and config rollout as explicit deployment events, not background surprises.

Common Mistakes and Troubleshooting

  • "We edited the ConfigMap and some pods changed behavior without a deploy." Mounted config can update in running pods. Treat config objects as versioned release artifacts instead of editing them in place.
  • "Our file never refreshed." If you mount a ConfigMap or Secret using subPath, Kubernetes does not update that mount automatically. Use a directory mount if you expect refresh behavior, or better, use an explicit rollout for immutable config.
  • "The app crashed after deploy." Validate the JSON before building the manifest or generating the ConfigMap. A formatter or validator catches malformed syntax before it becomes a failed rollout.
  • "Rollback did not restore behavior." Make sure image version and config version are tracked together. Rolling back only the image while leaving newer config mounted often breaks compatibility.
  • "We need live toggles without redeploying." That is a different requirement. Use a runtime config service or feature-flag system instead of pretending an immutable file should behave like a dynamic control plane.

Conclusion

Immutable JSON configuration works best when you treat config as a release artifact, not a shared mutable scratchpad. Keep structured non-secret settings in JSON, keep secrets separate, mount both read-only, and replace pods whenever the config changes.

Before shipping, run the JSON through a formatter and validator so the file inside your container environment is predictable, readable, and syntactically correct. That small step prevents many avoidable rollout failures.

Need help with your JSON?

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