Environment Sync / EnvironmentConfig — minimal patch for creating an existing Service in another environment
buselco
HOBBYOP

15 days ago

Hello Railway Support,

we need clarification on the exact semantics of Environment Sync / staged EnvironmentConfig for an existing project-level Service.

Current topology:

  • one project-level Service already has a Service Instance in Environment A;
  • the same Service has no Service Instance in Environment B;
  • Environment Sync from A → B stages this Service as:

services[existingServiceId]:

isCreated: true

build: {...}

deploy: {...}

variables: {...}

We need to create the missing Service Instance in Environment B without:

  • deploying it,
  • attaching a repository or image source,
  • copying application variables,
  • creating domains,
  • creating volumes.

We intend to commit staged configuration with:

environmentPatchCommitStaged(skipDeploys: true)

or the equivalent Railway UI no-deploy action.

Could you please confirm:

  1. Is the following minimal staged EnvironmentConfig entry valid and sufficient to create the missing environment-specific Service Instance?

services[existingServiceId]:

isCreated: true

when committed with skipDeploys: true?

  1. If isCreated: true alone is not sufficient, which build/deploy fields are mandatory for Service Instance creation?

The Sync-generated entry currently contains these fields:

  • build.buildEnvironment = V3
  • build.builder = DOCKERFILE
  • build.dockerfilePath = /Dockerfile
  • deploy.ipv6EgressEnabled = false
  • deploy.multiRegionConfig.sfo.numReplicas = 1
  • deploy.runtime = V2
  • deploy.useLegacyStacker = false

Which of these, if any, are structurally required for creation of the Service Instance rather than merely optional build/runtime overrides?

  1. We also observed that a separate serviceInstanceUpdate attempt on a source-less Service in a disposable persistent/non-fork test project returned:

HTTP 400

Problem processing request

Does serviceInstanceUpdate in a non-fork environment attempt to apply configuration across other non-fork environments, and can the absence of that Service Instance in another environment cause this error?

We are specifically trying to understand the supported minimal provider configuration for creating an environment-specific instance of an existing Service without triggering a deployment.

Thank you.

Awaiting Conductor Response$10 Bounty

3 Replies

Status changed to Awaiting Railway Response Railway • 15 days ago


Railway
BOT

15 days ago

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

Status changed to Open Railway • 15 days ago


chevcellios
HOBBY

14 days ago

Railway Environment Sync / EnvironmentConfig Semantics — Technical Report

  1. Minimal Service Instance Creation

Yes — the following minimal staged EnvironmentConfig entry is valid and sufficient to create a missing Service Instance in Environment B:

json

services[existingServiceId]: {

"isCreated": true

}

When committed using:

environmentPatchCommitStaged(skipDeploys: true)

Railway will:

create the Service Instance in Environment B

not deploy it

not attach a repo or image

not copy variables

not create domains

not create volumes

This is the supported minimal configuration.

  1. Are build/deploy fields mandatory?

No — build/deploy fields are not required for Service Instance creation

They are only required if Railway would otherwise attempt a deployment.

Mandatory only when:

skipDeploys: false, or

the environment is expected to perform a build/deploy, or

the service has a source/image attached and Railway must validate build/runtime settings.

Since you explicitly skip deployment, the following Sync‑generated fields are optional:

json

build.buildEnvironment = "V3"

build.builder = "DOCKERFILE"

build.dockerfilePath = "/Dockerfile"

deploy.ipv6EgressEnabled = false

deploy.multiRegionConfig.sfo.numReplicas = 1

deploy.runtime = "V2"

deploy.useLegacyStacker = false

They are structural defaults copied from the source environment, not required for instance creation.

  1. Why Sync includes build/deploy fields

Environment Sync performs structural mirroring:

It copies build/deploy/runtime fields from the source environment.

These fields are used only if a deployment is triggered.

They are not required for creating an undeployed instance.

You may safely remove them.

  1. serviceInstanceUpdate returning HTTP 400

A serviceInstanceUpdate in a non‑fork project validates configuration across all environments.

If:

the Service exists in Environment A

but does not exist in Environment B

and you attempt a project‑level update

Railway may return:

HTTP 400

Problem processing request

This happens because:

the update implies configuration changes for all environments

but one environment is missing the Service Instance

the update cannot be applied consistently

This is expected behavior in non‑fork projects.

Forked projects do not have this constraint.

  1. Supported minimal provider configuration

To create an environment‑specific instance of an existing Service without triggering deployment, the minimal valid configuration is:

json

services[existingServiceId]: {

"isCreated": true

}

Commit with:

environmentPatchCommitStaged(skipDeploys: true)

No other fields are required.


chevcellios
HOBBY

14 days ago

Thanks for the live API reproduction — this is a very useful verification of the bounty question.

It confirms the main part of my answer: services[serviceId]: { isCreated: true }, staged with environmentStageChanges and committed using environmentPatchCommitStaged(..., skipDeploys: true), is sufficient to create the missing environment-specific Service Instance without deployment, source attachment, variable copying, domains, or volumes.

It also confirms that none of the Sync-generated build.* or deploy.* fields are structurally required for that creation.

However, your test also identifies an important correction to my bounty response.

My explanation of the reported serviceInstanceUpdate HTTP 400 was based on the assumption that, in a non-fork project, the mutation could fail because the Service Instance was missing from another environment.

Your live tests show that this explanation is not supported.

You tested serviceInstanceUpdate with:

  • an environment where the Service Instance exists,
  • an environment where it does not exist,
  • and with environmentId omitted,

and all returned true.

So the original poster's HTTP 400 cannot be attributed simply to the missing Service Instance in another non-fork environment. As you suggest, it is much more likely to depend on the specific ServiceInstanceUpdateInput payload, such as source, registryCredentials, baseConfigEtag, or another validation condition.

There is also an interesting API semantic exposed by your test that seems worth documenting:

serviceInstanceUpdate can return true when the target Service Instance does not exist, while creating nothing and leaving serviceInstances unchanged.

So true appears to mean that the mutation itself was accepted/processed, not necessarily that an environment-specific instance existed and was modified.

For anyone implementing this in a provider or automation tool, the safe distinction therefore seems to be:

Create missing Service Instance:
environmentStageChanges
  → services[serviceId].isCreated = true
  → environmentPatchCommitStaged(skipDeploys: true)

Update existing Service Instance:
serviceInstanceUpdate

with a state query after the mutation if confirmation of the resulting Service Instance is required.

The environmentPatchCommitStaged vs environmentPatchCommit distinction is also an important addition to the answer: the former commits only previously staged changes, while the latter accepts an explicit EnvironmentConfig patch.

And environmentUnskipService is definitely worth investigating as a separate path, particularly to determine the exact semantic distinction between a Service being "skipped" in an environment and a Service simply having no Service Instance there.

So I would amend my bounty answer accordingly: the minimal isCreated: true solution is verified, but my explanation for the reported serviceInstanceUpdate 400 was incorrect/unverified.

Great reproduction — thanks for testing the actual API behavior rather than relying only on inferred schema semantics.


chevcellios

Thanks for the live API reproduction — this is a very useful verification of the bounty question. It confirms the main part of my answer: `services[serviceId]: { isCreated: true }`, staged with `environmentStageChanges` and committed using `environmentPatchCommitStaged(..., skipDeploys: true)`, is sufficient to create the missing environment-specific Service Instance without deployment, source attachment, variable copying, domains, or volumes. It also confirms that none of the Sync-generated `build.*` or `deploy.*` fields are structurally required for that creation. However, your test also identifies an important correction to my bounty response. My explanation of the reported `serviceInstanceUpdate` HTTP 400 was based on the assumption that, in a non-fork project, the mutation could fail because the Service Instance was missing from another environment. Your live tests show that this explanation is not supported. You tested `serviceInstanceUpdate` with: - an environment where the Service Instance exists, - an environment where it does not exist, - and with `environmentId` omitted, and all returned `true`. So the original poster's HTTP 400 cannot be attributed simply to the missing Service Instance in another non-fork environment. As you suggest, it is much more likely to depend on the specific `ServiceInstanceUpdateInput` payload, such as `source`, `registryCredentials`, `baseConfigEtag`, or another validation condition. There is also an interesting API semantic exposed by your test that seems worth documenting: `serviceInstanceUpdate` can return `true` when the target Service Instance does not exist, while creating nothing and leaving `serviceInstances` unchanged. So `true` appears to mean that the mutation itself was accepted/processed, not necessarily that an environment-specific instance existed and was modified. For anyone implementing this in a provider or automation tool, the safe distinction therefore seems to be: ```text id="r63j9p" Create missing Service Instance: environmentStageChanges → services[serviceId].isCreated = true → environmentPatchCommitStaged(skipDeploys: true) Update existing Service Instance: serviceInstanceUpdate ``` with a state query after the mutation if confirmation of the resulting Service Instance is required. The `environmentPatchCommitStaged` vs `environmentPatchCommit` distinction is also an important addition to the answer: the former commits only previously staged changes, while the latter accepts an explicit `EnvironmentConfig` patch. And `environmentUnskipService` is definitely worth investigating as a separate path, particularly to determine the exact semantic distinction between a Service being "skipped" in an environment and a Service simply having no Service Instance there. So I would amend my bounty answer accordingly: the minimal `isCreated: true` solution is verified, but my explanation for the reported `serviceInstanceUpdate` 400 was incorrect/unverified. Great reproduction — thanks for testing the actual API behavior rather than relying only on inferred schema semantics.

buselco
HOBBYOP

14 days ago

Thank you very much for the clarification and for validating the live API behavior.

This fully resolves the main question for us.

The key confirmation we needed was that:

services[serviceId]: { isCreated: true }

staged with environmentStageChanges and committed with environmentPatchCommitStaged(..., skipDeploys: true) is sufficient to create the missing environment-specific Service Instance without deployment, source attachment, variables, domains, or volumes.

The clarification around serviceInstanceUpdate is also very useful, especially the fact that a true response does not necessarily mean that an environment-specific Service Instance was created or modified.

We’ll use the staged isCreated-only path with explicit post-mutation state verification.

Thanks again for the detailed answer and for correcting the earlier assumption based on the live reproduction.


Welcome!

Sign in to your Railway account to join the conversation.

Loading...