seekrit
Docs/Netlify

Sync to Netlify

Netlify reads a site's environment variables during a build and hands them to functions and edge functions at runtime — from its own copy, before anything of yours runs. A binding writes one site's variables, for the deploy contexts it names.

At a glance
What seekrit writesA site's environment variables, per deploy context
Addressed byThe site's API ID (a UUID) + one or more contexts
Connection carriesThe token, and the team whose variables it writes
Token permissionA personal access token whose user can change the team's environment variables
Takes effectNext build and deploy
Value visibilityWrite-only, when created as a Netlify secret (the default)

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 personal access token

Netlify issues them under User settings → Applications → Personal access tokens. A token carries its user's access, so the least-privilege setup is a machine user in only the team it syncs.

Unlike most connections here, a Netlify one has a second half to state: the team. Netlify keeps environment variables on the account, not the site — every endpoint is /accounts/{account_id}/env, with the site as a filter — and a personal access token belongs to a user who may sit in several teams, so it cannot say which one to write. Pass the slug from your dashboard URL (app.netlify.com/teams/<slug>), or the account ID; Netlify treats the two as interchangeable wherever {account_id} appears.

printf '%s' "$NETLIFY_AUTH_TOKEN" \
  | seekrit sync connect --name acme-netlify --provider netlify --account-id acme

2. Bind an environment

A Netlify value is keyed by site and deploy context, so a binding names both. --target takes the contexts — production, deploy-preview, branch-deploy, branch, or dev — and defaults to production:

seekrit sync verify acme-netlify --provider netlify \
  --netlify-site 3970e0fe-8564-4903-9a55-c5f8de49fb8b

seekrit sync enable --connection acme-netlify --provider netlify \
  --netlify-site 3970e0fe-8564-4903-9a55-c5f8de49fb8b \
  --target production,deploy-preview \
  --app storefront --env production --acknowledge-decryption

For one named branch, pass --target branch with --git-branch staging. A push writes only the contexts you list: the same variable's other contexts, and every variable this binding does not manage, are left exactly as they are — which is what makes it safe to point at a site that already has variables set by hand.

Netlify's all context is deliberately not offered. A secret value must be set against explicit contexts, and Netlify's own endpoint is reported to fail outright on context: "all". Naming the contexts you mean is what you want here anyway — a binding already exists to map one seekrit environment onto one deploy context.

caution

The site is named by its API ID, not its name. Netlify shows it under Project configuration → General → Project information, and it is a UUID.

seekrit refuses anything else on purpose. The environment variable endpoints take the site as a query parameter, where Netlify resolves no names — and a site it cannot resolve does not fail the request. The variables are created on the team instead, shared by every site in it. seekrit sync verify also checks that the site really belongs to the connection's team, which is the other half of that mistake.

note

Values are pushed as Netlify secrets. Netlify's Secrets Controller makes a variable write-only: after it is set, the value cannot be read back through the UI, CLI, or API. seekrit creates variables that way by default, which is what you want for something a secrets manager owns.

Three things follow from Netlify's rules. The flag can only be set when the variable is created — Netlify will not add it to one that already exists, and never removes it — so a name you already set by hand keeps whatever it has. A secret cannot carry the post-processing scope (a secret in snippet injection would be served to the browser), so seekrit sets builds, functions, and runtime. And values in the dev context stay readable by design, since local development needs them. If your plan does not include Secrets Controller, pass --no-netlify-secret.

How a push behaves

  • A create carries every context at once; an update sets one context's value at a time. So a variable seekrit creates costs one request, and a change to an existing one costs a request per context it writes.
  • A removal reads the variable's value ids first, then drops the ones for the contexts this binding owns — or the whole variable when it owns them all.
  • A run is capped at 400 operations, sized against Netlify's rate limit of 500 requests per minute per user, which binds well before the Worker subrequest cap and is shared with everything else that token does.
  • A value Netlify echoes back in an error is scrubbed before the reason is stored on the run row.

Netlify binds environment variables at build time, so a pushed value reaches the running site on its next deployment — a sync run does not start one.

Name and value rules

note

Netlify will not accept every name seekrit will. Variable names take only letters, numbers, and underscores, must start with a letter — where seekrit also allows a leading underscore — and cannot begin with NETLIFY_, which Netlify reserves for itself. Keys stop at 255 characters and values at 5,000. A name that breaks one of those is reported as a failure against that name alone; the rest of the environment still pushes. Watch for it if you use a --prefix.

Troubleshooting

SymptomCauseFix
The site ID is rejected when enablingA site name or .netlify.app address was pastedUse the API ID (a UUID) from Project configuration → General
Variables appeared on every site in the teamA site id Netlify could not resolve — the failure mode the strict check exists to preventDelete the team-level variables, then re-bind with the API ID
Verify fails saying the site isn't in this teamThe connection's --account-id is a different teamRe-create the connection with the team that owns the site
401 on every nameThe token was revokedMint a new personal access token
403The token's user can't change that team's environment variablesGive the machine user the right team role
A create fails mentioning secrets or scopesThe plan has no Secrets ControllerPass --no-netlify-secret to create readable variables
A variable set by hand is still readableThe secret flag only applies at creation, and Netlify won't add it laterDelete it at Netlify and let the next sync recreate it
One name failed, rest landedThe mapped name starts with a digit or underscore, begins NETLIFY_, or the value is over 5,000 charactersFix the prefix or rename
Values pushed, site unchangedNetlify binds at build timeTrigger a deploy. seekrit does not start builds

See also