Environment Variables

Environment Variables are secrets and configurations stored as named values in the operating system or container runtime that the application reads at startup. Platforms like Docker, Kubernetes, and most cloud providers inject them at deployment time, keeping secrets out of source code.

✅ When is it appropriate

Environment Variables are suitable if most of the following apply:

  • the application runs in a container or on a cloud platform that can inject secrets at deployment time without writing them to a file
  • the deployment pipeline such as GitHub Actions, GitLab CI, or AWS CodePipeline stores secrets in its own secret store and passes them to the app as environment variables
  • each environment such as development, staging, and production needs a different value for the same setting, and the platform handles injecting the correct value per environment
  • the team does not yet need a full secrets manager but wants to avoid hardcoding credentials in source code

An environment variable is set outside the application and read by it at startup using a call such as process.env.DATABASE_URL in Node.js or os.environ["DATABASE_URL"] in Python. The application never stores the value itself, which means it never appears in the codebase or a config file that could be committed to a repository.

❌ When is it NOT appropriate

Environment Variables may not be ideal if:

  • the project requires a log showing who changed a secret and when, because environment variables have no built-in change history
  • credentials must be rotated automatically on a schedule or immediately after a suspected breach without redeploying the application
  • dozens of services across multiple teams share the same secrets, because manually synchronising environment variables across that many deployments leads to mismatches
  • the application runs on a server where any process running as the same user can read all environment variables of that process

Environment variables are stored in memory for the lifetime of the process. On a shared server, any other process running as the same operating system user can read them. If an attacker gains shell access to the server, environment variables are one of the first places they check for credentials.

👍 Advantages

  • the deployment platform injects the value so the secret never appears in source code or a committed file
  • each environment gets a different value for the same variable name without any code change
  • containerised applications receive secrets without embedding them in the container image
  • most languages read them with a single built-in function call, requiring no external libraries

👎 Disadvantages

  • any process running as the same OS user can read the environment variables of a running process, so a compromised dependency or co-tenant process may access them
  • there is no built-in change history, so it is impossible to tell who set a variable or when it last changed
  • rotating a secret requires setting a new value in the platform and redeploying the application, which causes a brief interruption
  • keeping the same secret in sync across many services requires updating each deployment separately, which creates a risk of mismatched or stale values

🛠️ Typical use cases

  • containerised services in Docker or Kubernetes where the platform injects secrets at runtime
  • CI/CD pipelines where secrets are stored in the pipeline's secret store and passed to build or deploy steps
  • cloud functions and serverless workloads where each function receives its own isolated set of variables
  • development and staging environments alongside a .env file excluded from version control

⚠️ Common mistakes (anti-patterns)

  • printing or logging the value of an environment variable in application logs, which makes the secret readable to anyone with log access
  • running docker inspect on a container that was started with --env flags, which shows all environment variables including secrets in plaintext
  • storing a secret in a .env file and committing that file to a Git repository instead of listing it only in .gitignore
  • using environment variables as the only secrets management layer for dozens of microservices that all need the same credentials, without any central rotation or access control

A secret printed to application logs is often the easiest credential for an attacker to find. Application logging is typically less protected than the server itself, and logs are frequently forwarded to third-party services. Never log an environment variable value directly, even during debugging.

💡 How to build on it wisely

Recommended approach:

  1. Never hardcode a secret in source code. Read every credential from an environment variable at startup using the language built-in such as os.environ in Python or process.env in Node.js.
  2. Store secrets in the platform's own secret store rather than in a .env file on the server. GitHub Actions Secrets, GitLab CI Variables, AWS Parameter Store, and Kubernetes Secrets all inject values as environment variables without writing them to disk.
  3. Add your .env file to .gitignore before writing any real credentials into it, and commit only a .env.example file that lists variable names with empty values.
  4. Ensure the application never logs environment variable values. Add an explicit check in code review or a linting rule that flags any log statement containing a variable name that matches a known secret pattern.
  5. When the number of services sharing secrets grows beyond a few, or when credentials must be rotated without redeploying every service, migrate to a dedicated secrets manager such as HashiCorp Vault or AWS Secrets Manager.

If the same secret must be updated in more than five deployments at the same time, if there is no record of when a credential was last changed, or if a compromised credential cannot be revoked and replaced without redeploying every service that uses it, these are concrete signals to move to a dedicated secrets manager such as HashiCorp Vault or AWS Secrets Manager.

Feedback & Sharing

Give us your thoughts on this page, or share it with others who may find it useful.

Share with your network:

Feedback

Found this helpful? Let me know what you think or suggest improvements 👉 Contact me.