Kubernetes .env to ConfigMap & Secret

Paste a .env file and get a ConfigMap and a Secret, with the credentials separated out and the keys that will not survive envFrom named. It runs in this tab, which is the only sane place to paste a file full of passwords.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Convert. Nothing leaves this tab.

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

A credential in .env

A value that must become a Secret rather than a ConfigMap, detected by name

DB_PASSWORD=hunter2
DEBUG=true
API_URL=https://api.example.com

Values needing care

Quotes, spaces and a dollar sign, none of which Kubernetes interpolates

GREETING="hello world"
PATH_PREFIX=/api/v1
COST=$100
EMPTY=

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Putting a credential in a ConfigMap

    ConfigMaps are not access-controlled the way Secrets are, are readable by anyone with get in the namespace, and appear in kubectl describe output in full.

    Instead:Split the file: non-secret values to a ConfigMap, credentials to a Secret.

  2. Expecting .env quoting rules to carry over

    Docker Compose, dotenv libraries and shells all differ on quotes, escapes and interpolation. Kubernetes does none of it: the value is literal.

    Instead:Check any value containing quotes, spaces or a dollar sign after conversion.

  3. Assuming a ConfigMap change reaches the pod

    Values injected with envFrom are read at container start. Editing the ConfigMap does nothing until the pod restarts, unlike a mounted volume which updates eventually.

    Instead:Restart the workload, or mount as a volume if live updates matter.

What it catches

The conversion is four lines of sed. What is not is everything that goes wrong after the ConfigMap is applied and appears to have worked.

The keys envFrom throws away without telling you

envFrom turns every key in a ConfigMap into an environment variable, and a variable name has to match [A-Za-z_][A-Za-z0-9_]*. A key with a dot or a dash in it does not, so the kubelet skips it, writes an InvalidVariableNames event nobody reads, and starts the container anyway. The application sees the variable as unset and falls back to a default. Spring and Symfony .env files are full of dotted keys.

Credentials separated from configuration

Keys and values that look like credentials go into a Secret and the rest into a ConfigMap, with the reason for each move reported. A ConfigMap is readable by anything with get on ConfigMaps in the namespace and is dumped by default in most debug tooling, so the split is worth making at conversion time rather than never.

The values that make the apply fail

ConfigMap and Secret data are both string maps. PORT=8080 written unquoted becomes a YAML integer and the API server rejects the object with a Go unmarshalling error that never names the key. So does DEBUG=true, and so does anything YAML reads as a boolean, which includes no, off and y. Every value in the output is quoted, so the whole class is gone.

dotenv quoting, done the way dotenv does it

Escape sequences are interpreted inside double quotes and not inside single ones, which is what makes single quotes right for Windows paths. An unquoted hash starts a comment and a quoted one does not. Multi-line quoted values, which is how a PEM key ends up in a .env, are read to their closing quote and emitted as a block scalar.

What happens to these values once they are in the cluster

A .env file is read by the application at startup. A ConfigMap is read by the kubelet, at container start, and the difference in when and how often matters more than the difference in format.

envFrom takes everything, env takes one and can rename it

envFrom is the direct equivalent of an .env file and is what most people want. It is also where the silent key-skipping happens. Referencing a key explicitly with configMapKeyRef has no naming restriction at all, so it is the escape hatch for a key you cannot rename.

envFrom:                      # every key, names unchanged
  - configMapRef:
      name: app-config
  - secretRef:
      name: app-secrets

env:                          # one key, renamed on the way
  - name: DATABASE_URL
    valueFrom:
      configMapKeyRef:
        name: app-config
        key: spring.datasource.url   # dots are fine here

Changing a ConfigMap does not restart anything

This is the single most surprising part. A ConfigMap consumed through env or envFrom is read once, when the container starts. Edit it afterwards and every running pod keeps the old values indefinitely, because nothing triggers a restart. Mounted as a volume it does update in place, after a delay of up to the kubelet sync period, but the process still has to notice the file changed.

kubectl edit configmap app-config     # takes effect: never

kubectl rollout restart deploy/api    # takes effect: now

# or make the change force a rollout, by putting a hash
# of the config in the pod template annotations

A Secret is RBAC, not cryptography

Secret data is base64, which is an encoding. Anyone who can read the Secret can read the value, and by default that is anyone with get on Secrets in the namespace, plus every ServiceAccount token that grants it. What actually protects a Secret is RBAC, and encryption at rest if the cluster has it turned on. Neither of those is a property of the file this tool gives you.

# what is in the manifest either way:
stringData:
  DB_PASSWORD: hunter2        <- plain text

data:
  DB_PASSWORD: aHVudGVyMg==   <- also plain text,
                                 with extra steps

# so do not commit either. Sealed Secrets, the External
# Secrets Operator and SOPS all exist for this.

Kubernetes expands $(VAR) inside env values

Before the container starts, Kubernetes substitutes $(OTHER_VAR) in env values using the other variables in the same container. A password containing that sequence is rewritten, and if the name does not resolve the text is left alone, so the same value behaves differently depending on what else happens to be set.

PASSWORD=abc$(def)ghi

  ->  if DEF is set:    abc<value of DEF>ghi
  ->  if it is not:     abc$(def)ghi

escape it as $$(, or mount the value as a file,
where no substitution happens at all

More kubernetes tools

Kubernetes YAML Generator Answer a few questions, get a manifest Kubernetes YAML Validator Is this YAML valid, and where is it wrong? Kubernetes Rollout & Probe Timing Restart loops, capacity dips and stuck drains Kubernetes Service Checker Will this Service have any endpoints? Kubernetes Network Policy Viewer What can reach what, and what just broke Kubernetes Manifest Diff What actually changed Kubernetes Resource Calculator What these manifests actually reserve Kubernetes RBAC Viewer Who can actually do what Kubernetes Deployment Generator See the capacity dip before you deploy Kubernetes Service Generator port, targetPort, and which one you meant Kubernetes StatefulSet Generator Stable names, and volumes that outlive you Kubernetes QoS Class Calculator And where you sit in the eviction order Kubernetes Node Capacity Planner Allocatable is not capacity Kubernetes Max Pods per Node Calculator The ENI limit, not the 110 default Kubernetes Label Selector Tester Which key failed, not just that one did Kubernetes Taint & Toleration Tester Tolerations permit, they do not attract Kubernetes Resource Name Validator RFC 1123 subdomain, label, or 1035 Kubernetes Label & Annotation Validator Why your image tag is not a legal label Kubernetes CrashLoopBackOff Guide The status is a timer, not a cause Kubernetes TLS Certificate Decoder When it expires, and what it covers Kubernetes ServiceAccount Token Decoder Bound or legacy, and when it dies Kubernetes imagePullSecret Generator The registry URL nobody gets right Kubernetes cert-manager Certificates Issuer scope, and the rate limit that costs a week Kubernetes Flux HelmRelease Generator With the source it cannot work without Kubernetes Helm Chart Generator Chart.yaml, values, and working templates Kubernetes Argo CD Application Generator The defaults that are off, turned on Kubernetes Ingress Generator pathType, and the TLS secret that fails quietly Kubernetes HPA Generator Why it says unknown and never scales Kubernetes SecurityContext Generator Pod level, container level, and which wins Kubernetes Probe Generator How long it gets to start before liveness kills it Kubernetes PVC Generator Access modes your storage can actually do Kubernetes ResourceQuota Generator And the LimitRange that stops it breaking deploys Kubernetes CronJob Generator The fields that decide whether it runs Kubernetes NetworkPolicy Generator With the DNS rule already in it Kubernetes RBAC Generator And what it actually grants Kubernetes PodDisruptionBudget Generator Can this budget ever be satisfied? Kubernetes API Deprecation Checker What breaks on the next upgrade Kubernetes Kubeconfig Viewer Read it without handing it to anyone Kubernetes Quantity Parser What 512Mi, 100m and 1e3 actually mean Kubernetes Multi-Document YAML Splitter One file per object, in apply order Kubernetes Secret Decoder See what is actually in there Kubernetes Ingress to Gateway API Which annotations silently stop working Kubernetes Manifest Visualizer What points at what, and what points at nothing Kubernetes Patch Tester Which --type, and what it deletes Kubernetes kubectl JSONPath Tester kubectl's dialect, not generic JSONPath Kubernetes Node Affinity Tester Required excludes, preferred only scores Kubernetes Helm Values Diff A missing key is a third value Kubernetes Pod and Service CIDR Fixed at cluster creation Kubernetes Secret Encoder base64 is not encryption Kubernetes API Version Checker Will this apply on that cluster Kubernetes Go Template Tester Go truthiness is not JavaScript's Kubernetes Cluster Cost Estimator At your rates, not a price table Kubernetes Kustomize Build Transformers run in kustomize's order Kubernetes Helm Chart Validator version is SemVer, appVersion is not

Elsewhere on the site