
Environment variables have been the default way to configure applications for years. They’re simple, universal, and make it easy to inject settings into code. But as systems become more distributed and complex, that simplicity now hides real security risks.
In most setups, secrets stored in environment variables sit in plain text and are easier to leak than people realize. Logs, crash dumps, and debugging tools can capture them silently, and a single compromised host or container can expose everything at once. Also, if environment variables are so convenient, why do the same security teams that rely on them warn against using them in production?
In this article, we’ll cover why environment variables fall short in modern systems, how secrets managers solved that but added complexity, and how a hybrid approach gives you security and simplicity, without hidden risks. We’ll also walk through a clear guide on migrating your secrets away from environment variables.
Environment variables worked well when apps ran on single servers that stayed up for months. However, modern systems run across containers, functions, and clusters that start and stop frequently. Additionally, many teams inherit unsafe defaults, which grant these environments broad access and make it easier for secrets to leak.
Let’s look at a few common failure modes:
| Context | Exposure path | Security implication |
|---|---|---|
Containers | Anyone with access can run | A compromised container exposes every secret in its environment. |
Kubernetes | Env vars are accessible to any process in the pod. An engineer(or attacker) can run | Anyone with basic cluster access can read secrets, breaking least-privilege boundaries. |
Serverless | Functions(e.g., AWS Lambda) load env vars at startup. A memory dump or exploit can capture those variables in transit or at rest. | A runtime exploit can leak cloud keys from memory and allow attackers to pivot into other systems. |
CI/CD Pipelines | Build jobs inject secrets as env vars. A malicious PR or script can simply echo | Secrets leaked to CI logs are effectively public and may persist in build artifacts or storage. |
Most of these failures happen for the same reason. Environment variables store secrets in plain text, residing in memory where any process or user with enough access can read them. On top of that, teams often share .env files through chat, email, or shared drives, which spreads sensitive data across multiple systems without proper controls or auditing.
To reduce this risk, many teams now use dedicated secrets managers such as Doppler, HashiCorp Vault, or AWS Secrets Manager. These tools treat secrets as managed resources, rather than raw values in memory, and that shift has completely changed how teams handle sensitive data.
Secrets managers address most of the issues that make environmental variables dangerous. Here’s how they make this possible:
The trade-off is that secrets managers take more engineering effort to implement. Fetching secrets through SDKs, setting up access policies, and updating pipelines all add complexity. Still, that trade-off is worth it. The challenge is finding a way to keep that security without slowing developers down. For most teams, the answer lies in a hybrid approach.
A hybrid approach gives you the best of both worlds. You get the security of a secrets manager and the convenience of environment variables. In this model, environment variables still define your app’s behavior, while secrets managers handle credentials, keys, and tokens that grant access or carry risk if leaked. Instead of treating every value as a secret or leaving everything in plain text, you separate what’s sensitive from what’s just configuration.
To get this right, you need a clear rule of thumb. Review each variable and ask one question: if this value leaked, could it give someone access or cause damage? If yes, it belongs in a secrets manager. If not, it’s fine to keep it as an environment variable. Making this distinction early prevents most leaks from ever happening.
Here’s a quick reference guide:
| Type | Examples | Where to store | Why |
|---|---|---|---|
App settings and flags |
|
| Controls behavior or routing. Safe if exposed. |
Public or non-sensitive identifiers | OAuth client IDs, analytics keys, hostnames, base URLs |
| Public by design. No security impact if leaked. |
Database connections and credentials |
| Secrets manager | Direct access to data. Needs encryption, rotation, and audit. |
API keys and tokens | Stripe keys, GitHub tokens, webhook secrets | Secrets manager | Grants access to services. Must be tightly controlled and rotated. |
Encryption and cloud credentials | JWT signing keys, TLS private keys, IAM keys, service accounts | Secrets manager | High-impact secrets. Require strict access control and short lifetimes. |
A hybrid model allows developers to move quickly without sacrificing control. Secure configuration is stored in .env files, and sensitive data resides in a system that’s encrypted, auditable, and short-lived.
Now that you know what belongs where, the next step is making the switch. Let’s look at how to migrate your existing secrets from environment variables and into a secrets manager without breaking anything.
Moving from environment variables to a secrets manager can feel intimidating. You can’t just delete every DB_PASSWORD tomorrow without breaking something. The better approach is to migrate gradually and give your team time to adapt.
Here’s a clear path that works for most teams:

Start by mapping where your secrets actually live. Check your code, .env files, Dockerfiles, and CI/CD pipelines for any strings that resemble credentials or tokens, and track how these values flow through your system. You can make this easier by using secret scanning tools such as TruffleHog, Gitleaks, or GitHub Secret Scanning. These tools automatically detect keys, passwords, and tokens in your repositories and build logs, allowing you to quickly identify hidden risks.
Once you have that map, you can start migrating the highest-risk secrets first.
Introduce a secrets manager such as Doppler and begin migrating your most sensitive secrets first. Next, update your deployment or startup process so the app retrieves those secrets at runtime. Most secret managers provide SDKs or CLI tools that make this simple.
Here’s an example using the Doppler Node.js SDK:
In this example, the app retrieves secrets directly from Doppler at runtime. Static secrets are fetched securely, and dynamic secrets can expire after a set time, further reducing the risk of exposure. Nothing is stored on disk or in version control, and the secrets exist only in memory while the application is running.
At this stage, your secrets manager provides a centralized and controlled way to manage and distribute critical credentials.Even if the app temporarily loads secrets into memory, they’re delivered securely and never stored unencrypted on any system.
Once your main systems are using a secrets manager, start removing any remaining credentials from .env and configuration files. For local development, provide developers with a secure way to access secrets without sharing files. With Doppler, for example, they can authenticate once and run their apps as usual:
This workflow eliminates the need to share .env files over chat or email and keeps credentials out of version control. It also simplifies onboarding and gives developers the right secrets for their environment without manual setup or the risk of stale credentials.
As you migrate, keep your team informed about the significance of these changes. Emphasize that the goal is to reduce exposure, keep secrets out of logs, and remove manual handling. Encourage feedback from developers and be willing to adjust the process if something slows them down. A secure system only works if people are comfortable using it.
Are environment variables still safe for secrets? Not really. They remain practical for configuration, but they fall short when it comes to protecting sensitive information. To handle secrets safely without slowing development, think in layers. Store sensitive data in a secrets manager, and keep harmless configuration in environment variables.
Modern tools such as Doppler make this easier to adopt without slowing teams down. Start small by securing one application or pipeline, learn from that process, and expand gradually. Each step away from plaintext environment variables lowers your exposure and makes leaks less likely.
Want to see what this looks like in practice? Explore the Doppler demo, and experiment with runtime secret management firsthand.



Trusted by the world’s best DevOps and security teams. Doppler is the secrets manager developers love.
