Connect pinned GitHub source without initial deploy or push autodeploy
garniksacsha
HOBBYOP

a month ago

We have existing, never-deployed services with no GitHub source or deployment triggers.

We need to connect a GitHub repository at a pinned full commit SHA while guaranteeing both zero initial deployments and disabled future push autodeploy from the instant of connection.

The services include a migration runner, so connecting and disabling afterward is unsuitable.

Does setting serviceInstanceAutoDeployUpdate(enabled:false) before a source exists persist through source binding, including when status currently reports NO_REPO? If so, which source mutation preserves that flag? Alternatively, is there an atomic source-binding API or staged config field that disables trigger creation? Please give the supported sequence and readback fields proving the effective state. Does environmentPatchCommitStaged(skipDeploys:true) only suppress the immediate deploy, or also affect push-trigger creation? What does branch:null mean when repo and commitSha are supplied?

$10 Bounty

3 Replies

Railway
BOT

a month ago

This thread has been opened as a bounty so the community can help solve it.

Status changed to Open Railway • 27 days ago


a month ago

Short version: Railway separates source binding, push triggers, and deploys into

three independent surfaces. Once you see that, most of this resolves — and one of

your premises doesn't hold.

  1. branch:null with repo + commitSha

No mutation accepts repo and commitSha together, so this case doesn't arise.

ServiceConnectInput is {repo, branch, image}; ServiceSourceInput is {repo, image}.

Neither takes a SHA. commitSha appears only on the deploy mutations:

serviceInstanceDeploy(serviceId, environmentId, commitSha, latestCommit)

serviceInstanceDeployV2(serviceId, environmentId, commitSha)

In ServiceConnectInput, branch is optional (String, not String!). Omitting it means

Railway resolves the repo's default branch. It does not mean "untracked" or "unpinned."

So a pinned SHA is a property of a deploy, never of the binding. Bind the repo, then

deploy the SHA explicitly when you're ready.

  1. Push autodeploy is a separate object, not a flag on the binding

Push deploys are DeploymentTrigger rows, managed by deploymentTriggerCreate /

deploymentTriggerUpdate / deploymentTriggerDelete, and readable via:

deploymentTriggers(projectId, environmentId, serviceId)

Project.deploymentTriggers / Environment.deploymentTriggers

Your guarantee should come from asserting no DeploymentTrigger exists for the

service instance — not from trusting a boolean set before binding.

  1. serviceInstanceAutoDeployUpdate before a source exists

I would not rely on it persisting. The status query is:

serviceInstanceAutoDeployStatus(projectId, environmentId, serviceId)

-> { enabled, canEnable, reason }

The presence of canEnable and reason indicates the flag is gated on source state —

NO_REPO is a reason for canEnable:false. Treat autoDeploy as derived state you assert

after binding and then verify, rather than a durable pre-set.

  1. environmentPatchCommitStaged(skipDeploys: true)

Its documented behaviour is "Skip deploys for services affected by this patch" — it

suppresses deploys arising from that patch only. Triggers are created through a

separate mutation (deploymentTriggerCreate), so skipDeploys has no bearing on whether

a push trigger exists. Assert trigger absence separately.

  1. Sequence I'd use

a. Bind without a branch:

 serviceInstanceUpdate(serviceId, environmentId,

   input: { source: { repo: "owner/name" } })

b. Assert no trigger:

 deploymentTriggers(projectId, environmentId, serviceId)

   -> deploymentTriggerDelete(id) for anything returned

c. Assert autodeploy off:

 serviceInstanceAutoDeployUpdate(

   input: { projectId, environmentId, serviceId, enabled: false })

d. Read back and prove effective state:

 serviceInstance(serviceId, environmentId) {

   hasEverDeployed      # false proves zero deployments ever occurred

   source { repo }

   latestDeployment { id }   # null

 }

 deploymentTriggers(projectId, environmentId, serviceId) { edges { node { id } } }

 serviceInstanceAutoDeployStatus(projectId, environmentId, serviceId) {

   enabled canEnable reason }

e. When you actually want the pinned build:

 serviceInstanceDeployV2(serviceId, environmentId, commitSha: "<40-char sha>")

hasEverDeployed is the field that answers your migration-runner concern directly —

it is a permanent record, so you can verify nothing ever fired rather than inferring

it from deployment list emptiness.

Caveat on what I've verified: the above is from the live GraphQL schema

(introspection shapes, descriptions, and nullability). Two things I have not

confirmed empirically on a live NO_REPO service: whether serviceConnect auto-creates

a DeploymentTrigger as a side effect, and whether a pre-binding autoDeploy:false

survives binding. Step (b) makes both moot by asserting the end state rather than

trusting the transition — which I'd recommend regardless.


garniksacsha
HOBBYOP

a month ago

Thanks. Our requirement is zero deployments during source binding, not just a clean final state. Does serviceInstanceUpdate with source.repo create an initial deployment or deployment trigger on a never-deployed NO_REPO service? Is there a supported operation that guarantees neither side effect? Post-binding deletion is too late for our migration runner.


michael-tudor-github
PRO

23 days ago

We have the same requirement for an existing production service: attach a GitHub repo without any initial deployment or temporary autodeploy window.

Can Railway staff confirm whether there is a supported atomic or staged sequence that guarantees this? If not, we'll continue using our manual archive deployment path.


Welcome!

Sign in to your Railway account to join the conversation.

Loading...