All posts
5 min read

Managing Environment Variables Across Vercel, Convex, and Railway

guidesenv-varsvercelconvex

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

  1. Stale values -- You rotate STRIPE_SECRET_KEY in Vercel but forget to update Convex. Webhooks start failing silently.
  2. Missing variables -- A new RESEND_API_KEY gets added to production but never makes it to the development environment.
  3. Inconsistent naming -- One platform has DB_URL, another has DATABASE_URL. Now you're debugging a typo that isn't even a typo.
  4. No audit trail -- Nobody knows who changed what, when, or in which platform. Your .env.local has 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

$ dotenvy init

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: test

The 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/mydb

Each 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 live

This 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 targets

dotenvy 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 production

This 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 init

Add your targets to dotenvy.yaml, set your values with dotenvy set, and you'll never copy-paste a secret between dashboards again.