Turn on Railway PR environments and every pull request gets a running copy of your app at a public URL. Railway's docs describe what that copy contains without much ceremony: a PR environment "replicates your entire base environment, including services, networking, and variables."
That base is production by default.
TL;DR
Railway PR environments clone your base environment, variables included, and the default base is production. Sealed variables are the exception: Railway never copies them, so the safe thing breaks your preview and the unsafe thing works fine. The fix is one setting, and it is not per-variable overrides. Change the base environment PR previews inherit from, so they clone staging instead.
What a PR environment actually contains
When you enable the feature under Project Settings then Environments, Railway starts watching for pull requests. Open one and it provisions a full copy of the base environment: your services, the private networking between them, and every variable those services can see. Merge or close the PR and Railway de-provisions the whole thing.
The convenience is real. Your branch deploy gets the same DATABASE_URL, the same API keys, and the same reference variables as production, with nothing to configure. That is also the problem.
The default base is your production environment
Railway's guide states it plainly: "By default, this base is your production environment, but you can change which environment PR Environments inherit from in your Railway project settings."
If you have never opened that setting, every preview deploy in your project has been running with production credentials.
The preview is reachable too. Asked directly on Railway's community forum how domains work for previews, Railway employee Brody answered: "As long as the service in the base environment has a service domain, the service in the PR environment will also have a domain, and we will show that in a table that our bot makes in a commit on the GitHub PR."
So the URL is public, and it is posted into the pull request where anyone with repo read access can see it. Nothing sits in front of it except whatever auth your app implements.
The sealed variable trap
Railway's answer to "don't let people read this secret" is sealing. A sealed variable is "provided to builds and deployments but is never visible in the UI nor can it be retrieved via the API," and the CLI won't return it either.
Now read the restriction Railway lists on the same page:
Sealed variables are not copied into PR environments
Two lines from Railway's variables documentation: "Sealed variables are not copied over when creating PR environments" and "Sealed variables are not copied when duplicating an environment."
The variable isn't blanked or set to a placeholder. It's absent. Your service starts, reads process.env.STRIPE_SECRET_KEY, gets undefined, and crashes or silently misbehaves.
Put those two behaviours next to each other and you get a genuinely bad incentive:
- Seal the secret, and preview deploys break with a missing-variable crash on every PR.
- Leave it unsealed, and the production value is copied into a publicly-addressable environment every time anyone opens a pull request.
Whoever is on call for "previews are broken again" will reach for the second option. It fixes the build in about four seconds and nothing in the UI suggests a cost. Sealing also can't be undone, so the person who sealed it originally may not even be able to walk it back without rotating the key.
This is the same asymmetry we've written about in Railway shared variables: the protection Railway offers is real, but it stops following the variable at exactly the boundaries where a copy of your environment gets created.
Fix it at the base, not per variable
The obvious instinct is to override individual variables inside each PR environment. That path is thinner than it looks. A developer on Railway's forum ran into it directly, reporting that "PR environment overrides does not enable custom environment variable overrides to be set," and no Railway staff correction followed. You're not going to talk your way out of this one variable at a time.
Change what previews inherit from instead.
Create a staging environment if you don't have one. Give it its own database, its own Stripe test keys, its own everything. This is the environment you're willing to have copied onto a public URL.
In Project Settings then Environments, change the base environment for PR environments from production to staging.
Open a throwaway pull request. Read the variables Railway provisioned into the resulting environment and confirm none of them are live credentials. Do this by eye once, because the whole failure mode here is assuming rather than looking.
Seal the production secrets in production only. They were never going to reach a PR environment anyway, and now that's a property of your setup instead of an accident.
After that change, a preview running with a leaked staging key is an annoyance. Before it, it's an incident.
What Railway already does right
One risk that sounds obvious turns out not to apply, and it's worth knowing so you don't spend a sprint defending against it.
Railway won't deploy a pull request branch from someone outside your workspace unless that person has been invited to the project with the same GitHub account that opened the PR. A drive-by PR from a stranger on a public repo does not provision an environment and does not get your variables. That is a deliberate safety property, and it means the exposure here is scoped to people who already have some level of access.
The remaining surface is the preview URL itself, which is public, indexed by nothing but shared in a place plenty of people can read.
Railway sets RAILWAY_GIT_BRANCH, RAILWAY_GIT_COMMIT_SHA, and RAILWAY_GIT_AUTHOR in PR environments. Check for RAILWAY_GIT_BRANCH at boot and refuse to start if a production-only credential is also present. That turns "someone left a live key in a preview" from a thing you find out about later into a failed deploy.
A quick audit
Five minutes, and you'll know where you stand:
Once the preview is deployed, the variables are only half the story. Whatever the app exposes on that public URL is exposed for real: open endpoints, keys shipped into the client bundle, missing auth on an admin route. CheckYourVibe scans a deployed URL, and a preview URL works as well as a production one, which makes a PR environment a reasonable place to catch a problem before it ships.
Do Railway PR environments inherit production variables?
Yes, by default. Railway's docs state that a PR environment replicates your entire base environment including services, networking, and variables, and that the base is your production environment unless you change it in Project Settings.
How do I enable PR environments in Railway?
Go to Project Settings, open the Environments tab, and click Enable PR Environments. Railway then spins up a temporary environment for each pull request and de-provisions all its services when the PR is merged or closed.
Why is my sealed variable missing in a Railway PR environment?
Railway does not copy sealed variables into PR environments, and the same restriction applies when you duplicate an environment. The variable name is absent rather than empty, so the service fails at startup instead of falling back to a default value.
Can someone open a pull request to get my Railway variables?
No. Railway will not deploy a PR branch from a user outside your workspace unless they have been invited to the project with the GitHub account that opened the pull request. An outside contributor's PR does not provision an environment.
Are Railway preview URLs public?
Yes. If the base environment's service has a domain, the PR environment's copy gets one too, and Railway's bot posts it in a table on the GitHub pull request. Nothing authenticates that URL unless your application does.
Check What Your Preview Is Exposing
Paste a Railway preview URL into CheckYourVibe and see the exposed keys, missing auth, and open endpoints before the branch reaches production.