.env vs Helm: Why Your Local Environment Variables Don’t Affect Kubernetes Deployments
“I updated the .env file — why didn’t the change take effect after deployment?”
“I updated the
.envfile — why didn’t the change take effect after deployment?”
If this sounds familiar, you’re not alone. Many developers moving between local development and Kubernetes-based deployment with Helm hit this confusing wall. And it usually stems from a false assumption:
Assuming that
.envfiles affect deployed environments.
They don’t. In this post, we'll explore why, show how to fix it, and help you never make this mistake again.
🧪 Local .env Files: The Developer Convenience Layer
In local development, we use .env files to define environment-specific variables. These are typically loaded by:
dotenv(Node.js, Python, etc.)docker-composewithenv_fileFramework-native loaders (e.g. Create React App, NestJS)
Shell tools or scripts
Example .env File
DATABASE_URL=mongodb://localhost:27017/dev-db
API_KEY=dev-local-key PORT=3000This file is parsed at runtime to set process.env (or equivalent), affecting only your local process.
⚠️ It is not bundled or passed into Docker images, Helm charts, or Kubernetes pods — unless you explicitly do so (which you shouldn’t for production).
🚀 Helm & Kubernetes: How Environment Variables Actually Work in Prod
When deploying with Helm, Kubernetes environment variables must be explicitly set in your Helm chart:
# templates/deployment.yaml
env:
- name: DATABASE_URL
value: {{ .Values.env.DATABASE_URL }}Your actual values live in values.yaml, or are passed via --set at deploy time:
env:
DATABASE_URL: mongodb://mongo.prod.svc:27017/prod-dbThese values are the ones that define what your application sees inside the container.
💥 Real-World Issue: The Hidden Misconfiguration
🔧 Scenario
Let’s say you're working locally with:
FEATURE_FLAG_NEW_UI=trueYou test the feature locally, it works, so you push the code and run:
helm upgrade my-app ./charts/my-appYou open the production environment... and the new UI is missing. Why?
🧨 The Root Cause
Your .env file updated the value locally, but you never updated values.yaml or passed --set FEATURE_FLAG_NEW_UI=true to Helm. So Kubernetes is still running with the old value:
env:
FEATURE_FLAG_NEW_UI: falseThe container received the wrong configuration — and you’re now debugging the wrong thing.
🔍 How to Confirm the Problem
Run this command to inspect what environment variables your Kubernetes pod is actually running with:
kubectl exec -it <pod-name> -- printenv | grep FEATURE_FLAGOr describe the pod and inspect the environment section:
kubectl describe pod <pod-name>Look for:
Environment: FEATURE_FLAG_NEW_UI: false💥 There’s your mismatch.
✅ Best Practices to Avoid This Pitfall
1. Centralize Config in a Shared Source
Maintain a .env.defaults or .env.shared file that you use to populate both:
.env(for local dev)values.yaml(for Helm)
2. Automate Syncing .env → values.yaml
#!/bin/bash
echo "env:" > helm/values.env.yaml
grep -v '^#' .env | while read line; do
key=$(echo "$line" | cut -d= -f1)
val=$(echo "$line" | cut -d= -f2-)
echo " $key: \"$val\"" >> helm/values.env.yaml
doneNow you can include this in your build pipeline or CI workflow.
3. Use Secrets for Sensitive Data
Never put secrets in .env or values.yaml. Use Kubernetes-native tools:
kubectl create secretBitnami SealedSecrets
External Secrets Operator
HashiCorp Vault
4. Add CI Validation for Env Parity
Use a script or GitHub Action to compare keys between .env and values.yaml.
diff \
<(grep -v '^#' .env | cut -d= -f1 | sort) \
<(grep ':' helm/values.yaml | cut -d: -f1 | sed 's/ *//g' | sort)
Fail your CI if mismatches are found.
🧠 TL;DR
Concept.env FileHelm/K8s ConfigScopeLocal development onlyDeployed application runtimeLoad Methoddotenv loaders, shell parsingHelm chart + Kubernetes manifestsAffects Prod?❌ Never✅ AbsolutelySync Required?✅ Yes—Security for secrets❌ Not secure✅ Use Secrets
📦 Conclusion
Your .env file is a local development tool. Helm values are the production config source. They are not connected unless you explicitly wire them together.
Understanding this separation is key to preventing production bugs, subtle misconfigurations, and deployment-time surprises. If you’ve ever wondered why “it works locally but not in prod,” this is likely the reason.
🛠 Bonus: Want Automation?
Would you like a CLI tool or GitHub Action to:
Sync
.env→values.yamlValidate variable parity
Auto-generate Secrets
👉 Let me know and I’ll publish a tool/script tailored to your stack.


