20 days ago
We are coordinating a logging credential rotation for two staging services using Railway CLI 5.45.7 and the public GraphQL API. Please confirm these supported server-side behaviors; this is a technical question, not a request to modify our account.
-
With an unapplied environment stage affecting only service B, does variableUpsert with exact projectId, environmentId, serviceId=A, name, value and skipDeploys:true update A's saved configuration while preserving B's staged contents, STAGED status and appliedAt:null, without creating a deployment?
-
Does a subsequent railway up targeting only service A use A's newly saved variables without applying B's pending stage? Changes to stage ID or timestamp alone are acceptable; contents and application state are what matter.
-
Alternative: if both services' approved changes may be combined, what is the supported path to merge only those changes into one staged changeset, commit it without starting any deployment, then upload exact source to service A, verify A, and only then upload exact source to B? Specifically, does environmentStageChanges(merge:true), followed by environmentPatchCommitStaged(skipDeploys:true), persist that changeset without deploying either service? How should actual variable unsets be represented and verified when decryptVariables:false returns opaque staged values?
We need to preserve unrelated configuration and cannot use a normal Deploy/Apply all action that starts deployments. No new resources or plan changes are requested.
Please point to the authoritative contract, or explicitly confirm the existing-stage and no-deploy behavior. We have reviewed the manage-variables documentation, staged-changes documentation and CLI 5.45.7 source. This public question intentionally contains no project, service or deployment identifiers, site names, local paths, private repository information, credential values, logs or attachments.
2 Replies
20 days ago
This thread has been opened as a bounty so the community can help solve it.
Status changed to Open Railway • 20 days ago
20 days ago
I think the slightly confusing part here is that the service ID itself isn't different between production and staging.
The serviceId identifies the service, while the environmentId selects which environment's instance/config you're changing.
So for a variable that should only exist/update on that service in staging, I'd use something like:
mutation variableUpsert($input: VariableUpsertInput!) {
variableUpsert(input: $input)
}with:
{
"input": {
"projectId": "PROJECT_ID",
"environmentId": "STAGING_ENVIRONMENT_ID",
"serviceId": "SERVICE_ID",
"name": "MY_VARIABLE",
"value": "new-value",
"skipDeploys": true
}
}A couple important details:
- Keep
serviceIdin the input. If you leave it out, Railway treats it as a shared variable for that environment. - Make sure
environmentIdis the actual staging environment ID. That's what prevents the production value from being touched. skipDeploys: trueupdates the variable without immediately deploying the service. Remove it/set it to false if you want the variable change to trigger a deployment.
For multiple variables I'd use variableCollectionUpsert instead:
mutation variableCollectionUpsert($input: VariableCollectionUpsertInput!) {
variableCollectionUpsert(input: $input)
}{
"input": {
"projectId": "PROJECT_ID",
"environmentId": "STAGING_ENVIRONMENT_ID",
"serviceId": "SERVICE_ID",
"variables": {
"VAR_ONE": "value",
"VAR_TWO": "value"
},
"skipDeploys": true
}
}I would also avoid replace: true unless you actually want Railway to remove every other variable that isn't included in the payload.
You can verify the result without deploying by querying variables again with the same project/environment/service IDs.
So if the concern is that the same serviceId exists in both prod and staging, that's expected — environmentId is the part that scopes the update to staging.
19 days ago
We send OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS as direct variable-node JSON null values through environmentStageChanges(merge:true), but environmentStagedChanges.patch(decryptVariables:false) returns masked {value:string} objects.
Is setting the variable node itself to JSON null the supported deletion representation for this exact environmentStageChanges(merge:true) operation? If so, which supported read or metadata field can verify that these two variables are recorded as actual DELETE operations in the existing pending changes, without revealing values or committing/deploying anything?
If account-specific investigation is necessary, please identify an authorized private support channel available to a Trial account. We will not post private identifiers or credentials here.