Skip to content

(chore) Railway previews: clone a template environment per PR instead of creating 13 services imperatively #5650

Description

@mmabrouk

Railway PR previews fail often, and every failure has needed its own patch. The root problem is architectural, not any single bug: each preview constructs a full deployment from nothing (1 project, 13 services, 3 volumes, ~100 variables, a domain) by issuing roughly 55-75 Railway CLI calls plus ~17 GraphQL calls, all non-transactional, many non-idempotent with ambiguous timeouts, against one shared account token. With that many remote writes per run, some run always fails, and a half-created environment cannot self-heal because "Re-run failed jobs" skips the green setup job. We have accumulated 22 distinct patched failure modes in hosting/railway (service-add ambiguity #5566, volume throttling, project-list races #4589, token confusion #4392, read-after-write races #5322, rate limits, and more). PR #5576 was the latest attempt to patch one of them.

Proposed change: clone, don't construct

Railway duplicates a fully configured environment in one call (environmentCreate with sourceEnvironmentId; CLI railway environment new --duplicate), copying services, variables, and settings. Railway's own documented CI recipe for PR previews is exactly this: clone on PR open, delete on PR close.

Target shape:

  • One always-on, fully configured template environment (13 services from GHCR images, volumes, variables, healthchecks). We evolve it once, through review, not on every PR.
  • Per PR: one clone call, then patch the 4 app image tags to the PR's GHCR tags (serviceInstanceUpdate + serviceInstanceDeployV2 each), run migrations and tests, and one environmentDelete on close.
  • Per-PR Railway mutations drop from ~75 to ~10-12. bootstrap.sh and most of configure.sh retire, and most of the historical failure catalog attaches to calls that no longer exist.

Prerequisite: the wrapper Dockerfiles that deploy-from-images.sh generates at deploy time (redis chown, seaweedfs IAM, nginx config, venv paths) move into the build workflow so GHCR images are self-contained and swapping the tag is the entire per-PR change.

Change management for the template

  • The template definition lives in the repo as versioned code (serialized config + an apply script), not as hand-clicked dashboard state. Changes are PRs.
  • App code and wrapper changes never touch the template; they ride the per-PR image tag. The template changes only for topology and config shape: add/remove/rename a service, add/remove a required variable, volume or mount changes, healthcheck/networking/domain changes, infra image bumps (postgres, redis, seaweedfs, supertokens), Railway settings.
  • Testing a template change: the PR clones the current template, applies the definition delta to the clone, and runs the normal deploy + smoke + tests against it. On merge, CI applies the definition to the live template environment.
  • Ordering rule, same discipline as DB migrations: additive template changes merge and apply first, then the code PR that depends on them. Template changes must stay backward compatible with the newest images on main.
  • Drift guard: a scheduled job diffs the live template environment against the versioned definition and fails loudly on hand-edits.

Plan

  • Spike (in a throwaway Railway project, no workflow changes): hand-build the template environment, then script the full cycle clone → patch image tags → deploy → smoke → delete, driving GraphQL directly. Success: 10 consecutive green cycles, ≤15 API calls per cycle, wall time at or below today's setup+deploy. Must answer with evidence: 504-then-poll behavior at 13-service scale, clone fidelity (reference variables, volumes, domains, private networking), whether template services can stay undeployed to save cost, and that the flow authenticates from a GitHub Actions PR job (account-token requirement; fork PRs stay unsupported as today).
  • Phase 1: publish self-contained wrapper images from the build workflow (also kills the wrapper-vs-compose drift failure family).
  • Phase 2: versioned template definition + apply script + clone/patch/delete scripts (GraphQL, retries, poll-by-name).
  • Phase 3: swap workflows 41/43/45 to the new flow; retire bootstrap.sh and most of configure.sh.
  • Change-management protocol live: template-change test flow and drift guard.

Known risks: the GraphQL API is effectively the dashboard's API and not contract-stable; environmentCreate has a documented history of 504-while-succeeding; sealed variables do not copy; per-preview secrets need an explicit overwrite list; the template environment needs an owner. Fallback if clone fidelity fails: templateDeployV2 with a serialized config we control.

Design workspace with the full failure catalog and platform research: docs/design/railway-preview-clone-spike/.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions