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 writes | A site's environment variables, per deploy context |
| Addressed by | The site's API ID (a UUID) + one or more contexts |
| Connection carries | The token, and the team whose variables it writes |
| Token permission | A personal access token whose user can change the team's environment variables |
| Takes effect | Next build and deploy |
| Value visibility | Write-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.
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.
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
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
| Symptom | Cause | Fix |
|---|---|---|
| The site ID is rejected when enabling | A site name or .netlify.app address was pasted | Use the API ID (a UUID) from Project configuration → General |
| Variables appeared on every site in the team | A site id Netlify could not resolve — the failure mode the strict check exists to prevent | Delete the team-level variables, then re-bind with the API ID |
| Verify fails saying the site isn't in this team | The connection's --account-id is a different team | Re-create the connection with the team that owns the site |
401 on every name | The token was revoked | Mint a new personal access token |
403 | The token's user can't change that team's environment variables | Give the machine user the right team role |
| A create fails mentioning secrets or scopes | The plan has no Secrets Controller | Pass --no-netlify-secret to create readable variables |
| A variable set by hand is still readable | The secret flag only applies at creation, and Netlify won't add it later | Delete it at Netlify and let the next sync recreate it |
| One name failed, rest landed | The mapped name starts with a digit or underscore, begins NETLIFY_, or the value is over 5,000 characters | Fix the prefix or rename |
| Values pushed, site unchanged | Netlify binds at build time | Trigger a deploy. seekrit does not start builds |
See also
- Third-party sync — the shared model, naming and filtering, deletions
- Branch configs — per-PR environments that pair with deploy previews
- CLI reference — every
seekrit syncflag