Your .env file has your database password, API keys, and AWS credentials. It’s committed to git (or worse, deployed via scp). Here’s how we switched to GCP Secret Manager in a Node.js app — and why it’s worth the 15 minutes of setup.
The Problem With .env
- PM2 caches env vars in memory. Changing
.envdoes nothing until you restart. - Secrets in git history. Even if you
.gitignoreit, one accidental commit and it’s in history forever. - No audit trail. Who changed the API key? When? No way to know.
- No versioning. Rotated a key? Hope you remembered the old one.
The Setup (15 Minutes)
1. Create secrets in GCP
echo -n "your-database-url" | gcloud secrets create DATABASE_URL --data-file=-
echo -n "your-api-key" | gcloud secrets create API_KEY --data-file=-
2. Load at startup
// SecretsService.ts
const MANAGED_SECRETS = [
'DATABASE_URL',
'REDIS_URL',
'AWS_ACCESS_KEY_ID',
'AWS_SECRET_ACCESS_KEY',
'STRIPE_SECRET_KEY',
// ... 16 secrets total
];
export class SecretsService {
static async preloadSecrets(): Promise<void> {
const client = new SecretManagerServiceClient();
for (const name of MANAGED_SECRETS) {
const [version] = await client.accessSecretVersion({
name: `projects/my-project/secrets/${name}/versions/latest`
});
process.env[name] = version.payload.data.toString();
}
}
}
3. Call before anything else
// bootstrap.ts
await SecretsService.preloadSecrets();
// Now process.env has all secrets
// Start Express, connect to DB, etc.
What You Get
- Versioning. Every secret change creates a new version. Roll back anytime.
- Audit trail. GCP logs every access.
- No .env in production. Secrets live in GCP, not on disk.
- Rotation without restart. Update the secret, restart PM2, done.
Cost
GCP Secret Manager: $0.06 per 10,000 access operations. With 16 secrets loaded once at startup, it’s essentially free.
This runs at MisuJob, managing 16 production secrets for database, AWS SES, Stripe, and more.
How do you manage secrets in production? Vault? AWS SSM? .env? Let me know.

