envFrom turns every key into an environment variable, and env[].valueFrom.configMapKeyRef picks single keys. A configMap volume turns every key into a file. The difference shows when the ConfigMap changes:
kubectl apply -f - >/dev/null <<'EOF'
apiVersion: v1
kind: Pod
metadata: { name: cfg-demo }
spec:
containers:
- name: shell
image: localhost:33500/booknest-web:1.3
command: [sleep, "3600"]
envFrom: [{ configMapRef: { name: demo-literal } }]
volumeMounts: [{ name: settings, mountPath: /etc/booknest }]
volumes: [{ name: settings, configMap: { name: demo-dir } }]
EOF
kubectl wait --for=condition=Ready pod/cfg-demo >/dev/null
kubectl exec cfg-demo -- sh -c 'echo "env: $LOG_LEVEL"; ls -la /etc/booknest | tail -n +4'
kubectl patch configmap demo-literal -p '{"data": {"LOG_LEVEL": "debug"}}' >/dev/null
kubectl patch configmap demo-dir -p '{"data": {"log-level": "debug\n"}}' >/dev/null
S=$SECONDS; until kubectl exec cfg-demo -- grep -q debug /etc/booknest/log-level 2>/dev/null; do
sleep 2; done
echo "file: after $((SECONDS - S)) s; env: $(kubectl exec cfg-demo -- printenv LOG_LEVEL)"Output
env: info drwxr-xr-x 2 root root 4096 Sep 25 16:01 ..2026_09_25_16_01_55.1695178277 lrwxrwxrwx 1 root root 32 Sep 25 16:01 ..data -> ..2026_09_25_16_01_55.1695178277 lrwxrwxrwx 1 root root 16 Sep 25 16:01 log-level -> ..data/log-level lrwxrwxrwx 1 root root 16 Sep 25 16:01 page-size -> ..data/page-size file: after 69 s; env: info
Environment variables are fixed at process start; a Deployment sees new values only after kubectl 5,150 rollout restart or a changed checksum annotation in its Pod template (Helm 29,435 's pattern, Packaging BookNest with Helm). Files change: the kubelet writes each version into a timestamped directory and swaps the ..data symlink atomically, after its sync period plus cache TTL (over a minute here). A subPath mount never updates, and the application must reread.