Software Engineering·July 21, 2026·6 min read

Secrets and Config Management

Every app has settings that change between environments and sensitive values that must never leak. This guide explains the difference between config and secrets, where each should live, and how to manage rotation.

devopssecretsconfigurationsecurityengineering

Every app needs two kinds of values it can't hardcode: configuration (which database to connect to, which feature flags are on) and secrets (API keys, passwords, signing keys). Getting this right early prevents some of the most common and most damaging security mistakes.

If Cartara flagged this in your diff, you likely added environment variable reads, a .env file, or changed how your app loads configuration.


Config vs. Secrets

Configuration is any value that varies by environment: the database URL, the log level, which API endpoint to use, feature toggles. Not necessarily sensitive, but shouldn't be hardcoded.

Secrets are the subset of config that would cause harm if exposed: API keys, database passwords, signing keys, OAuth client secrets. These need extra protection — encryption, access control, and rotation.

Both should live outside your code. The difference is that secrets need stronger safeguards.


The Core Rule: Store Config in Environment Variables

A widely-followed principle says: store config in environment variables, not in your code.

The reason is that the same built artifact should deploy to dev, staging, and production without modification — only the environment around it changes. If your database URL is hardcoded, you have to rebuild for each environment.

In practice, your app reads values like this:

```ts
const databaseUrl = process.env.DATABASE_URL;
const stripeKey = process.env.STRIPE_SECRET_KEY;
const logLevel = process.env.LOG_LEVEL ?? 'info';
```


The .env File

Locally, developers keep config in a .env file that the app loads on startup:

```
DATABASE_URL=postgres://localhost/myapp_dev
STRIPE_SECRET_KEY=sk_test_abc123
LOG_LEVEL=debug
```

The non-negotiable rule: .env must be in .gitignore and never committed to Git.

Committing a .env with real secrets is one of the most common ways credentials leak — and Git remembers everything, so even a deleted-and-recommitted secret is still in the history and must be rotated immediately.

Commit a .env.example with the keys but placeholder values so teammates know what's needed:

```

.env.example

DATABASE_URL=your-database-url
STRIPE_SECRET_KEY=your-stripe-key
```


Where Secrets Should Live

EnvironmentWhere secrets go
Local devA .env file, gitignored
Hosting platformsThe platform's encrypted environment variables UI (Vercel, Railway, Render)
CI/CD pipelineThe platform's encrypted secret store — never in the workflow YAML
Larger setupsA dedicated secrets manager (HashiCorp Vault, AWS Secrets Manager, Doppler)

A dedicated secrets manager is worth it when you have many secrets, multiple services, or compliance requirements. It centralises storage, encrypts at rest, controls access, and logs who read what.


What You'll See in Your Code

Secrets and config management typically shows up in diffs as:

Reading from environment variables:
```ts
const config = {
databaseUrl: process.env.DATABASE_URL!,
jwtSecret: process.env.JWT_SECRET!,
stripeKey: process.env.STRIPE_SECRET_KEY!,
};
```

A .env.example file being added or updated with new required variables.

Validation at startup — catching missing required variables before the app starts:
```ts
const required = ['DATABASE_URL', 'JWT_SECRET', 'STRIPE_SECRET_KEY'];
for (const key of required) {
if (!process.env[key]) throw new Error(Missing required env var: ${key});
}
```


Rotation

Secrets should be rotatable — changeable without a painful redeploy — and should be rotated periodically, and immediately if one might have been exposed.

  • A leaked-but-rotated secret is harmless. A leaked long-lived secret is a standing risk.
  • Rotation is much easier if you designed for it (secrets read from the environment, not hardcoded).

Where possible, prefer short-lived credentials (cloud IAM roles, OIDC tokens) over long-lived static keys. A credential that expires in an hour is far less dangerous to leak.


The Principle of Least Privilege

Every secret should grant the minimum access needed. A key used only to read from one storage bucket shouldn't also be able to delete your database. Scope credentials tightly so a single leaked secret can't compromise everything.


Related concepts

Rate Limiting and Throttling
Rate limiting caps how many requests a client can make in a given window. It protects against abuse, runaway costs from paid APIs, and accidental self-inflicted overload.
Observability Deep Dive
The three pillars of observability in depth — logs, metrics, traces — plus OpenTelemetry, correlation IDs, and SLO-based alerting.
SRE and Incident Management
Site Reliability Engineering — how to set reliability targets, manage incidents calmly, and run blameless postmortems so reliability compounds over time.

Turn shipping into understanding

Cartara measures what your team actually learns from every AI coding session.

Join the waitlist