---
title: "Secrets Management: Stop .env Sprawl Across Environments"
slug: "secrets-management-stop-env-sprawl"
published: "2026-07-14"
updated: "2026-07-28"
validated: "2026-07-28"
categories:
  - "Next.js"
tags:
  - "secrets management"
  - "secrets manager"
  - "manage environment variables"
  - "Infisical"
  - "SOPS"
  - "HashiCorp Vault"
  - "Vercel environment variables"
  - "Next.js secrets"
  - "secret sprawl"
  - "secret-zero problem"
  - "secrets rotation"
llm-intent: "reference"
audience-level: "intermediate"
framework-versions:
  - "next.js"
  - "payload cms"
  - "infisical"
  - "vercel"
  - "aws secrets manager"
status: "stable"
llm-purpose: "Secrets management: centralize env vars and stop .env sprawl across Vercel, VPS, CI, and local dev. Compare Infisical, Vault, SOPS, and cloud options…"
llm-prereqs:
  - "Access to Next.js"
  - "Access to Payload CMS"
  - "Access to Infisical"
  - "Access to Vercel"
  - "Access to AWS Secrets Manager"
llm-outputs:
  - "Completed outcome: Secrets management: centralize env vars and stop .env sprawl across Vercel, VPS, CI, and local dev. Compare Infisical, Vault, SOPS, and cloud options…"
---

**Summary Triples**
- (.env files, fail to scale when, projects grow beyond one developer and one server (cause sprawl, drift, and secret leakage))
- (Central secrets manager, should be, single source of truth for all environments and identities)
- (Secrets delivery, should occur, at runtime into process.env (not via copied .env files))
- (Access model, must separate, human and machine identities and use scoped RBAC and short-lived credentials)
- (Managed platform env vars (e.g., Vercel), are, fast to adopt but limited to that platform and can complicate cross-environment consistency)
- (Cloud-native secret managers, offer, managed rotation, audit logs, and integrations for cloud-hosted services)
- (Infisical and similar tools, provide, cross-platform sync, developer CLI, and managed/self-hosted options for multi-host setups)
- (SOPS, is, a Git-friendly, encrypted-secrets strategy that requires KMS/PGP for key management)
- (HashiCorp Vault (self-hosted), is best when, you need full control, custom auth methods, or dynamic secrets)
- (Secret-zero, is mitigated by, using short-lived bootstrap tokens and ephemeral identities rather than static master secrets)
- (Migration, requires, inventorying secrets, picking a manager, creating identities/policies, implementing runtime delivery, and rotating/retiring .env files)
- (Local dev, should use, per-developer credentials or a secure CLI sync (not committed .env files))
- (CI/CD and hosts, should fetch secrets via, provider integrations, short-lived auth, or sidecar agents to avoid baked-in secrets)

### {GOAL}
Secrets management: centralize env vars and stop .env sprawl across Vercel, VPS, CI, and local dev. Compare Infisical, Vault, SOPS, and cloud options…

### {PREREQS}
- Access to Next.js
- Access to Payload CMS
- Access to Infisical
- Access to Vercel
- Access to AWS Secrets Manager

### {STEPS}
1. Inventory current environment variables
2. Separate secrets from public configuration
3. Choose the right secret store
4. Create projects and environments centrally
5. Migrate local development first
6. Integrate CI and staging deployments
7. Test build-time vs runtime variables
8. Migrate production and remove old files
9. Rotate shared credentials and revoke access
10. Plan recovery and auditing

<!-- llm:goal="Secrets management: centralize env vars and stop .env sprawl across Vercel, VPS, CI, and local dev. Compare Infisical, Vault, SOPS, and cloud options…" -->
<!-- llm:prereq="Access to Next.js" -->
<!-- llm:prereq="Access to Payload CMS" -->
<!-- llm:prereq="Access to Infisical" -->
<!-- llm:prereq="Access to Vercel" -->
<!-- llm:prereq="Access to AWS Secrets Manager" -->
<!-- llm:output="Completed outcome: Secrets management: centralize env vars and stop .env sprawl across Vercel, VPS, CI, and local dev. Compare Infisical, Vault, SOPS, and cloud options…" -->

# Secrets Management: Stop .env Sprawl Across Environments
> Secrets management: centralize env vars and stop .env sprawl across Vercel, VPS, CI, and local dev. Compare Infisical, Vault, SOPS, and cloud options…
Matija Žiberna · 2026-07-14

The fix for secret sprawl is a central secrets manager that becomes the single source of truth for every environment, gives humans and machines separate scoped identities, and delivers values into `process.env` at runtime instead of living inside copied `.env` files. This guide walks through why `.env` files stop scaling once a project grows past one developer and one server, what a proper secrets-management setup provides, and how to choose between platform variables, cloud-native secret managers, managed cross-platform tools, self-hosted options like Infisical, and Git-based encryption with SOPS.

I run a mix of Next.js and Payload CMS projects for different clients, and almost none of them live on a single host anymore. A typical engagement now spans a Vercel-hosted frontend, a self-hosted Payload instance on a VPS, a managed Postgres database, S3-compatible storage, and an n8n automation server, each with its own staging and production credentials. Once I hit four or five projects with that shape, keeping track of which `.env.production` file had the current database password stopped being realistic. This article covers the reasoning behind moving to a real secrets manager, and the next one in the series covers the actual Infisical setup for Next.js and Payload.

## The problem starts small and stays small for a while

Every project begins the same way. You create a `.env` file, drop in a database connection string, generate an application secret, paste in a few API keys, and start building.

```dotenv
# File: .env.local
DATABASE_URL=postgresql://localhost/my-app
PAYLOAD_SECRET=development-secret
S3_ACCESS_KEY_ID=example
S3_SECRET_ACCESS_KEY=example
```

For one developer running one application on one machine, this setup does the job completely.

The trouble shows up once the application grows a staging environment, a production server, a second developer, a CI pipeline, background workers, or a scheduled job hosted somewhere else. Copying `.env` files between machines works for a while, and then it quietly turns into a liability, because nobody can say with confidence which file on which machine holds the value that is actually live in production.

## Environment variables are a delivery mechanism, not a management system

An environment variable is just a value handed to a running process. In a Node.js app you read it through `process.env`:

```ts
// File: src/lib/env.ts
const databaseUrl = process.env.DATABASE_URL
const payloadSecret = process.env.PAYLOAD_SECRET
```

The application itself has no idea where that value came from. It could have arrived through a local `.env` file, Docker Compose, a systemd unit, Vercel, Cloudflare, AWS Secrets Manager, a CI pipeline, or a self-hosted secrets manager. `process.env` looks identical either way.

That's the part worth internalizing early: environment variables describe how configuration reaches your code. The governance around where that configuration is stored, who can see it, and how it gets rotated is a separate concern that a `.env` file was never built to handle.

## Configuration and secrets need different treatment

Not every environment variable deserves the same level of protection. Values like these are normal configuration:

```dotenv
# File: .env
APP_NAME=CanPrev
DEFAULT_LOCALE=en
LOG_LEVEL=info
PUBLIC_SITE_URL=https://www.example.com
```

These can live in Git if the team wants them versioned alongside the code. Values like these are secrets:

```dotenv
# File: .env (do not commit)
DATABASE_URL=postgresql://user:password@database/app
PAYLOAD_SECRET=...
BREVO_API_KEY=...
S3_SECRET_ACCESS_KEY=...
STRIPE_SECRET_KEY=...
```

A secret grants access, signs data, decrypts something, or proves identity. Someone who gets hold of one can reach a database, send email under your name, modify storage, or impersonate a service. Configuration benefits from visibility and version history. Secrets need restricted access, encryption, rotation, and a controlled delivery path. Managing both categories through a pile of manually copied `.env` files tends to blur that distinction until it disappears entirely.

## How `.env` files turn into secret sprawl

There's nothing wrong with using a `.env` file for local development. The trouble is relying on it as the source of truth once the system looks like this:

```text
Application
├── Developer laptop
├── Staging VPS
├── Production VPS
├── CI pipeline
├── Background worker
└── n8n automation
```

Each of those needs some combination of `DATABASE_URL`, `PAYLOAD_SECRET`, `REDIS_URL`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `SMTP_PASSWORD`, `BREVO_API_KEY`, and `INTERNAL_WEBHOOK_SECRET`. Without a central system, the same secrets end up copied into `.env.local`, `.env.staging`, `.env.production`, `docker-compose.yml`, GitHub Actions secrets, VPS shell configuration, systemd service files, deployment scripts, password managers, Slack messages, and internal docs. At that point nobody can say for certain which copy is current. That's secret sprawl, and it grows quietly because each individual copy felt reasonable when someone made it.

## The failure modes are mostly boring, not dramatic

Committing a production `.env` file to Git gets attention because it's an obvious mistake, and it's a real one since the secret can linger in repository history and in clones long after the file is deleted. Most of the damage from poor secret management shows up in quieter, everyday ways instead.

**Environment drift.** Staging uses `S3_BUCKET=project-staging` while production uses `AWS_BUCKET=project-production`, and a deployment that works fine in staging fails in production because the configuration model drifted without anyone noticing.

**Unknown ownership.** An API key stops working and nobody can say who created it, which account owns it, which services depend on it, or whether rotating it will break something else.

**Stale access.** A contractor leaves the project but still has last quarter's production `.env` file sitting on a laptop. Revoking a GitHub permission does nothing to claw back credentials that were already copied elsewhere.

**Shared production credentials.** Several servers and developers all authenticate with the same database password or storage key, so revoking access for one person means rotating a credential everyone depends on.

**Unreliable deployments.** A new VPS goes up and someone has to rebuild its configuration from an old server, a password manager, and a scattering of chat messages, which makes the deployment depend on memory rather than a repeatable process.

**Inconsistent local environments.** A developer loses hours debugging an app before realizing their `.env.local` still points at an endpoint that was decommissioned months ago.

**Build-time versus runtime confusion.** This one shows up constantly in Next.js. A server-only value like `DATABASE_URL` gets injected when the process starts. A value prefixed with `NEXT_PUBLIC_` gets baked into the client bundle at build time, and needs to exist when `next build` runs rather than when the server starts. Treating both the same way produces broken deployments and occasionally leaks a secret into a public bundle.

## What a proper secrets-management setup actually provides

A good system does more than hide values behind a login. It builds a workflow around storing, accessing, and delivering them.

A single authoritative location holds each secret, so instead of asking a teammate to send over the current production `.env`, everyone opens the project's production environment inside the secrets manager.

Environments stay explicitly separated:

```text
My application
├── Development
├── Staging
└── Production
```

The same key can exist in each environment with a different value:

```text
Development
DATABASE_URL=postgresql://localhost/app

Staging
DATABASE_URL=postgresql://staging-database/app

Production
DATABASE_URL=postgresql://production-database/app
```

Humans and machines get distinct identities. A developer might have permission to view and edit development secrets. A production server authenticates as itself with read-only access to production secrets, rather than borrowing a developer's personal login.

Access follows least privilege: a CanPrev staging server can read CanPrev staging secrets and nothing beyond that, so compromising one server doesn't hand over every other project the team runs.

Access is revocable per identity. When a server gets replaced or a teammate leaves, that one identity gets revoked without rotating every credential across the organization.

Delivery stays simple on the application side. Code keeps reading `process.env.DATABASE_URL` exactly as before; the secrets manager handles fetching the right value and injecting it when the process starts, so the application never gets coupled to a specific secrets platform.

Rotation and history are tracked, so the team knows which value is current and which services need a restart when it changes. More advanced platforms add version history, approval workflows, audit logs, and automated rotation on top of that.

Recovery gets planned for too. The secrets manager becomes important infrastructure in its own right, and its encryption keys, database, and backups need a recovery plan just like anything else you'd call critical.

## What this looks like once it's running

A central setup for a small agency might look like this:

```text
Secrets manager
├── CanPrev website
│   ├── Development
│   ├── Staging
│   └── Production
│
├── CanPrev DAM
│   ├── Development
│   ├── Staging
│   └── Production
│
└── Internal automation
    ├── Development
    ├── Staging
    └── Production
```

A developer authenticates through the CLI and runs:

```bash
secrets-tool run --environment=development -- pnpm dev
```

The development secrets get fetched and injected into that new process. A production VPS authenticates with its own machine identity, something like `canprev-website-production-vps`, scoped to read-only access on the CanPrev website production environment. When the application starts:

```bash
secrets-tool run --environment=production -- node server.js
```

The app still reads `process.env` the same way it always did. There's no custom secret-fetching logic scattered through the codebase.

## You may already be using a secrets manager without the name

Managed hosting platforms made this workflow familiar long before anyone called it "secrets management."

Vercel lets you define variables per development, preview, and production environment, and because it owns the build and runtime, it injects the right values automatically during deployment. That's convenient and usually plenty when the entire application runs on Vercel. It starts to feel thin once the system also includes a self-hosted Payload instance, separate worker servers, n8n, or anything deployed outside Vercel, since Vercel's variable management is scoped to Vercel projects rather than an organization's whole infrastructure.

Cloudflare offers a similar model for Workers and Pages, and it works well for the same reason: Cloudflare owns the runtime that receives the values.

AWS Secrets Manager centralizes secrets and integrates with AWS IAM, so applications authenticate through AWS identities and pull only what they're permitted to see. That's a strong option when most of the infrastructure already runs inside AWS, and the same logic applies to Google Secret Manager and Azure Key Vault. It becomes less convenient once applications are spread across unrelated VPS providers, local machines, and client-owned environments that have no natural AWS identity to authenticate with.

## Comparing the main categories of tools

There's no universally correct choice here. The right tool depends on where your applications run, how granular your access control needs to be, and how much infrastructure your team is willing to operate.

| Approach | Examples | Best suited for |
|---|---|---|
| Hosting-platform variables | Vercel, Cloudflare, Render | Applications running mostly on one hosting platform |
| Cloud-native secret managers | AWS Secrets Manager, Google Secret Manager, Azure Key Vault | Infrastructure concentrated in one cloud provider |
| Managed cross-platform platforms | Doppler, managed Infisical | Teams wanting one hosted system across providers |
| Self-hosted secret managers | Infisical, OpenBao, HashiCorp Vault | Teams wanting provider-neutral control on their own infrastructure |
| Encrypted configuration in Git | SOPS with age or a cloud KMS | Infrastructure-as-code workflows and smaller teams comfortable with Git-based operations |

### Staying with the hosting platform

An application fully hosted on Vercel may not need anything beyond Vercel's own environment variables, since the platform already covers environment separation, build integration, runtime injection, team permissions, and CLI access. Layering an external secrets manager on top can add more overhead than value here. That calculation changes once the system crosses platform boundaries, for example a Next.js frontend on Vercel, Payload on DigitalOcean, a managed Postgres database, S3-compatible storage, and an n8n VPS running automation. At that point secrets live across several dashboards and no single system sees the whole picture.

### Using the cloud provider's secret manager

AWS Secrets Manager, Google Secret Manager, and Azure Key Vault fit naturally for a company already committed to one cloud ecosystem, mainly because of identity integration: an AWS workload can receive an IAM role and pull secrets without a permanent access key sitting on the server. That's a strong security model as long as the workload runs inside that cloud. Pointing an external DigitalOcean or Hetzner server at AWS Secrets Manager works technically, but that server still needs its own secure way to authenticate into AWS, which means the architecture now depends on a cloud provider that isn't hosting the application.

### Using a managed provider-neutral platform

A managed secrets platform can serve as the common source of truth across local development, GitHub Actions, Vercel, DigitalOcean, Hetzner, AWS, Kubernetes, and plain Node.js processes, with the vendor handling the central service, database, updates, and uptime. That takes operational work off your plate in exchange for a subscription cost and an external dependency, which is a reasonable trade for a lot of teams.

### Self-hosting a secrets manager

A self-hosted platform gives you a Vercel-like interface for environments and secrets while staying independent of whatever hosts the application. This fits organizations running multiple applications across different VPS providers who want to keep control of the underlying platform. Infisical, OpenBao, and HashiCorp Vault are the common options here.

Infisical provides projects, environments, a dashboard, a CLI, machine identities, and integrations for delivering secrets into applications, and its mental model tracks closely with Vercel's:

```text
Project
├── Development
├── Staging
└── Production
```

Infisical doesn't host your application; you connect it explicitly to Docker, systemd, CI, or a local dev command. That makes it a practical choice for teams that want a central, provider-neutral secrets manager without taking on the full operational weight of a Vault-style system.

OpenBao and HashiCorp Vault go further, supporting dynamic database credentials, time-limited secret leases, PKI, certificate issuance, encryption services, and advanced authentication policies. Those capabilities matter in larger or more security-sensitive environments, and they come with real design, maintenance, and operational overhead. For a team mainly trying to stop copying `.env` files between VPSs, that overhead is often more infrastructure than the problem calls for.

### Encrypting secrets in Git with SOPS

SOPS skips the central server entirely. You store encrypted configuration files in Git instead:

```text
secrets/
├── development.enc.env
├── staging.enc.env
└── production.enc.env
```

The encrypted files version safely in the repository, while approved developers and servers hold the keys needed to decrypt them, using age, AWS KMS, Google Cloud KMS, Azure Key Vault, or PGP. This suits infrastructure-as-code workflows and smaller teams comfortable operating close to Git. Its friction shows up operationally: managing access, revoking machines, rotating encryption recipients, and delivering secrets dynamically all take more manual effort than a central platform handles for you. SOPS also pairs well alongside a full secrets manager, protecting the bootstrap credentials needed to deploy that manager in the first place.

## How to choose

The simplest rule is to pick the system closest to where your application already runs. Reach for platform environment variables when the application runs entirely on one managed platform and you don't need a central view across external services. Reach for a cloud-native secrets manager when most workloads sit in one cloud provider and its identity system already matters more to you than portability. Reach for a managed provider-neutral platform when applications span several providers, you'd rather not operate the secrets service yourself, and the subscription cost is acceptable. Reach for a self-hosted secrets manager when applications span multiple VPSs, you want one central source of truth that you own, and you're prepared to operate and back it up. Reach for SOPS when Git already anchors your infrastructure workflow, deployments happen through controlled changes, and your team is comfortable managing encryption keys directly.

## Why Infisical fits the projects I run

Most of what I build now includes more than one deployment target: a Next.js frontend, Payload CMS, Postgres, Redis, object storage, background jobs, n8n, external APIs, a staging VPS, a production VPS, and local development, often owned by different clients and hosted by different providers. The Vercel-style mental model of projects and environments is still the right one, it just needs to exist independently of Vercel itself.

Infisical fits that gap well. It can be self-hosted, gives you a central dashboard, separates projects and environments cleanly, issues separate identities to humans and servers, and its CLI injects variables into ordinary processes. Next.js and Payload keep reading `process.env` exactly as before, and none of it requires the application to run on a particular cloud.

The workflow ends up feeling familiar. Locally:

```bash
infisical run --env=dev -- pnpm dev
```

On staging:

```bash
infisical run --env=staging -- node server.js
```

In production:

```bash
infisical run --env=prod -- node server.js
```

The application code stays conventional:

```ts
// File: src/lib/env.ts
const databaseUrl = process.env.DATABASE_URL
```

Infisical handles where that value lives, who can see it, and which environment gets it.

## The secret-zero problem doesn't fully disappear

There's one detail worth naming directly. A generic VPS still needs some initial way to authenticate to the secrets manager itself. Rather than storing every application secret locally, the server holds one restricted machine credential:

```dotenv
# File: .env (VPS bootstrap only)
INFISICAL_CLIENT_ID=...
INFISICAL_CLIENT_SECRET=...
```

That identity might be scoped to read-only access on the CanPrev website production environment and nothing else. This is called the secret-zero problem, and it can't be eliminated entirely on an ordinary VPS. It can be narrowed down to one revocable, tightly scoped credential, which is a far smaller blast radius than a complete production `.env` file carrying database, storage, email, and API credentials all at once.

## What still belongs in the repository

Moving to a secrets manager doesn't mean every setting leaves Git. Keep a `.env.example` documenting the variables the application expects:

```dotenv
# File: .env.example
DATABASE_URL=
PAYLOAD_SECRET=
S3_BUCKET=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
BREVO_API_KEY=
```

Use names and safe placeholder values here, never real credentials. Non-sensitive configuration can stay in the repo too:

```dotenv
# File: .env
DEFAULT_LOCALE=en
LOG_LEVEL=info
FEATURE_SEARCH_ENABLED=true
```

The point isn't hiding every setting from Git. It's centralizing the sensitive values and building a predictable, repeatable way to deliver them.

## A migration path that doesn't require a big-bang cutover

Moving to a secrets manager works best as a sequence rather than one large infrastructure change:

1. Inventory the current environment variables.
2. Separate secrets from public configuration.
3. Identify where every current value is stored today.
4. Create development, staging, and production environments centrally.
5. Migrate local development first.
6. Integrate the staging deployment.
7. Test application restarts and failed secret retrieval.
8. Integrate production.
9. Remove old plaintext files.
10. Rotate credentials that were previously shared widely.

Start in a non-critical environment and confirm the workflow holds up before touching production. Importing a `.env` file into a new dashboard is the easy part; the actual improvement comes from defining ownership, access boundaries, rotation, and recovery around it.

## FAQ

**Do I need a secrets manager if I only deploy to Vercel?**
Probably not yet. Vercel already separates development, preview, and production variables and injects them automatically at build and runtime. A separate secrets manager earns its place once your infrastructure spreads beyond what Vercel controls, like a self-hosted Payload instance or a background worker on its own VPS.

**What's the difference between a secret and regular configuration?**
A secret grants access, signs data, decrypts something, or proves identity, so exposure lets someone reach a resource they shouldn't. Configuration values like a locale code or a log level carry no access risk on their own and can usually live safely in Git.

**Can NEXT_PUBLIC_ variables come from a secrets manager too?**
They can, but remember that anything prefixed `NEXT_PUBLIC_` gets baked into the client bundle at build time rather than read at runtime. Your secrets manager needs to inject those values into the `next build` step specifically, not just into the running server process.

**Does self-hosting Infisical remove all local secrets from my servers?**
No. Each server still needs one bootstrap credential to authenticate to Infisical itself, which is the secret-zero problem. What changes is the blast radius: that one credential can be scoped to read-only access on a single environment, instead of a full `.env` file carrying every credential the application uses.

**Is SOPS a replacement for a secrets manager like Infisical or Vault?**
It can be, especially for smaller teams already running an infrastructure-as-code workflow through Git. It can also complement a full secrets manager by protecting the bootstrap credentials needed to deploy that manager in the first place.

## Wrapping up

Copying `.env` files between machines feels harmless right up until a second developer, a staging server, or a CI pipeline enters the picture, at which point it turns into an undocumented deployment process with weak access control. A proper secrets-management setup gives every environment a defined source of truth, lets developers authenticate as themselves and servers authenticate as machines, and grants each identity only the access it actually needs. The application code doesn't change; it keeps reading `process.env` exactly as before. What changes is how those values get governed and delivered.

The next article in this series moves from strategy to implementation: self-hosting Infisical on a VPS, setting up projects and environments, creating human and machine identities, connecting a local Next.js and Payload dev environment, injecting secrets into Docker and systemd, separating build-time from runtime variables, and migrating away from existing production `.env` files.

Let me know in the comments if you have questions, and subscribe for more practical development guides.

Thanks, Matija

## LLM Response Snippet
```json
{
  "goal": "Secrets management: centralize env vars and stop .env sprawl across Vercel, VPS, CI, and local dev. Compare Infisical, Vault, SOPS, and cloud options…",
  "responses": [
    {
      "question": "What does the article \"Secrets Management: Stop .env Sprawl Across Environments\" cover?",
      "answer": "Secrets management: centralize env vars and stop .env sprawl across Vercel, VPS, CI, and local dev. Compare Infisical, Vault, SOPS, and cloud options…"
    }
  ]
}
```