seekrit
Docs/Vercel

Sync to Vercel

A Vercel build reads environment variables that Vercel already holds — there is no earlier point at which seekrit could inject them. So seekrit stays the source of truth and pushes each secret into the project as an encrypted environment variable, for the deployment targets the binding names.

At a glance
What seekrit writesProject environment variables, type: encrypted
Addressed byVercel project id (prj_…) or name, plus one or more targets
Connection carriesThe API token, and a team id for team-owned projects
Token permissionA token with access to the project — Vercel has no per-resource scope
Takes effectNext build. Vercel injects at build and run time from its own copy
Value visibilityEncrypted at rest by Vercel, and readable by anyone who can read the project's env vars

New to sync? Read Third-party sync first — the decryption grant, name mapping, deletions, and failure handling are the same on every destination.

1. Create a Vercel token

In Vercel, go to Settings → Tokens and create a token with access to the project you want to sync into. If the project belongs to a Vercel Team, note the team id (team_…) — a personal-scope token can't write to a team project, and Vercel reports that as a bare 403.

Give the token an expiry you will actually notice, and name it after the connection. Vercel tokens carry their creator's access, so the least-privilege setup is a machine user added to only the projects it syncs.

2. Add the connection

In the dashboard, open Sync → Add connection, choose Vercel, paste the token, and set the team id if you have one. The token is encrypted in your browser before it is sent.

Or from the CLI. The token is read from stdin — never a flag, so it can't end up in your shell history — and is wrapped to the connection's public key before it is sent:

printf '%s' "$VERCEL_TOKEN" | seekrit sync connect \
  --name acme-production --team-id team_abc

Vercel is the default provider, so --provider vercel can be left off. The team id is sent as ?teamId= on every request the connection makes; omit it for a personal account.

Check it can reach the project before you bind anything to it:

seekrit sync verify acme-production --project prj_abc

Verify fetches the project (GET /v9/projects/{id}), which proves three things at once: the token is valid, it reaches this project, and the team id is right or not needed.

3. Bind an environment

Pick the application environment to sync and the Vercel project and targets (production, preview, development). This is the step that shows the decryption disclosure and creates the key grant, so it has to be done by someone who can already read the environment.

seekrit sync enable \
  --connection acme-production \
  --app storefront --env production \
  --project prj_abc \
  --target production \
  --acknowledge-decryption

--target takes a comma-separated list and defaults to production. A binding writes each variable to every target it names, which is how one seekrit environment feeds both production and preview from a single binding.

seekrit pushes once immediately, then on every change.

Preview deployments and one branch

--git-branch restricts preview writes to a single git branch. Vercel only honors it when preview is among the targets, so seekrit sends it only then — a production-only binding that carried a branch would be rejected outright.

seekrit sync enable --connection acme-production \
  --app storefront --env staging \
  --project prj_abc --target preview --git-branch staging \
  --acknowledge-decryption

This pairs naturally with branch environments: point a seekrit branch environment at a Vercel preview target with its git branch set, and each PR gets its own overlay downstream. When the branch expires its binding goes with it, and seekrit stops pushing — the values already on Vercel stay where they are.

How a push behaves

  • One request per name, POST /v10/projects/{id}/env?upsert=true. Vercel's upsert is create-or-replace, so a run never needs to read the project's current values first.
  • Values are stored as encrypted. Vercel can read them — that is inherent to pushing anywhere, and is exactly what the sync key grant authorizes.
  • A removal costs a listing. Deleting needs Vercel's internal env-var id, so a run with removals lists the project's variables once, then deletes each. Only records whose targets overlap this binding's are touched, so two bindings can point at one project with different targets and neither will delete the other's variables. A variable already gone counts as deleted rather than failing forever.
  • A run is capped at 500 operations. Beyond that it reports that it has more to do and the engine re-runs immediately, so a very large first sync is slower rather than silently partial.
  • A 429 stops the whole run. seekrit honors Vercel's Retry-After rather than its own backoff curve; what already landed is reported, and the rest retries.

Troubleshooting

SymptomCauseFix
403 on every nameThe project is team-owned and the connection has no team idAdd --team-id team_…, or re-create the connection with it
403 after it was workingThe token expired, or its owner lost access to the projectMint a new token and re-create the connection
404 on the projectWrong project id, or the project was deleted or renamedCheck prj_… in the project's Settings; seekrit sync verify catches this before a binding exists
400 mentioning gitBranchA branch was set on a binding whose targets don't include previewAdd preview to --target, or drop --git-branch
429, run reported partialVercel rate-limited the accountNothing to do — seekrit backs off and retries the rest
Values pushed, site unchangedVercel injects env vars at build timeRedeploy. seekrit does not trigger Vercel builds

See also