ConfigMaps and Secrets
ConfigMaps hold plain settings and Secrets hold credentials, but base64 in a Secret is encoding, not encryption.
process.loadEnvFile([path]) // path defaults to './.env'A ConfigMap is a named set of key/value strings in the cluster, for settings that are not confidential. A Secret has the same shape and is meant for credentials. Both exist so settings live outside the image, and one image can run anywhere.
Getting them into the process
Either as environment variables — envFrom for a whole ConfigMap, valueFrom.secretKeyRef for one key — or as a volume mount: a directory in the container holding one file per key. Environment variables arrive on process.env. Mounted keys are read with fs.readFile.
The choice matters when a value changes. A mounted Secret is updated inside the container, eventually, when the Secret changes. An environment variable is fixed when the container starts and never changes, so a rotated credential reaches the process only when the pod is replaced.
A Secret is not a vault
Values in a Secret's data field are base64-encoded strings; stringData takes plain text instead and the API server encodes it. Base64 is encoding, which anyone can reverse. The Kubernetes documentation is direct about this: anyone with API access can retrieve or modify a Secret. Restrict who can read them, turn on encryption at rest, and keep Secret manifests out of Git.
On your own machine
There is no cluster to supply the variables. node --env-file=.env app.js (Node.js 20.6.0) loads them before your code runs. process.loadEnvFile() is the shorthand for process.loadEnvFile('./.env') and does it from inside the program; it stopped being experimental in Node.js 24.10.0.
Example
When to use it
- Pointing one image at a different database per environment without rebuilding it.
- Keeping a database password out of the repository while still having it reach the pod.
- Rotating a credential without a deploy, by mounting it as a file and re-reading it.
- Running the same service on a laptop with no cluster, using a .env file.
More examples
The two objects, and the pod that uses them
envFrom is the bulk form and env with valueFrom is the one-key form; both land on process.env. Using stringData keeps the manifest readable, but the stored object still holds base64, so kubectl get secret -o yaml shows an encoded value and nothing is protected by that encoding.
apiVersion: v1
kind: ConfigMap
metadata:
name: api-config
data:
LOG_LEVEL: "info"
DB_HOST: "postgres"
DB_USER: "app"
---
apiVersion: v1
kind: Secret
metadata:
name: api-db
stringData: # plain text in, base64 in the stored object
password: "p4ssw0rd" # do not commit this file
---
# in the Deployment's container:
envFrom:
- configMapRef:
name: api-config # every key becomes an environment variable
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: api-db
key: passwordMounting a Secret as files instead
Choose this form for anything that rotates: TLS keys, database passwords managed by an operator, tokens with a short life. The file in the volume is updated when the Secret changes, so a service that re-reads the file on a reconnect picks up the new value without a deploy. Note that a mount using subPath does not receive those updates.
containers:
- name: api
volumeMounts:
- name: db-credentials
mountPath: /etc/db # becomes /etc/db/password
readOnly: true
volumes:
- name: db-credentials
secret:
secretName: api-db
---
# Reading it back in the service:
# const password = (await readFile('/etc/db/password', 'utf8')).trim();The same configuration locally
Both read the same file format, and both put the values on process.env; the difference is when they run. Use --env-file-if-exists (Node.js 22.9.0) instead of --env-file when the file is optional, because --env-file throws if the file is missing. In the cluster neither is used: the variables come from the ConfigMap and the Secret.
# .env, listed in .gitignore
# LOG_LEVEL=debug
# DB_HOST=127.0.0.1
# DB_USER=app
# DB_PASSWORD=localdev
# The flag form: variables exist before the first line of your code runs,
# and Node.js settings in the file, such as NODE_OPTIONS, are applied.
node --env-file=.env server.js
# The in-code form, for a script that should work without the flag.
# NODE_OPTIONS in the file has no effect here, because Node has already
# started by the time this line runs.
node -e "process.loadEnvFile(); console.log(process.env.DB_USER)"
Discussion