[{"data":1,"prerenderedAt":439},["ShallowReactive",2],{"blog-how-to/railway-environments-explained":3},{"id":4,"title":5,"body":6,"category":410,"date":411,"dateModified":411,"description":412,"draft":413,"extension":414,"faq":415,"featured":413,"headerVariant":423,"image":424,"keywords":425,"meta":426,"navigation":427,"ogDescription":428,"ogTitle":424,"path":429,"readTime":430,"schemaOrg":431,"schemaType":432,"seo":433,"sitemap":434,"stem":435,"tags":436,"twitterCard":437,"__hash__":438},"blog/blog/how-to/railway-environments-explained.md","Railway Environments: What Is Isolated and What Is Not (2026)",{"type":7,"value":8,"toc":396},"minimark",[9,13,16,22,37,42,49,59,68,71,79,83,139,146,152,156,159,164,171,178,188,191,195,198,201,205,208,215,225,228,232,237,242,248,254,260,274,280,284,312,326,367,384],[10,11,12],"p",{},"Railway's documentation for isolating staging from production contains one sentence that most people building on Railway have not read: \"every service with a public domain is reachable from the internet regardless of environment.\"",[10,14,15],{},"Your staging environment is not behind a wall. It is a second production, with a different URL and usually worse data hygiene.",[17,18,19],"tldr",{},[10,20,21],{},"A Railway environment isolates services, variables, volumes and the private network. It does not isolate who can see the project or who can reach the app. Duplicating production to make staging gives you a structural copy with a real public domain, an empty database, and (correctly) no sealed variables. The gap between \"isolated\" and \"private\" is where the exposure lives.",[23,24,25],"definition-box",{},[10,26,27,31,32,36],{},[28,29,30],"strong",{},"Railway environment",": per Railway's docs, \"isolated instances of all services in a project.\" Every project starts with one named ",[33,34,35],"code",{},"production",". \"All changes made to a service are scoped to a single environment,\" so editing a variable in staging does not touch production.",[38,39,41],"h2",{"id":40},"what-an-environment-actually-contains","What an environment actually contains",[10,43,44,45,48],{},"When you create a second environment, Railway gives you two options in the ",[33,46,47],{},"+ New Environment"," dropdown or under Settings, Environments:",[50,51,53],"step",{"number":52},"1",[10,54,55,58],{},[28,56,57],{},"Duplicate Environment."," Copies the selected environment \"including services, variables, and configuration.\" Every service is staged for deployment and waits for your approval before it goes live.",[50,60,62],{"number":61},"2",[10,63,64,67],{},[28,65,66],{},"Empty Environment."," No services. You add what you want.",[10,69,70],{},"Duplicate is what nearly everyone picks, because a staging environment that structurally matches production is the entire point.",[10,72,73,74,78],{},"Worth knowing if you inherited an older project: Railway deprecated ",[75,76,77],"em",{},"forked"," environments in January 2024 in favour of isolated environments with Sync. Environments forked before that change still exist and still behave the old way, so a project older than that may not match the current docs.",[38,80,82],{"id":81},"the-four-things-that-are-isolated","The four things that are isolated",[84,85,86,99],"table",{},[87,88,89],"thead",{},[90,91,92,96],"tr",{},[93,94,95],"th",{},"Isolated per environment",[93,97,98],{},"What that means in practice",[100,101,102,111,123,131],"tbody",{},[90,103,104,108],{},[105,106,107],"td",{},"Services",[105,109,110],{},"Each environment runs its own instances. A crash in staging does not touch production.",[90,112,113,116],{},[105,114,115],{},"Variables",[105,117,118,119,122],{},"Same variable name, different value per environment. ",[33,120,121],{},"DATABASE_URL"," resolves to that environment's database.",[90,124,125,128],{},[105,126,127],{},"Volumes and data",[105,129,130],{},"Sync copies configuration, not data. Databases, volumes, and storage bucket contents stay separate. A duplicated Postgres comes up empty.",[90,132,133,136],{},[105,134,135],{},"Private network",[105,137,138],{},"Each environment gets its own encrypted Wireguard mesh. Services talk over private DNS without exposing ports.",[10,140,141,142,145],{},"The private network detail is the one that trips people up in a good way. The internal DNS name is identical in both environments, so ",[33,143,144],{},"postgres.railway.internal"," works unchanged in staging and production and resolves to that environment's instance. Your code and your reference variables stay the same; only the environment they run in changes what they point at.",[147,148,149],"info-box",{},[10,150,151],{},"This is why \"it works in staging\" is a meaningful signal on Railway in a way it isn't everywhere. The connection string shape, the private DNS, and the variable names are genuinely the same. What differs is the data behind them.",[38,153,155],{"id":154},"the-two-things-that-are-not","The two things that are not",[10,157,158],{},"Railway's isolation guide is unusually direct about this. \"Two things are shared across environments and need their own handling: project membership (addressed with Environment RBAC) and public networking (every service with a public domain is reachable from the internet regardless of environment).\"",[160,161,163],"h3",{"id":162},"public-networking","Public networking",[10,165,166,167,170],{},"Duplicate production and the copy inherits its service configuration, which includes public domains. Railway generates a new ",[33,168,169],{},"*.up.railway.app"," domain for the staging copy. That domain is live, indexable, and reachable by anyone who finds it.",[10,172,173,174,177],{},"Nothing about it says \"staging\" to a scanner. It runs the same code, exposes the same endpoints, and answers the same requests. If your production app has an admin route protected by an environment variable check, and staging's copy of that variable is a placeholder someone set to ",[33,175,176],{},"true"," while debugging, the admin route is open on a public URL.",[179,180,181],"danger-box",{},[10,182,183,184,187],{},"Staging environments are where security controls get temporarily disabled and never re-enabled. Auth bypasses for testing, ",[33,185,186],{},"DEBUG=true",", relaxed CORS, a seeded admin account with a password someone will recognise. All of it on a public domain that inherits nothing protective from being called \"staging\".",[10,189,190],{},"We see this constantly in scans: the production domain is clean and a forgotten preview or staging domain on the same platform answers with a stack trace. The environment boundary protected the data. It did nothing about the front door.",[160,192,194],{"id":193},"project-membership","Project membership",[10,196,197],{},"On most plans, adding someone to a Railway project adds them to all of its environments. They can read production's variables because they can read the project.",[10,199,200],{},"Environment RBAC changes that, and it is an Enterprise feature. Marking production restricted means \"non-admin members and deployers can see the environment exists but cannot view its variables, logs, metrics, services, or configuration.\" Useful, but only if you're on a plan that has it. Below that, treat every project member as a production secret holder, because they are.",[38,202,204],{"id":203},"sealed-variables-and-the-sync-boundary","Sealed variables and the sync boundary",[10,206,207],{},"Sealed variables are the one place Railway deliberately breaks the copy. Per the isolation guide, \"sealed values are excluded from environment duplication, PR environments, environment sync diffs, and external integrations,\" so \"production secrets never leak into staging through a sync.\"",[10,209,210,211,214],{},"That is the right behaviour, and it produces a confusing symptom. Duplicate production to create staging, deploy, and the service crashes at boot because ",[33,212,213],{},"STRIPE_SECRET_KEY"," is not merely wrong, it is absent. The variable name does not exist, so there is no fallback and no obvious clue.",[216,217,218],"warning-box",{},[10,219,220,221,224],{},"Read the crash as the feature working. The fix is to set a ",[75,222,223],{},"staging"," value for that variable in the staging environment: a Stripe test key, a sandbox credential, a throwaway token. If you find yourself copying the production value across to make the error go away, you have manually recreated the leak Railway just prevented.",[10,226,227],{},"The general rule Railway's guide states is worth adopting even outside Railway: keep credentials environment-scoped. Same variable name, different value per environment, production keys only ever in production.",[38,229,231],{"id":230},"setting-up-staging-without-copying-productions-problems","Setting up staging without copying production's problems",[50,233,234],{"number":52},[10,235,236],{},"Duplicate production. Do not approve the staged deployments yet.",[50,238,239],{"number":61},[10,240,241],{},"Go through the variable list before the first deploy. Every sealed variable is missing and needs a non-production value. Every unsealed secret was copied verbatim and needs replacing with a test credential. That second category is the dangerous one, because nothing breaks to remind you.",[50,243,245],{"number":244},"3",[10,246,247],{},"Decide whether staging needs a public domain at all. If it is only for you and CI, remove the generated domain and reach it over the private network or the Railway CLI. Removing the domain is the single highest-value step here, and it is one click.",[50,249,251],{"number":250},"4",[10,252,253],{},"If it does need to be public, put something in front of it. Basic auth middleware, an allowlist, or a shared token header. Any of them beats an unauthenticated copy of your app on a guessable subdomain.",[50,255,257],{"number":256},"5",[10,258,259],{},"Seed staging with generated data. Restoring a production dump into staging moves every real customer record onto that public domain and undoes the one isolation guarantee Railway gave you for free.",[261,262,264],"code-block",{"label":263},"Switching environments from the CLI",[265,266,271],"pre",{"className":267,"code":269,"language":270},[268],"language-text","railway environment                 # interactive picker\nrailway environment staging         # switch directly\nrailway variables                   # list what this environment resolves to\nrailway run npm start               # run locally against this environment's variables\n","text",[33,272,269],{"__ignoreMap":273},"",[10,275,276,279],{},[33,277,278],{},"railway run"," is the fastest way to catch a variable that only exists in production. Run it against staging and watch what fails to start.",[38,281,283],{"id":282},"a-ten-minute-audit","A ten-minute audit",[285,286,287,292,296,300,304,308],"checklist-section",{},[288,289],"checklist-item",{"description":290,"label":291},"Settings, Environments. Old PR environments should be gone automatically; persistent ones people made and forgot are the risk.","List every environment in the project",[288,293],{"description":294,"label":295},"Anything non-production with a domain is on the internet. Decide deliberately, do not inherit it from the duplicate.","Check which services have a public domain in each environment",[288,297],{"description":298,"label":299},"If it loads your app without asking who you are, so does everyone else.","Open each non-production public URL in a private window",[288,301],{"description":302,"label":303},"A copied production API key with the right name in the wrong environment is invisible in the dashboard.","Compare variable values between environments, not just names",[288,305],{"description":306,"label":307},"Sealing also stops it being copied into duplicates and PR environments, which is the point.","Seal anything that is genuinely a secret",[288,309],{"description":310,"label":311},"Volumes are separate until someone deliberately copies them across.","Confirm no production data was restored into a non-production database",[10,313,314,315,320,321,325],{},"For the mechanics of adding and scoping variables in the first place, the ",[316,317,319],"a",{"href":318},"/blog/how-to/railway-env-vars","Railway environment variables guide"," covers the dashboard and CLI paths. If you have PR environments turned on, ",[316,322,324],{"href":323},"/blog/how-to/railway-pr-environments","what they copy from production"," covers the ephemeral case, which has a different default base and a different failure mode.",[327,328,329,339,349,355,361],"faq-section",{},[330,331,333],"faq-item",{"question":332},"What is a Railway environment?",[10,334,335,336,338],{},"Railway's docs define one as an isolated instance of all services in a project. Every project starts with one called ",[33,337,35],{},", and all changes made to a service are scoped to a single environment, so editing staging never touches production.",[330,340,342],{"question":341},"Does duplicating a Railway environment copy my database contents?",[10,343,344,345,348],{},"No. It copies the database ",[75,346,347],{},"service",", not the rows in it. Railway's sync copies configuration rather than data, so volumes, database contents, and storage bucket contents stay separate per environment. A duplicated Postgres comes up empty and your migrations have to run.",[330,350,352],{"question":351},"Why is my variable missing after I duplicated an environment?",[10,353,354],{},"It was sealed. Railway excludes sealed values from environment duplication, PR environments, sync diffs, and external integrations, so the name is absent entirely rather than set to a wrong value. Set a non-production value for it in the new environment.",[330,356,358],{"question":357},"Is my Railway staging environment public?",[10,359,360],{},"If any service in it has a public domain, yes. Railway's isolation guide lists public networking as one of two things shared across environments: every service with a public domain is reachable from the internet regardless of which environment it lives in. Remove the domain if staging does not need one.",[330,362,364],{"question":363},"Can I stop teammates from seeing production variables?",[10,365,366],{},"Only with Environment RBAC, an Enterprise feature that lets you mark production restricted so non-admin members and deployers can see the environment exists but cannot view its variables, logs, metrics, services, or configuration. On other plans, project membership covers every environment in the project.",[368,369,370,375,379],"related-articles",{},[371,372],"related-card",{"description":373,"href":318,"title":374},"Adding, scoping, and securing variables in Railway, dashboard and CLI.","Railway Environment Variables",[371,376],{"description":377,"href":323,"title":378},"What a pull request preview copies from production, and the one setting that fixes it.","Railway PR Environments",[371,380],{"description":381,"href":382,"title":383},"Project-level variables, what linking one actually does, and when it reaches the build.","/blog/how-to/railway-shared-variables","Railway Shared Variables",[385,386,389,393],"cta-box",{"href":387,"label":388},"/","Start Free Scan",[38,390,392],{"id":391},"found-a-staging-url-you-forgot-about","Found a staging URL you forgot about?",[10,394,395],{},"Point CheckYourVibe at it. The scanner reads it the way a stranger would, headers and all.",{"title":273,"searchDepth":397,"depth":397,"links":398},2,[399,400,401,406,407,408,409],{"id":40,"depth":397,"text":41},{"id":81,"depth":397,"text":82},{"id":154,"depth":397,"text":155,"children":402},[403,405],{"id":162,"depth":404,"text":163},3,{"id":193,"depth":404,"text":194},{"id":203,"depth":397,"text":204},{"id":230,"depth":397,"text":231},{"id":282,"depth":397,"text":283},{"id":391,"depth":397,"text":392},"how-to","2026-08-27","Railway environments isolate services, variables, volumes and the private network. Railway's own docs name the two things they do not, and both face outward.",false,"md",[416,418,420,421,422],{"question":332,"answer":417},"Railway's docs define one as an isolated instance of all services in a project. Every project starts with one called production, and all changes made to a service are scoped to a single environment, so editing staging never touches production.",{"question":341,"answer":419},"No. It copies the database service, not the rows in it. Railway's sync copies configuration rather than data, so volumes, database contents, and storage bucket contents stay separate per environment. A duplicated Postgres comes up empty and your migrations have to run.",{"question":351,"answer":354},{"question":357,"answer":360},{"question":363,"answer":366},"yellow",null,"railway environments, railway environment, railway staging environment, railway duplicate environment, railway environment sync, railway environment variables scope, railway environment isolation",{},true,"Duplicating production gives you a structural copy with a real public domain. What Railway's environment boundary covers, and what it leaves open.","/blog/how-to/railway-environments-explained","9 min read","[object Object]","HowTo",{"title":5,"description":412},{"loc":429},"blog/how-to/railway-environments-explained",[],"summary_large_image","HVkzQIgUsgTg79eY4iGdTkjNlOrvXy1pCyu1CluLUJM",1789672859777]