How to sync environment variables with Vercel
Vercel gives every project three separate sets of environment variables and no way to tell whether they still agree with each other — or with the .env on the laptop of whoever set them up. The drift is silent until a preview deploy behaves differently from production.
Why Vercel environments drift
A Vercel project stores variables against three targets — Production, Preview and Development — and each one is edited independently. That is a reasonable design, and it produces a specific failure mode:
- Someone adds a key to Production during an incident and never backfills Preview.
- A rotated credential is updated in the dashboard, and the local
.envthat everyone copied months ago keeps the old one. - A new hire gets a
.envfrom a teammate, which is a snapshot of that teammate's machine on the day they sent it. - Nothing anywhere records who changed a value, or when, or what it used to be.
The dashboard is the source of truth for what is deployed, but it is not a source of truth for the team — you cannot diff it, you cannot see its history, and you cannot hand someone read access to only the development set.
Pull what Vercel already has into one place
The first move is not to start retyping keys. Import what is there, so the starting state is real rather than reconstructed.
Create a Vercel access token from your account settings, then link the directory and import. Sink uses the token for that one request and never stores it:
sink login
sink init # writes .sink.json — ids only, safe to commit
sink import vercel development \
--project my-app \
--provider-env development \
--dry-run
--dry-run prints exactly what would be created or changed and stops.
Read that list before you run it for real — it is the cheapest moment to notice
that the project you named is not the project you meant.
Then drop the flag to apply it, and repeat for the other targets into their own Sink environments:
sink import vercel development --project my-app --provider-env development
sink import vercel preview --project my-app --provider-env preview
sink import vercel production --project my-app --provider-env production
Keys that already exist are skipped by default. Pass --mode overwrite
when you want Vercel's value to win — each overwrite mints a new version rather
than destroying the old one, so an import that turns out to be wrong is
recoverable.
Prefer not to paste the token into your shell history? It can come from stdin or the environment instead:
sink import vercel development --project my-app --token -
# or
export SINK_VERCEL_TOKEN=...
sink import vercel development --project my-app
Day-to-day: pull, diff, push
Once the environments live in Sink, the local .env stops being
something you protect and becomes something you regenerate:
sink pull development # writes .env, 0600 permissions
sink diff development # what differs, values masked
sink push development # send local changes back up
sink diff is the one that earns its place. It answers "is my local
file still current?" — a question that, without it, is usually answered by
debugging for twenty minutes. Add --show-values when you need to see
what actually differs, and --exit-code when you want a script to fail
on any difference:
sink diff production --exit-code || echo "production has moved on"
Onboarding, in one line
This is the part that changes shape completely. The new hire does not need a
.env from anyone:
git clone [email protected]:you/my-app.git
cd my-app
sink login
sink pull development
Because .sink.json is committed, the clone already knows which
workspace, team, project and environment it belongs to. There is no file to send,
so there is nothing to send over Slack — which removes the most common reason
credentials end up somewhere permanent. If you have already been sending them
that way, see sharing a .env file securely.
In CI, and in Vercel's own build
CI has no keychain and no interactive login, so it reads an API key from the environment instead:
# in your CI provider's secret store
SINK_API_KEY=sk-...
# in the job
sink pull production --stdout > .env
One thing worth being clear about: Vercel builds still read Vercel's own environment variables. Sink is where the team's copy lives, gets versioned and gets audited — it does not replace what Vercel injects at build time. Treat Sink as the source of truth and Vercel as a deployment target you push to, the same way you would treat any other.
Keeping it honest
- Re-run
sink import vercel ... --dry-runoccasionally. If it reports differences, something was changed in the dashboard and not written down. - Rotate in the provider first, then update Sink, then let everyone re-pull.
- Use
sink pull --version Nto read an environment at an earlier version when you need to work out what a value used to be. - Give people the narrowest role that works — Viewer for anyone who only needs development.
Sink keeps every environment under AES-256-GCM envelope encryption, with roles, versions and an audit trail — and puts it back in your .env with one command. The free tier does not ask for a card.