One of the most common and most damaging mistakes in software is committing a secret — an API key, a database password, a token — into source control. It feels harmless in the moment and becomes a serious exposure the instant that repository is shared, made public, or breached. Automated scanners crawl code hosts constantly looking for exactly these strings.

The fix is a small shift in habit: code should never contain its own secrets or its environment-specific settings. Those come from outside, at run time, through configuration.

Configuration lives outside the code

The standard mechanism is the environment variable: a named value the running process reads from its environment rather than from the source. The same build then behaves correctly in development, staging and production simply because each environment supplies different values — different database URLs, different keys, different feature flags. The artifact stays identical; only the configuration changes.

This separation is not only about secrets. Anything that differs between environments — endpoints, limits, toggles — belongs in configuration, not baked into the code. The result is one artifact you can promote anywhere, and code that makes no assumptions about which environment it is in.

Keeping secrets out of the repository

For local development, a dotenv file holds your values and is listed in the ignore file so it never gets committed — with a checked-in example file that lists the names but not the values, so a new teammate knows what to provide. In deployed environments, secrets come from the platform's own secret store or CI secret settings, injected as environment variables at run time and never written into the repo.

The principle is consistent everywhere: the names of your settings can live in the codebase; the values, especially secret ones, must not. If you can read a real key by browsing the source, so can everyone else who ever sees it.

When a secret leaks anyway

If a key does land in a commit, treat it as compromised the moment it was pushed — do not just delete the line. Rotate it: generate a new key at the provider and revoke the old one, so the exposed value stops working. Removing it from the latest commit is not enough, because it still lives in the repository's history and in any clone.

Rotation is the real remediation; scrubbing history is secondary cleanup. Build the habit of rotating first and cleaning second, and add a secret scanner to your pipeline so the next near-miss is caught before it is ever pushed. Prevention is cheaper than every response.