Managing Environment Variables Across Vercel, Convex, and Railway
If you deploy to more than one platform, you've hit this problem: your Stripe key lives in Vercel, your database URL lives in Convex, and your local .env file is three days stale. You update one, forget to update the others, and spend an hour debugging why staging is broken.
This is the reality of multi-platform development. And copy-pasting between dashboards doesn't scale.
The multi-platform problem
Modern stacks are rarely single-provider. A typical setup might look like:
- Vercel for the Next.js frontend
- Convex for the backend and database
- Railway for background workers or a separate API
Each platform has its own dashboard for environment variables. Each has its own concept of environments (Vercel splits into development, preview, and production; Convex uses deployment names; Railway uses service-level variables).
When you rotate a secret or add a new integration, you're logging into three dashboards, pasting the same value, and hoping you didn't miss one. When a new team member joins, they need to collect variables from each platform separately.
What typically goes wrong
- Stale values -- You rotate
STRIPE_SECRET_KEYin Vercel but forget to update Convex. Webhooks start failing silently. - Missing variables -- A new
RESEND_API_KEYgets added to production but never makes it to the development environment. - Inconsistent naming -- One platform has
DB_URL, another hasDATABASE_URL. Now you're debugging a typo that isn't even a typo. - No audit trail -- Nobody knows who changed what, when, or in which platform. Your
.env.localhas values from last month.
A better approach: declare, set, sync
dotenvy takes a different approach. Instead of treating each platform as an independent island, you declare which secrets your project needs in a single config file, keep the values in local .env files, and push them to every platform with one command.
Step 1: Initialize your config
This creates a dotenvy.yaml file in your project root:
version: 2
secrets: []
targets: {}Step 2: Add your targets
Define where your secrets need to go. Each target maps your local environments (test and live) to the platform's native environment names.
version: 2
secrets:
- STRIPE_SECRET_KEY
- STRIPE_WEBHOOK_SECRET
- DATABASE_URL
- RESEND_API_KEY
targets:
vercel:
type: vercel
project: my-app
mapping:
development: test
preview: test
production: live
convex:
type: convex
deployment: my-app-dev
mapping:
default: testThe mapping section is the key concept. It tells dotenvy: "When I sync test, push to Vercel's development and preview environments, and to Convex's default deployment."
Step 3: Set values and sync
dotenvy set STRIPE_SECRET_KEY=sk_test_xxx
dotenvy set DATABASE_URL=postgres://localhost:5432/mydbEach set command writes the value to your .env.test file and syncs it to every target that maps to the test environment. One command, all platforms updated.
For production values:
dotenvy set STRIPE_SECRET_KEY=sk_live_xxx --env liveThis writes to .env.live and syncs to every target mapped to live (Vercel production, etc.).
Step 4: Sync everything at once
When you want to push all tracked secrets to all targets:
dotenvy sync test # Push .env.test to development targets
dotenvy sync live # Push .env.live to production targetsdotenvy reads the values from your local file and pushes each one to the platform's API. Vercel secrets go through the Vercel API. Convex secrets go through the Convex API. There is no intermediary server.
Pulling secrets from remote
Sometimes you need to go the other direction -- a teammate set up a new secret directly in the Vercel dashboard and you need to pull it locally.
dotenvy pull vercel --env productionThis fetches all tracked secrets from Vercel's production environment and writes them to your .env.live file. Only secrets listed in dotenvy.yaml are pulled, so you don't accidentally overwrite unrelated variables.
Platform-specific secrets
Not every secret needs to go everywhere. Maybe CONVEX_DEPLOY_KEY only matters for Convex, and VERCEL_TOKEN only matters for the Vercel target.
dotenvy's auth credentials are resolved from environment variables on your machine, not from the synced secrets themselves. Your VERCEL_TOKEN is read from your shell environment (or a .env file in your home directory) and used to authenticate API calls. It's never pushed to any target.
The trust model
dotenvy has no server. Your secrets live in .env.test and .env.live on your local machine, and they're pushed directly to each platform's API over HTTPS. The dotenvy.yaml config file is safe to commit -- it only contains secret names, not values.
This means:
- No third-party breach surface
- No subscription for storing secrets
- No network dependency beyond the platforms you already use
If you can curl the Vercel API, dotenvy can sync your secrets.
Getting started
curl -fsSL https://dotenvy.dev/install.sh | sh
dotenvy initAdd your targets to dotenvy.yaml, set your values with dotenvy set, and you'll never copy-paste a secret between dashboards again.