[{"data":1,"prerenderedAt":465},["ShallowReactive",2],{"blog-how-to/railway-shared-variables":3},{"id":4,"title":5,"body":6,"category":434,"date":435,"dateModified":435,"description":436,"draft":437,"extension":438,"faq":439,"featured":437,"headerVariant":450,"image":451,"keywords":452,"meta":453,"navigation":114,"ogDescription":454,"ogTitle":451,"path":455,"readTime":456,"schemaOrg":457,"schemaType":458,"seo":459,"sitemap":460,"stem":461,"tags":462,"twitterCard":463,"__hash__":464},"blog/blog/how-to/railway-shared-variables.md","Railway Shared Variables: How They Work and What They Expose (2026)",{"type":7,"value":8,"toc":426},"minimark",[9,13,16,32,37,40,51,57,66,73,128,132,135,146,149,159,162,171,175,178,181,261,272,275,281,284,287,291,294,318,321,325,328,331,387,410,422],[10,11,12],"p",{},"A Railway shared variable is not a broadcast. It's a value you define once in Project Settings, and then link, service by service, on purpose. Railway's docs are explicit about the linking step: you either \"click the Share button from Project Settings and select specific services\" or open a service's Variables tab and click \"Shared Variable.\"",[10,14,15],{},"That distinction matters more than it sounds, because most of the security advice written about this feature assumes the opposite. If shared variables really did land in every service automatically, the advice would be \"use fewer of them.\" They don't, so the actual risk lives somewhere else: in which services you link, and in what happens to those links when Railway copies an environment.",[17,18,19],"tldr",{},[10,20,21,22,26,27,31],{},"Shared variables sit at the project level, scoped per environment, and each service opts in by linking one. Linking writes a reference of the form ",[23,24,25],"code",{},"${{shared.KEY}}"," into that service. The exposure to watch is that a linked variable is available to the service's ",[28,29,30],"strong",{},"build"," as well as its runtime, and that sealed variables silently do not follow into PR environments, duplicated environments, or duplicated services.",[33,34,36],"h2",{"id":35},"how-linking-actually-works","How Linking Actually Works",[10,38,39],{},"Three things happen in sequence, and the middle one is the part people skip.",[41,42,44],"step",{"number":43},"1",[10,45,46,47,50],{},"You add the variable under ",[28,48,49],{},"Project Settings, Shared Variables",", picking the environment first. Production and staging each hold their own value under the same key.",[41,52,54],{"number":53},"2",[10,55,56],{},"You link it to specific services. Nothing reaches a service until you do this.",[41,58,60],{"number":59},"3",[10,61,62,63,65],{},"Railway writes a reference variable into the linked service, in the form ",[23,64,25],{},". It is a pointer, not a copy.",[10,67,68,69,72],{},"The pointer behavior is the useful half. Rotate ",[23,70,71],{},"STRIPE_SECRET_KEY"," once in Project Settings and every linked service picks up the new value on its next deploy, with no chance of one service being left on the old key. That is a genuine improvement over pasting the same secret into six Variables tabs.",[74,75,77],"code-block",{"label":76},"What the reference looks like in a service",[78,79,84],"pre",{"className":80,"code":81,"language":82,"meta":83,"style":83},"language-bash shiki shiki-themes github-dark","# In the service's Variables tab\nAPI_KEY=${{shared.API_KEY}}\n\n# Resolves at build and deploy time to whatever\n# Project Settings holds for the current environment\n","bash","",[23,85,86,95,109,116,122],{"__ignoreMap":83},[87,88,91],"span",{"class":89,"line":90},"line",1,[87,92,94],{"class":93},"sAwPA","# In the service's Variables tab\n",[87,96,98,102,106],{"class":89,"line":97},2,[87,99,101],{"class":100},"s95oV","API_KEY",[87,103,105],{"class":104},"snl16","=",[87,107,108],{"class":100},"${{shared.API_KEY}}\n",[87,110,112],{"class":89,"line":111},3,[87,113,115],{"emptyLinePlaceholder":114},true,"\n",[87,117,119],{"class":89,"line":118},4,[87,120,121],{"class":93},"# Resolves at build and deploy time to whatever\n",[87,123,125],{"class":89,"line":124},5,[87,126,127],{"class":93},"# Project Settings holds for the current environment\n",[33,129,131],{"id":130},"the-blast-radius-question","The Blast Radius Question",[10,133,134],{},"Because linking is manual, every link is a decision you made. The question worth asking on each one is not \"does this service use the value\" but \"what else runs inside this service.\"",[10,136,137,138,141,142,145],{},"Railway injects variables into four contexts, per its own documentation: the build process for each service deployment, the running deployment, any command you run through ",[23,139,140],{},"railway run",", and ",[23,143,144],{},"railway shell",".",[10,147,148],{},"The first of those is the one that surprises people. A shared database password linked to a frontend service is present during that service's build, which means it's present while npm executes install scripts for every package in that dependency tree. You didn't grant a secret to your code. You granted it to your lockfile.",[150,151,152],"warning-box",{},[10,153,154,155,158],{},"This is the concrete cost of a lazy link. Adding ",[23,156,157],{},"${{shared.DATABASE_URL}}"," to a static frontend \"so the build can prerender some pages\" hands your production database credentials to several hundred packages' postinstall hooks. We flag this shape constantly in scans, and it almost never comes from someone being careless with the secret itself. It comes from someone being casual about which service gets the link.",[10,160,161],{},"The fix is boring and it works: link a shared variable to the services that need it at the moment they need it, and unlink when a service stops needing it. Unlinking is a real operation, not just deleting a line, and it's the step that never happens because nothing breaks when you skip it.",[10,163,164,165,170],{},"If your build genuinely needs a value that the browser will also see, that's a different problem with a different failure mode. ",[166,167,169],"a",{"href":168},"/blog/how-to/railway-reference-variables","Railway reference variables and the build-time leak"," covers what happens when a value ends up baked into a client bundle.",[33,172,174],{"id":173},"sealed-variables-and-the-four-places-they-dont-follow","Sealed Variables and the Four Places They Don't Follow",[10,176,177],{},"Sealing is Railway's answer to \"I don't want this readable from the dashboard.\" A sealed variable's value goes to builds and deployments but, in Railway's words, is \"never visible in the UI nor can it be retrieved via the API.\"",[10,179,180],{},"It's the right control for production secrets. It also has a set of documented gaps that turn into outages if you don't know them going in.",[182,183,184,197],"table",{},[185,186,187],"thead",{},[188,189,190,194],"tr",{},[191,192,193],"th",{},"Situation",[191,195,196],{},"What happens to a sealed variable",[198,199,200,209,216,223,237,245,253],"tbody",{},[188,201,202,206],{},[203,204,205],"td",{},"Creating a PR environment",[203,207,208],{},"Not copied",[188,210,211,214],{},[203,212,213],{},"Duplicating an environment",[203,215,208],{},[188,217,218,221],{},[203,219,220],{},"Duplicating a service",[203,222,208],{},[188,224,225,234],{},[203,226,227,230,231,233],{},[23,228,229],{},"railway variables"," or ",[23,232,140],{}," from the CLI",[203,235,236],{},"Value not provided",[188,238,239,242],{},[203,240,241],{},"Syncing environment changes",[203,243,244],{},"Not shown in the diff",[188,246,247,250],{},[203,248,249],{},"External integrations",[203,251,252],{},"Not synced",[188,254,255,258],{},[203,256,257],{},"Trying to un-seal it",[203,259,260],{},"Not possible, ever",[10,262,263,264,267,268,271],{},"The PR environment row is the one that bites. You open a pull request, Railway spins up an environment, and the service crashes on boot because ",[23,265,266],{},"DATABASE_URL"," isn't there. Note the failure mode: the variable is ",[28,269,270],{},"absent",", not empty. Your app doesn't quietly connect to the wrong database, it fails to start.",[10,273,274],{},"That's the good outcome. The bad outcome is what people do next.",[276,277,278],"danger-box",{},[10,279,280],{},"The natural workaround is to add the missing value straight into the PR environment as a normal, unsealed variable so the preview finally boots. Now a production credential exists in plaintext, in a short-lived environment, readable from the dashboard and the API, on a branch anyone with repo access can push to. The seal you set up on production is doing nothing for the copy you just made of it.",[10,282,283],{},"Do this instead: give PR environments their own credentials. A separate database, a Stripe test key, a scoped-down API token. If a preview environment can reach production data, the preview environment is production as far as an attacker is concerned.",[10,285,286],{},"The un-sealing row deserves its own note. Since you can never read a sealed value back, store it somewhere durable before you seal it. Otherwise recovering it means rotating at the provider, which for something like a Stripe live key is a coordinated change across every service holding the old one.",[33,288,290],{"id":289},"a-five-minute-audit","A Five-Minute Audit",[10,292,293],{},"Open Project Settings, Shared Variables, and go environment by environment.",[295,296,297,302,306,310,314],"checklist-section",{},[298,299],"checklist-item",{"description":300,"label":301},"Railway shows this from the Share button. Anything linked to more services than you can justify out loud is the first thing to trim.","List which services each variable is linked to",[298,303],{"description":304,"label":305},"Those builds run untrusted install scripts. A secret linked there is a secret shared with your dependency tree.","Flag every link into a frontend or static service",[298,307],{"description":308,"label":309},"If a value is readable in the dashboard, treat it as readable by everyone with project access, including anyone who still has access and shouldn't.","Confirm production secrets are sealed",[298,311],{"description":312,"label":313},"Sealed values did not come along. Whatever those services are using instead is either a separate credential you set up on purpose, or a copy of production someone pasted in to unblock a preview.","Check what your PR environments are actually connecting to",[298,315],{"description":316,"label":317},"Nothing breaks when a stale link stays, which is exactly why they accumulate. Grep the service for the key name before you decide it is unused.","Unlink variables from services that no longer use them",[10,319,320],{},"The fourth item is where the real findings tend to be. Preview environments get set up once, in a hurry, by whoever needed the PR to deploy that afternoon.",[33,322,324],{"id":323},"when-a-service-variable-is-the-better-choice","When a Service Variable Is the Better Choice",[10,326,327],{},"Shared variables earn their keep when several services need the same value and you want one rotation point. When only one service needs it, a plain service variable is stricter and costs you nothing.",[10,329,330],{},"A rough rule: if the answer to \"which services need this\" is one service, don't share it. If it's every service, ask why, because a value that genuinely every service needs is often either a project-wide config flag (fine to share) or a credential with far too much scope (worth splitting before you share it).",[332,333,334,341,357,372,378],"faq-section",{},[335,336,338],"faq-item",{"question":337},"Are Railway shared variables automatically available to every service?",[10,339,340],{},"No. The variable is defined once at the project level, but each service has to link it before it shows up in that service's environment. Railway's docs describe the two ways to do it: the Share button in Project Settings, or the \"Shared Variable\" option in a service's Variables tab. Until you do one of those, the value exists and reaches nothing.",[335,342,344],{"question":343},"What is the syntax for a Railway shared variable?",[10,345,346,348,349,352,353,356],{},[23,347,25],{},", where ",[23,350,351],{},"KEY"," is the shared variable's name. A linked service ends up with a line like ",[23,354,355],{},"API_KEY=${{shared.API_KEY}}",". Railway writes that reference for you during linking, so you rarely type it by hand, but it is worth recognising in a Variables tab because it tells you the value is a pointer to the project rather than a copy living on the service.",[335,358,360],{"question":359},"Are shared variables scoped per environment?",[10,361,362,363,365,366,368,369,371],{},"Yes. You choose the environment when adding one in Project Settings, so ",[23,364,266],{}," under production and ",[23,367,266],{}," under staging are separate values sharing a name. That is what makes the pattern work: a service references ",[23,370,157],{}," once and resolves to the right database depending on where it's deployed.",[335,373,375],{"question":374},"Why is my sealed variable missing in a Railway PR environment?",[10,376,377],{},"Railway does not copy sealed variables into PR environments, and the same is true when duplicating an environment or a service. The variable is absent rather than empty, so the service usually fails at startup instead of connecting somewhere unexpected. Give PR environments their own non-production credentials rather than pasting the production value in unsealed to get the preview running.",[335,379,381],{"question":380},"Can I un-seal a Railway variable?",[10,382,383,384,386],{},"No. Railway's documentation states that sealed variables cannot be un-sealed, and the value is not retrievable through the UI, the API, or ",[23,385,229],{}," from the CLI. Save the value in your own password manager before sealing it. Recovering it otherwise means rotating the credential at its source.",[388,389,390,396,400,405],"related-articles",{},[391,392],"related-card",{"description":393,"href":394,"title":395},"The full variables workflow, from the dashboard UI to the CLI","/blog/how-to/railway-env-vars","Railway Environment Variables: Complete Setup Guide",[391,397],{"description":398,"href":168,"title":399},"The three reference forms, the git variables, and the build-time leak","Railway Reference Variables and RAILWAY_PUBLIC_DOMAIN",[391,401],{"description":402,"href":403,"title":404},"What to do when a key you deployed on Railway has already leaked","/blog/how-to/fix-railway-api-key-exposure","Fix Railway API Key Exposure",[391,406],{"description":407,"href":408,"title":409},"How sealing works, why it cannot be undone, and the five ways a sealed secret still leaves your project.","/blog/how-to/railway-secrets","Railway Secrets and Sealed Variables",[411,412,415,419],"cta-box",{"href":413,"label":414},"/","Start Free Scan",[33,416,418],{"id":417},"see-what-your-deployed-app-exposes","See What Your Deployed App Exposes",[10,420,421],{},"Our scanner checks your live site for secrets baked into client bundles, exposed config endpoints, and the misconfigurations that follow a variable into production.",[423,424,425],"style",{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":83,"searchDepth":97,"depth":97,"links":427},[428,429,430,431,432,433],{"id":35,"depth":97,"text":36},{"id":130,"depth":97,"text":131},{"id":173,"depth":97,"text":174},{"id":289,"depth":97,"text":290},{"id":323,"depth":97,"text":324},{"id":417,"depth":97,"text":418},"how-to","2026-08-14","Shared variables are project-level values each service links on purpose, not a broadcast. Here is how the linking works, what it puts in every build, and the sealed-variable gap that breaks PR environments.",false,"md",[440,442,444,446,448],{"question":337,"answer":441},"No. A shared variable is defined once at the project level, but each service has to link it before it appears in that service's environment. Railway adds it as a reference variable in the form ${{shared.KEY}} when you link it.",{"question":343,"answer":443},"${{shared.KEY}} where KEY is the shared variable name. For example API_KEY=${{shared.API_KEY}}. Railway writes this reference for you when you link the variable through the UI.",{"question":359,"answer":445},"Yes. You pick the environment when you add a shared variable in Project Settings, so production and staging hold separate values under the same name. That is what lets a service reference ${{shared.DATABASE_URL}} once and resolve to a different database per environment.",{"question":374,"answer":447},"Because Railway does not copy sealed variables into PR environments. The same applies when you duplicate an environment or duplicate a service. The variable name is absent entirely, so the service fails at startup rather than falling back to a default.",{"question":380,"answer":449},"No. Railway's docs state plainly that sealed variables cannot be un-sealed. If you need the value back, you have to rotate it at the source and add a fresh variable, so seal a value only after you have stored it somewhere you control.","yellow",null,"railway shared variables, railway shared variable, railway project variables, railway sealed variables, railway pr environment variables, shared.VARIABLE railway",{},"How Railway shared variables actually work, why linking one to the wrong service widens your blast radius, and the four places sealed variables silently do not follow.","/blog/how-to/railway-shared-variables","8 min read","[object Object]","HowTo",{"title":5,"description":436},{"loc":455},"blog/how-to/railway-shared-variables",[],"summary_large_image","Uyif_tKYkhZyU1wIdBtRwDOCP3fPDbljA2hnKIrkKJU",1787602618885]