Platform guide

Cloud-Native Configuration Management and Secrets: Secure, Observable, and Portable

External configuration is a Twelve-Factor principle; a usable cloud-native implementation adds ownership, validation, access control, rotation, and safe operational evidence.

Published
Updated
Reading time
16 min read
On this page

Configuration tells a service where and how to run. Secrets authorize it to do so. Both change independently from application code, and both can cause an outage or data exposure when treated as incidental deployment details. The goal is not simply to put values in environment variables. It is to create an explicit, auditable contract between an application and its runtime.

At the scale a managed edge-orchestration layer operates — many services and environments behind one entry point — that contract has to hold consistently everywhere, or it becomes the single largest source of unreproducible incidents. This guide treats cloud-native configuration management and secrets management as one discipline: the practices that keep Optimi's Performance, Security, and Visibility promises true release after release.

Kubernetes ConfigMaps and Secrets are useful distribution mechanisms, but they are not a complete secrets management strategy. A Kubernetes Secret is encoded, not automatically encrypted end-to-end, and its security depends on API-server encryption configuration, RBAC, admission controls, node access, and the surrounding delivery pipeline.

Separate configuration by sensitivity and behavior

Classify every runtime value before choosing how to deliver it:

  • Public operational configuration: ports, log levels, feature defaults, regions, and non-sensitive endpoints.
  • Sensitive configuration: API keys, database passwords, signing material, OAuth client secrets, and private certificates.
  • Policy configuration: rate limits, cache behavior, allowed origins, and routing rules that can change security or latency behavior.
  • Identity configuration: workload identity, service account, role, audience, and trust domain.

This classification prevents two bad defaults: storing every value in source control because it is convenient, or treating every setting as a secret and making ordinary troubleshooting impossible. Use names that express intent, define acceptable formats and ranges, and document an owner for each value.

Classification decays if nothing enforces it. A "debug webhook URL" that later carries a signed token, or a "default region" that becomes a routing policy, can quietly drift from public to sensitive. Revisit the classification whenever a value's shape or usage changes, not only when it is first introduced.

Treat cloud-native configuration management as a typed contract

Read configuration once during startup, parse it into a typed structure, and fail clearly for missing required values or invalid combinations. Redact sensitive names and values in errors. A service that silently substitutes an unsafe default may appear healthy while sending traffic to the wrong endpoint or disabling a critical control.

Keep configuration orthogonal. Prefer individual values such as PAYMENTS_TIMEOUT_MS and EDGE_CACHE_TTL_SECONDS over a single environment label with implicit behavior. Environment names are still useful for selecting a deployment, but they should not conceal the values that determine correctness.

Validation at the field level is not enough. Two values can each be individually valid and still be unsafe together — a cache TTL longer than a signed URL's expiry, a connection-pool ceiling above the database's max-connections limit, or a public listener left enabled while TLS verification is disabled for debugging. Encode these cross-field invariants in the same startup check and refuse to start rather than degrade silently. A schema confirms shape; an invariant check confirms the shapes are safe together — the part of cloud-native configuration management a schema file alone cannot express.

Version configuration changes like code. Review high-impact changes, test them in a representative environment, and record which release used which configuration revision. When a latency regression follows a deployment, engineers need to distinguish a new image from a changed timeout, cache key, or upstream endpoint.

Choose a secrets management delivery pattern

Kubernetes offers more than one way to get a value from a secrets management system into a running container, and the patterns are not interchangeable:

  • Sync-to-Secret controllers — the pattern used by tools such as the External Secrets Operator — read from a secret manager and write an ordinary Kubernetes Secret, which the kubelet mounts as usual. Existing tooling keeps working unchanged, but the value still lands in etcd: encryption at rest and RBAC remain mandatory, and the controller's refresh interval governs how quickly a revoked value disappears.
  • CSI-based mounting — the pattern used by the Secrets Store CSI Driver — mounts secret content into the pod filesystem through an ephemeral volume without ever creating a Kubernetes Secret object. This keeps the value out of etcd entirely, narrowing one attack surface, but anything that specifically expects a Secret resource will not find one.
  • Agent or sidecar injection with dynamic, leased credentials asks the backing system — a database, a cloud provider, a broker such as Vault — to mint a short-lived credential on request instead of storing one at rest. It expires on its own schedule and can be revoked centrally: the strongest option where the backing system supports it, and a poor fit where it only accepts long-lived static credentials.

Pick the pattern per workload based on what the consuming system can accept and how fast a revoked value needs to disappear, and document the choice next to the workload rather than assuming one pattern fits the whole cluster.

Base64 is encoding, not encryption

Kubernetes Secret data is base64-encoded in manifests. Protect it with encryption at rest for the API server, narrowly scoped RBAC, secure backups, and access reviews. Do not commit real Secret manifests or decoded values to a repository.

Use Kubernetes primitives with their limits in mind

Mount a ConfigMap or Secret as a volume when an application can reload file-based configuration safely. Environment variables are simple, but an already-running process does not receive a changed value. Volume-projected content may update eventually, but the application must detect and validate the change; never assume this makes every setting dynamically reloadable.

Mark ConfigMaps and Secrets that do not need in-place edits as immutable: true. The kubelet stops watching for changes, reducing API-server load at cluster scale and removing the risk that a "harmless" in-place edit silently changes behavior for every replica at once. The tradeoff: an immutable object cannot be patched. A value change means creating a new object — commonly with a content-hash or revision suffix — updating the workload's reference, and rolling pods in controlled batches. That extra step is a feature, not friction: it turns an ordinary config rotation into a reviewable, revertible change instead of a silent mutation.

Avoid broad envFrom imports for production services. They make the effective contract invisible and allow an unrelated key to alter a runtime. Reference exact keys, scope configuration per workload, and avoid putting secrets in command-line arguments, annotations, labels, or debug endpoints.

Use namespaced RBAC and grant workloads only the ability to read the specific resources they need, ideally through a controller or external secret integration rather than direct broad API access. Limit who can read Secret objects, who can create pods that mount them, and who can inspect CI logs or deployment manifests. A subject that can create a pod with another workload's service account can often obtain that workload's access.

Prefer short-lived workload identity

Where your cloud platform supports it, let a workload authenticate as its own identity and exchange short-lived credentials for the resource it needs. This reduces the blast radius and rotation burden of long-lived static keys. Bind identity to the correct namespace and service account, constrain the audience and role, and test what happens when token renewal or the identity provider is unavailable.

Static secrets remain necessary for some systems. Keep them in a dedicated secrets manager, deliver them just in time where possible, rotate them on a tested schedule, and support overlapping credentials during rotation. A rotation that changes the server first and clients later can create a widespread availability event.

Where the backing system supports it, prefer dynamic, leased credentials over rotating a static one. A database secrets engine that mints a scoped username and password with a bounded time-to-live removes rotation as a manual step: the credential expires whether or not anyone remembers to revoke it, and a leak has a built-in shelf life. Reserve manual config rotation for systems — legacy databases, third-party APIs, signing keys — that cannot issue credentials on demand.

For TLS certificates, coordinate issuance, deployment, reload, and expiry monitoring. A certificate change at an ingress or edge may need a different procedure from an application-to-database credential.

Rotate credentials with a safe config rotation sequence

  1. Create a new credential with the minimum required permission.
  2. Distribute it through the approved secret path while retaining the old credential.
  3. Reload or roll workloads in controlled batches and verify authentication success, latency, and error rate.
  4. Revoke the old credential after all consumers have moved and the overlap window has elapsed.
  5. Audit access and document the result without recording secret material.

Generalize step 2 and 4 with an explicit label rather than an implicit assumption. Several managed secret stores make this a first-class concept — a staging label that marks the previously active value, the value being validated, and the value clients should still be using — so every consumer and operator agrees on which credential is authoritative at each moment. Build the same three-state thinking into a homegrown config rotation path even without that tooling: label credentials as retiring, active, and staged, and never revoke retiring until nothing is presenting it.

Watch for one specific failure mode: a long-lived connection pool that authenticated before rotation and never renegotiates keeps working on the old credential until recycled, which can hide a failed rotation for hours. Bound connection lifetime below your rotation overlap window, or recycle the pool as an explicit rotation step.

Credential rotation keeps old and new values valid during rollout

An overlap window makes client rollout and connection recycling observable before the prior credential is revoked.

Download:PNGSVG

Keep changes from becoming latency incidents

Configuration can create performance regressions. A lower connection-pool limit increases wait time; a shorter upstream timeout can turn slow requests into errors; an incorrect cache vary rule can leak personalized responses or destroy cache hit ratio. Define a latency and availability budget for critical values, test the values under load, and roll out changes progressively.

Keep origin endpoints private where possible and configure the edge-to-origin trust path independently from public client configuration. Do not distribute an origin address or bypass credential to browser code. When routing, caching, or failover configuration changes, validate cacheability, authorization, TLS, health checks, and request correlation from edge through origin.

Observe configuration without exposing it. Emit a configuration revision, release digest, feature flag state where non-sensitive, and secret version identifier or rotation timestamp where permitted. Do not log the actual secret, full connection string, authorization header, or signed URL. These identifiers make it possible to associate a rise in p99 latency or authentication failures with a change.

One configuration contract, many providers

A request behind one entry point often crosses several specialized providers — CDN, WAF, DNS, origin — where a config or credential change at any single layer produces the same symptom: a latency spike or a wave of authorization failures. Tag every revision and secret version with an identifier MYO can correlate across providers, so a regression points to the layer that actually changed instead of a manual search across every dashboard.

Common pitfalls

  • Secrets in images or Git history: deleting a file does not revoke a leaked key. Rotate it and scan image layers, repositories, CI artifacts, and logs.
  • One shared production secret: it prevents attribution and makes rotation risky. Issue separate credentials per service and environment.
  • Configuration reload without validation: a partially written or malformed value can break every replica. Validate before activation and retain the last known-good configuration when the application supports reload.
  • Debug access that reveals environment variables: process inspection, crash dumps, and support bundles can expose credentials. Redact by default and restrict diagnostic access.
  • Secrets available to every pod in a namespace: namespace isolation alone is not least privilege. Review RBAC and pod-creation rights together.
  • Working around immutability instead of versioning: recreating an immutable: true object under the same name to force an edit discards the revision history that made the change reviewable. Roll forward to a new revisioned name instead.
  • Rotation that ignores connection pools: revoking the old credential while pooled connections are still authenticated with it produces intermittent, hard-to-reproduce failures well after the rotation window closes.

Operational checklist

For each service, document the complete configuration schema, sensitivity classification, source of truth, owner, consumer identity, secrets management delivery pattern, rotation procedure, reload behavior, and monitoring signal. Test a missing value, invalid value, revoked credential, rotation overlap, secret-store outage, and rollback. Include the deployment system and backups in the threat model: a secure runtime cannot compensate for plaintext CI artifacts or overly broad cluster administration.

Apply it: Acme Shop runtime trust flow

Overview

Outcome and prerequisites

Outcome: Acme Shop catalog-api receives only reviewed configuration and a rotatable database credential, without application code reading Kubernetes Secrets directly. Prerequisites: a non-production namespace, an approved secret manager or controller, and RBAC review access.

Acme Shop keeps the database credential in its approved secret manager. A narrowly scoped sync controller writes one namespace Secret; the kubelet mounts that exact key for catalog-api. The application authenticates to PostgreSQL and emits only a secret version identifier. Developers and the application service account do not need broad Secret-list access. This is the sync-to-Secret pattern described above: acme-secret-sync treats the secret manager as the source of truth and reconciles it into one named Secret, so catalog-api and its developers never need direct read access to the Kubernetes Secrets API.

Acme Shop secret trust flow
  1. Approved secret manager
  2. acme-secret-sync identity
  3. Exact namespace Secret
  4. catalog-api mounted key
  5. PostgreSQL and redacted telemetry

Figure 1. Trust flows from the secret manager through a narrowly scoped controller and kubelet mount to catalog-api; telemetry carries a version ID, never the credential.

Exact sandbox manifests and RBAC

The Secret value is deliberately absent: a controller supplies it. catalog-api mounts one named key and has no Kubernetes API Secret permission. The sync identity is restricted to one pre-created Secret.

apiVersion: v1
kind: ServiceAccount
metadata: { name: acme-secret-sync, namespace: acme-shop-sandbox }
---
apiVersion: v1
kind: ServiceAccount
metadata: { name: catalog-api, namespace: acme-shop-sandbox }
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { name: update-catalog-runtime-secret, namespace: acme-shop-sandbox }
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["catalog-api-runtime"]
    verbs: ["get", "patch", "update"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: acme-secret-sync, namespace: acme-shop-sandbox }
subjects: [{ kind: ServiceAccount, name: acme-secret-sync, namespace: acme-shop-sandbox }]
roleRef: { apiGroup: rbac.authorization.k8s.io, kind: Role, name: update-catalog-runtime-secret }
---
apiVersion: v1
kind: ConfigMap
metadata: { name: catalog-api-config, namespace: acme-shop-sandbox }
immutable: true
data: { PAYMENTS_TIMEOUT_MS: "700", CONFIG_REVISION: "2026-07-14.1" }
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: catalog-api, namespace: acme-shop-sandbox }
spec:
  selector: { matchLabels: { app: catalog-api } }
  template:
    metadata: { labels: { app: catalog-api } }
    spec:
      serviceAccountName: catalog-api
      containers:
        - name: catalog-api
          image: registry.example.invalid/acme/catalog-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
          env:
            - name: PAYMENTS_TIMEOUT_MS
              valueFrom: { configMapKeyRef: { name: catalog-api-config, key: PAYMENTS_TIMEOUT_MS } }
            - name: DATABASE_PASSWORD_FILE
              value: /var/run/acme/database-password
          volumeMounts: [{ name: runtime-secret, mountPath: /var/run/acme, readOnly: true }]
      volumes:
        - name: runtime-secret
          secret: { secretName: catalog-api-runtime, items: [{ key: database-password, path: database-password }] }

The ConfigMap is marked immutable: true, so a change to PAYMENTS_TIMEOUT_MS or CONFIG_REVISION means creating catalog-api-config-2 (or another content-hash-suffixed name) and updating the Deployment's configMapKeyRef, not editing this object in place. The API server rejects an in-place edit outright, which is the intended guardrail, not a bug to route around.

Representative output: redacted startup contract
catalog-api config_revision=2026-07-14.1 payments_timeout_ms=700
catalog-api secret_version=rotation-2026-07-a credential=redacted
catalog-api database_auth=success

Rotation, validation, and failure behavior

Create rotation-2026-07-b with the same minimum database role, update the approved source, and let the controller update the named Secret. Restart one sandbox replica, then canary the remaining replicas while both credentials are valid. Validate successful authentication, p95 latency, and error rate before revoking rotation-2026-07-a. Because catalog-api holds a small connection pool to PostgreSQL, the rollout step also recycles pooled connections older than the rotation start time; otherwise a connection opened under rotation-2026-07-a could keep working past revocation and mask a failed rotation. If the new credential fails, restore the previous source version and keep the old credential active; never print either value to diagnose the fault.

Use only server-side dry-run validation: kubectl apply --dry-run=server -n acme-shop-sandbox -f catalog-runtime.yaml. Then verify version identifiers, not values. On a malformed ConfigMap, stop rollout and restore the last reviewed source revision.

SymptomLikely causeSafe checkRecovery
Authentication fails after rotationNew version is not accepted by PostgreSQL.Check redacted auth result and version ID.Restore the prior source version before revocation.
Workload lists unrelated SecretsRBAC is broader than the trust flow.Run kubectl auth can-i list secrets --as=system:serviceaccount:acme-shop-sandbox:catalog-api.Remove list/watch permissions and review pod-create access.
Config changed but process did notValue was injected as an environment variable.Compare pod start time and config revision.Restart in controlled batches or implement validated file reload.
Secrets appear in diagnosticsEnv dump or redaction is unsafe.Inspect a sandbox support bundle.Revoke exposed credentials and remove the diagnostic path.
kubectl apply fails with "field is immutable"The ConfigMap or Secret was created with immutable: true.Diff the applied manifest against the live object with kubectl diff.Create a new revisioned object name and update the reference instead of mutating in place.
Rotation looks complete but auth still fails intermittentlyA long-lived connection pool cached the prior credential.Compare active connection age against the rotation timestamp.Force a pool recycle or bound connection lifetime below the rotation overlap window.

Authoritative references

Make configuration and secrets part of your edge strategy

Talk to Optimi about aligning cloud-native configuration management, secrets management, and config rotation with the Performance, Security, and Visibility your edge orchestration depends on.

Review configuration architecture