Secrets Management Beyond .env Files
.env files are fine on a laptop and fragile everywhere else. Where secrets should live in production and CI, secret managers vs platform env vars, OIDC to remove long-lived CI credentials, rotation, least privilege, keeping secrets out of logs and AI agent context, and a practical maturity path.
Every project starts with a .env file, and that's fine. The trouble starts when .env becomes the system: pasted into Slack for a new teammate, copied onto a server by hand, stored in a CI variable nobody remembers creating, and readable by every process — including your AI coding agent. (What Is an Environment Variable? covers the basics.)
What "managed" secrets means
A mature setup answers five questions for every secret:
- Where is the source of truth? One place, not five copies.
- Who and what can read it? Least privilege — per environment, per service.
- How does it reach the running app? Without being written into images, repos, or logs.
- How is it rotated? Ideally without downtime, and quickly after a leak.
- Who accessed or changed it? An audit trail.
.env files answer none of these.
Where secrets should live
Local development
.env (git-ignored) with development-only credentials: test-mode Stripe keys, a local database, a dev AI key with a low spending cap. Never production values on a laptop. Commit .env.example with names only. (What Is .gitignore?.)
Better: pull dev secrets from the secret manager with a CLI (op run, doppler run, infisical run, vault), so nothing sits on disk in plain text and onboarding is one login.
Production
Two reasonable tiers:
- Your platform's encrypted environment variables. Most PaaS and container platforms store them encrypted and inject them at runtime. For many small apps this is enough — provided access to the platform is itself locked down (SSO, MFA, few admins).
- A dedicated secret manager: AWS Secrets Manager or SSM Parameter Store, Google Secret Manager, Azure Key Vault, HashiCorp Vault/OpenBao, 1Password, Doppler, Infisical. You gain central management across services and environments, fine-grained access policies, audit logs, versioning, and rotation hooks.
CI
Use the CI system's encrypted secrets, scoped to environments with protection rules (e.g. production secrets only available to jobs on main, after approval). And where possible, don't store cloud credentials at all — see OIDC below. (GitHub Actions CI Basics.)
Don't bake secrets into images
# ❌ Ends up in an image layer forever
ENV STRIPE_SECRET_KEY=sk_live_...
COPY .env .
Anyone who can pull the image can extract it. Inject at runtime instead. If a secret is needed at build time (a private registry token), use BuildKit secret mounts, which aren't persisted in layers:
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci
Add .env* to .dockerignore. (Writing a Production Dockerfile for a Node.js App.)
(Framework caveat: some values are legitimately needed at build time and become public — NEXT_PUBLIC_*, VITE_*. Anything with those prefixes must never be secret.)
OIDC: no long-lived CI credentials
Instead of storing an AWS access key in GitHub, configure your cloud provider to trust GitHub's OIDC identity tokens. Each workflow run requests a short-lived token, scoped to a specific repository, branch, or environment, and exchanges it for temporary cloud credentials. Nothing long-lived to leak. GitHub Actions supports this for AWS, Google Cloud, Azure, and others, and many secret managers accept the same tokens.
Least privilege
- Separate secrets per environment. Staging must not hold production credentials.
- Scope API keys to the permissions needed: restricted Stripe keys, read-only database roles for reporting, cloud IAM roles per service.
- Separate database users for the app, migrations, and humans. The app user rarely needs
DROP. - Spending limits on anything billed per use. (How to Stop Bots From Running Up Your AI App's Bill.)
Rotation
Rotation is only real if it's rehearsed. For each secret, know:
- How to issue a new one and deploy it.
- Whether old and new can overlap (most APIs allow two active keys) so rotation needs no downtime.
- Who gets notified if it's rotated in an emergency.
Rotate on a schedule for high-value secrets, and immediately on any suspected exposure or when someone with access leaves. (I Leaked an API Key. What Now?.)
Better still, prefer short-lived credentials that don't need rotating: OIDC for CI, cloud IAM roles for workloads, database credentials issued dynamically by Vault.
Keep secrets out of logs, errors, and telemetry
- Don't log whole request headers, env objects, or config dumps.
- Redact known sensitive keys (
authorization,password,token,secret,cookie) in your logger. (Structured Logging.) - Scrub error reports — error trackers capture local variables and request bodies by default in some SDKs.
- Watch for secrets in URLs (query strings end up in access logs).
Secrets and AI coding agents
A coding agent that can run shell commands can read anything the process can: .env, ~/.aws/credentials, ~/.ssh, and every environment variable. And its context may be influenced by untrusted content. (Prompt Injection and Coding Agents.)
- Give agents development credentials only, never production.
- Deny reads of secret files in the agent's permission rules — e.g.
Read(./.env)in Claude Code's deny list. (Claude Code Permission Modes.) - Run agents in an isolated environment that doesn't contain your personal credential files at all.
- Restrict outbound network access so a leaked secret has nowhere to go. (Agent Egress Control.)
Scanning
Layer detection: push protection on your Git host, a pre-commit scanner (gitleaks, trufflehog), and periodic history scans. Scanners catch the common key formats; they won't catch your database password, so the structural fixes above still matter.
A maturity path
.envgit-ignored,.env.examplecommitted, platform env vars in production, push protection on.- Separate dev/staging/prod credentials; scoped keys; spending limits.
- A secret manager as the source of truth; CLI injection locally; CI environments with protection rules.
- OIDC for CI; IAM roles for workloads; rehearsed rotation; audit logs reviewed.
Most small teams should be at step 2 before launch and step 3 once there's more than one service or person.
EasySpawn stores each project's secrets as server-side environment variables inside an isolated workspace — separate from your laptop's credentials and from every other project — so Claude Code gets the dev keys it needs and nothing else. See how it works or join the waitlist.
Related: How to Keep API Keys Out of an AI-Built App · I Leaked an API Key. What Now? · npm Supply Chain Security
Keep reading
Where Should User Uploads Go? Object Storage Explained
Profile photos that vanish after a deploy, a database bloated with images, a public bucket full of private documents. Where uploaded files should live, how object storage works, and the three decisions — public or private, who uploads, and how files are served — that keep uploads fast and safe.
Reverse Proxies Explained: Nginx, Caddy, and Traefik in Front of Your App
A reverse proxy sits between the internet and your app, handling TLS, routing, compression, and more. What reverse proxies do, how Nginx, Caddy, and Traefik differ, forwarded headers and trusting the real client IP, WebSockets and streaming, timeouts and body limits, and common 502/504 causes.