Skip to content

fix(iac): converge empty tcpProxies and import database networking - #1169

Merged
paulocsanz merged 2 commits into
masterfrom
pcs/iac-database-networking-converges
Sep 4, 2026
Merged

fix(iac): converge empty tcpProxies and import database networking#1169
paulocsanz merged 2 commits into
masterfrom
pcs/iac-database-networking-converges

Conversation

@paulocsanz

@paulocsanz paulocsanz commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Fixes #1168.

What. railway config plan converges after a successful apply. A file that declares a database (or service) private no longer re-plans Update <name> networking forever, database nodes import their live networking so railway config pull writes a public proxy back as db.networking = { tcpProxies: { "5432": {} } }, and a database node without a networking block keeps the exposure it has. tcpProxies: { "5432": null } converges once the apply has removed the proxy, and tcpProxies: {} warns that it cannot remove one instead of planning a removal that never lands.

Why. Backboard merges a networking change as servicePatch.networking = change.after, which makes an authored block a sparse patch: a key the author leaves out stays as Railway has it, and inside tcpProxies an entry keeps or creates a proxy, a per-port null deletes one, and an unmentioned port is left alone — so an empty map changes nothing. diff_networking compared the block as if it were the whole desired state, so three plans promised work the apply never does and proposed it again after every apply: tcpProxies left out, privateNetworkEndpoint left out, and tcpProxies: {} against a live proxy. Backboard also serializes a service with no proxy as no tcpProxies key while the SDK compiles tcp: [] to tcpProxies: {}, and the database import branch built its node without networking at all, so a public proxy on a database was invisible to both the plan and pull.

How.

  • networking_after_apply replays an authored block over the live one the way backboard merges it, and diff_networking plans a change only when that effect differs from what Railway has. The change set still carries the block as written, because that is the payload the apply sends and a per-port null is what deletes a proxy.
  • normalize_for_diff("networking") drops empty maps, so { tcpProxies: {} } and absent networking compare equal (services included).
  • An empty tcpProxies against a live proxy raises a warning naming the spelling that removes it, in place of a removal the apply would skip.
  • environment_config_to_graph imports networking for databases through the same helper services use.
  • diff_networking skips a database whose desired node has no networking key: the helpers cannot author it, and pulled files only carry it when a proxy exists.
  • railway config pull renders db.networking = { ... } after the helper call for databases with a proxy (TypeScript; Python and Go get the same comment form as deploy overrides). Service domains stay out of the file, as for services.

Tests. src/iac/tests.rs: empty tcpProxies converges for postgres, redis and a service; an imported database keeps and round-trips its proxy; unauthored database networking is clean against a live proxy; an empty map warns instead of planning a removal, for a database and for a service; a per-port null plans the removal and plans nothing once the proxy is gone; an omitted tcpProxies or privateNetworkEndpoint keeps what Railway has; an explicit public declaration plans the proxy. src/commands/config/mod.rs: pull renders the override line right after the helper call, strips service domains, and writes nothing for a private database.

`tcp: []` compiles to `tcpProxies: {}` while Railway serializes a
proxy-less service with no `tcpProxies` key, so a database (or service)
declared private re-planned `Update <name> networking` after every
successful apply. Empty networking maps now normalize away before the
diff.

Database nodes also import their live networking through the same helper
services use, `railway config pull` writes it back as
`db.networking = { tcpProxies: { "5432": {} } }` when a proxy exists, and
a database node without a networking block keeps the exposure it has.

Fixes #1168
`environmentApplyChangeSet` reads `networking` as a sparse patch: a key the
author leaves out stays as Railway has it, and inside `tcpProxies` an entry
keeps or creates a proxy, a `null` entry deletes one, and an unmentioned port
is left alone — so an empty map changes nothing.

Diffing the block as if it were the whole desired state promised three changes
the apply never makes, and re-planned every one of them after each apply:
`tcpProxies` left out, `privateNetworkEndpoint` left out, and `tcpProxies: {}`
against a live proxy.

The plan now diffs the block's effect, so a change is planned only when the
apply will move something, while the change set still carries the block as
written — that is the payload the apply sends, and a `null` entry is how a
proxy gets deleted. `tcpProxies: { "5432": null }` converges once the apply
has removed the proxy, and an empty map warns that it cannot remove one
instead of planning a removal that never lands.
@paulocsanz
paulocsanz merged commit f97e45c into master Sep 4, 2026
7 checks passed
@paulocsanz
paulocsanz deleted the pcs/iac-database-networking-converges branch September 4, 2026 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release/patch Author patch release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IaC: empty database TCP networking never converges; database import omits networking

1 participant