Log In

How Environment Files Work and How to Keep Secrets Out of Git

Environment files let developers store configuration values such as database URLs and API keys outside the application source. The pattern originated with Heroku in 2012 and spread through libraries like dotenv. A plain-text .env file loaded at startup supplies these values without hard-coding them. The same file must never reach a Git repository, because any committed secret remains in history even after later deletion. The Twelve-Factor App methodology treats configuration as environment variables that stay orthogonal and independently managed for each deploy. [12factor.net](https://12factor.net/config) formalizes this separation so that the same codebase can run in development, staging, and production without code changes.

Local Configuration with .env Files

Developers create a .env file in the project root to hold values that differ between machines. Typical entries include database connection strings, Redis URLs, and test API keys. The file is loaded early in the application startup so that the rest of the code can read values through the standard environment. Because the file lives only on the local disk, each developer can use different credentials without touching shared source.

The format supports simple KEY=value pairs, optional quoting for values that contain spaces, and comments that begin with #. Multi-line values are possible with certain parsers, though most projects keep entries short. The file is never committed, so it functions as a private scratchpad rather than shared documentation.

Rule Add .env, .env.local, and .env.*.local to .gitignore before the first commit. A secret that enters Git history is effectively public.

Documenting Required Variables with .env.example

A .env.example file committed to the repository lists every variable the application expects. It contains placeholder text rather than real secrets, serving as both onboarding guide and schema reference. New contributors copy the example, rename it to .env, and fill in their own values. The example file also acts as a checklist during code review when new configuration is introduced.

Comments inside .env.example explain the purpose, expected format, and whether a variable is required. Teams that maintain this file reduce onboarding friction and surface missing settings before they cause runtime failures.

Git Ignore Rules and Pre-Commit Protection

The .gitignore file must list every variant of the environment file that should remain private. A minimal entry covers .env, .env.local, and .env.*.local. In monorepos, each package directory may need its own ignore rule so that nested services do not leak files. Relying solely on a root-level ignore is insufficient when subdirectories contain their own .env files.

Pre-commit hooks add an automated layer. Tools such as gitleaks, detect-secrets, and git-secrets scan staged changes for patterns that resemble keys or tokens. When configured as a required hook, they block the commit before the secret reaches the repository. Server-side secret scanning on GitHub, GitLab, and Bitbucket provides a final safety net that can flag or even auto-rotate discovered credentials.

Handling Secrets Beyond Local Development

Plain .env files are acceptable only for local development of non-critical services. Production workloads require managed secret stores that provide encryption at rest, access control, audit logs, and rotation. Options include AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, and HashiCorp Vault. These systems inject values at runtime rather than storing them in files on disk.

When encrypted file solutions such as SOPS or git-crypt are used, the decrypted values still exist in memory as environment variables. The additional protection applies mainly to the version-control layer. For any deployment that handles sensitive data, a dedicated secrets manager remains the recommended approach.

Practical Steps and Common Pitfalls

Start every new project by creating .gitignore entries and a .env.example file. Validate that the application fails fast when a required variable is missing. This practice surfaces configuration problems at startup rather than during later runtime errors. Review the example file during pull-request checks to confirm that new variables are documented.

A committed secret can be recovered from Git history even after deletion. The 2022 Toyota incident demonstrated how a single public commit exposed customer records for weeks until rotation completed. Prevention through ignore rules and scanners is far less costly than post-incident remediation.

Sources

See our free AI tools →