Sync to Railway
Railway applies variables at deploy time and hands them to the process it starts, so there is no earlier point at which seekrit could inject them. A binding maps one seekrit environment onto one (project, environment, service) address inside Railway.
| At a glance | |
|---|---|
| What seekrit writes | A service's variables, or an environment's shared variables |
| Addressed by | Railway project, environment, and service — all three UUIDs |
| Connection carries | The API token, and which kind of token it is |
| Token permission | An account/workspace token, or a project token scoped to one project |
| Takes effect | On redeploy, which seekrit triggers by default |
| Value visibility | Readable by anyone with access to the Railway project |
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 token, and know which kind it is
Railway has two kinds, and they travel in different headers — a project token sent as an account one fails exactly like an invalid token would, so seekrit asks you which you pasted rather than guessing:
- account — from Account Settings → Tokens (personal or workspace).
Reaches every project you can see. Sent as
Authorization: Bearer. - project — from Project → Settings → Tokens. Railway scopes it to one
project and environment, which makes it the least-privilege choice when a
connection serves a single destination. Sent as
Project-Access-Token.
printf '%s' "$RAILWAY_TOKEN" | seekrit sync connect \
--name acme-railway --provider railway --token-kind project
--token-kind defaults to account. There is no workspace or team id to state:
Railway ids are globally unique and a binding names its project outright, so the
token plus the destination is the whole address.
2. Bind an environment
A Railway variable is addressed by project, environment, and service — all three
are UUIDs, so each has its own flag. The environment here is Railway's
(production, pr-42), not the seekrit environment the binding reads from; a
binding is the mapping between the two.
seekrit sync verify acme-railway --provider railway \
--railway-project 1111… --railway-environment 2222… --service 3333…
seekrit sync enable --connection acme-railway --provider railway \
--railway-project 1111… --railway-environment 2222… --service 3333… \
--app storefront --env production --acknowledge-decryption
Find the ids in the resource's URL, or on the project's Settings page. seekrit
checks each is UUID-shaped before storing it, because the alternative is a bare
Problem processing request hours later inside a run nobody is watching.
Verify asks for the environment and the service in one GraphQL document rather than two round trips — Railway's hourly quota is small enough that a spare request is worth avoiding.
Omit --service to write the environment's shared variables instead — the
project-level set that services opt into with ${{shared.NAME}}. That is a
genuinely different destination, not a wildcard: shared variables reach only the
services that reference them.
Railway applies variables at deploy time, so changing one triggers a
redeploy of the service. That is what makes a synced value reach the running
process, and it is the default. Pass --skip-deploys (or tick Don't redeploy
on change) when deploys are gated behind a release process — the values then
sit staged until your next one.
How a push behaves
- One request for the whole environment.
variableCollectionUpserttakes a map of up to 100 names, so a normal run costs a single call no matter how many secrets it carries. replace: false, always. Railway's upsert can replace a collection wholesale; seekrit never uses that. Railway's ownPORT, reference variables, and anything another binding owns are left alone, and a removal deletes just that name.- Removals are one request each. Railway has no bulk delete. A variable Railway has never heard of counts as deleted rather than as a failure that would retry forever.
- seekrit never reads Railway's existing values. Variables are addressed by name, not by an internal id, so unlike Vercel there is nothing to list first — which keeps unrelated third-party plaintext out of the sync engine entirely.
- A run is capped at 200 requests, far below the other connectors' budgets, because Railway's own rate limits bite first: as low as 100 requests per hour on the free plan, and 10 per second on Hobby. Since a normal push is one request, that ceiling only ever bounds runs with many deletions.
Railway answers a failed call with HTTP 200. A bad token, a project the
token cannot see, and a deleted service all come back as
200 {"errors":[{"message":"Not Authorized"}]}. seekrit treats a populated
errors array as a failure regardless of status — trusting the status alone
would mark every secret as pushed on a run where nothing left the building.
It also means the reason has to be read out of the message text. Not Authorized is the one Railway returns for all three causes, so the fix is
usually a process of elimination: check the token kind first, then the ids.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Not Authorized on everything, immediately after setup | The token kind is wrong — Railway sends the two in different headers | Re-create the connection with the right --token-kind. A project token presented as a bearer fails identically to an invalid one |
Not Authorized on a connection that worked | The token was revoked, or its owner lost access to the project | Mint a new token and re-create the connection |
Not Authorized naming one destination only | A project token pointed at a project or environment outside its scope | Use an account token, or a project token minted in the right project |
| A UUID is rejected when enabling | An id was pasted from the wrong place — a slug, or a name | All three ids are UUIDs. Take them from the resource's URL |
| Values pushed, process still on the old ones | --skip-deploys is on, or the deploy has not finished | Deploy the service; without the flag seekrit triggers one for you |
Run reported partial, 429 in the error | Railway rate-limited the token | Nothing to do — seekrit honors Retry-After and retries the rest |
See also
- Third-party sync — the shared model, naming and filtering, deletions
- CLI reference — every
seekrit syncflag