[{"data":1,"prerenderedAt":842},["ShallowReactive",2],{"blog-how-to/railway-reference-variables":3},{"id":4,"title":5,"body":6,"category":808,"date":809,"dateModified":809,"description":810,"draft":811,"extension":812,"faq":813,"featured":811,"headerVariant":827,"image":828,"keywords":829,"meta":830,"navigation":123,"ogDescription":831,"ogTitle":828,"path":832,"readTime":833,"schemaOrg":834,"schemaType":835,"seo":836,"sitemap":837,"stem":838,"tags":839,"twitterCard":840,"__hash__":841},"blog/blog/how-to/railway-reference-variables.md","Railway Reference Variables and RAILWAY_PUBLIC_DOMAIN (2026)",{"type":7,"value":8,"toc":796},"minimark",[9,26,33,71,76,82,168,179,189,192,201,207,253,256,276,281,291,294,340,344,347,406,411,469,484,495,499,502,513,516,566,576,584,587,591,594,643,649,655,679,683,761,780,792],[10,11,12,13,17,18,21,22,25],"p",{},"Railway injects about a dozen variables into every deployment that you never declared. ",[14,15,16],"code",{},"RAILWAY_PUBLIC_DOMAIN"," holds your service's URL. ",[14,19,20],{},"RAILWAY_GIT_COMMIT_SHA"," holds the commit that triggered the deploy. You reference them, and each other's variables, with a ",[14,23,24],{},"${{ }}"," template syntax that resolves before your app ever starts.",[10,27,28,29,32],{},"That resolution timing is the whole story. Railway hands variables to the build process as well as the running service, so a reference behind a ",[14,30,31],{},"VITE_"," prefix doesn't stay a reference. It becomes a literal string in the JavaScript you ship to browsers.",[34,35,36],"tldr",{},[10,37,38,39,42,43,46,47,50,51,53,54,57,58,60,61,53,63,66,67,70],{},"Reference variables use ",[14,40,41],{},"${{ SERVICE.VAR }}"," for another service, ",[14,44,45],{},"${{ shared.KEY }}"," for project-level shared variables, and ",[14,48,49],{},"${{ VAR }}"," for the same service. Railway also injects built-ins like ",[14,52,16],{},", ",[14,55,56],{},"RAILWAY_PRIVATE_DOMAIN"," and ",[14,59,20],{}," that you can reference the same way. All of them resolve at build time as well as runtime, so any reference sitting behind a ",[14,62,31],{},[14,64,65],{},"NEXT_PUBLIC_"," or ",[14,68,69],{},"PUBLIC_"," prefix gets baked into your client bundle, sealed or not.",[72,73,75],"h2",{"id":74},"the-three-reference-forms","The three reference forms",[10,77,78,79,81],{},"Every Railway reference is a ",[14,80,24],{}," expression in a variable's value field. Which namespace you use depends on where the source value lives.",[83,84,86],"code-block",{"label":85},"All three forms",[87,88,93],"pre",{"className":89,"code":90,"language":91,"meta":92,"style":92},"language-bash shiki shiki-themes github-dark","# Another service in the same project\nDATABASE_URL=${{ Clickhouse.DATABASE_URL }}\n\n# A project-level shared variable\nSTRIPE_SECRET_KEY=${{ shared.STRIPE_SECRET_KEY }}\n\n# Another variable on this same service\nAUTH_ENDPOINT=https://${{ BASE_URL }}/${{ AUTH_PATH }}\n","bash","",[14,94,95,104,118,125,131,142,147,153],{"__ignoreMap":92},[96,97,100],"span",{"class":98,"line":99},"line",1,[96,101,103],{"class":102},"sAwPA","# Another service in the same project\n",[96,105,107,111,115],{"class":98,"line":106},2,[96,108,110],{"class":109},"s95oV","DATABASE_URL",[96,112,114],{"class":113},"snl16","=",[96,116,117],{"class":109},"${{ Clickhouse.DATABASE_URL }}\n",[96,119,121],{"class":98,"line":120},3,[96,122,124],{"emptyLinePlaceholder":123},true,"\n",[96,126,128],{"class":98,"line":127},4,[96,129,130],{"class":102},"# A project-level shared variable\n",[96,132,134,137,139],{"class":98,"line":133},5,[96,135,136],{"class":109},"STRIPE_SECRET_KEY",[96,138,114],{"class":113},[96,140,141],{"class":109},"${{ shared.STRIPE_SECRET_KEY }}\n",[96,143,145],{"class":98,"line":144},6,[96,146,124],{"emptyLinePlaceholder":123},[96,148,150],{"class":98,"line":149},7,[96,151,152],{"class":102},"# Another variable on this same service\n",[96,154,156,159,161,165],{"class":98,"line":155},8,[96,157,158],{"class":109},"AUTH_ENDPOINT",[96,160,114],{"class":113},[96,162,164],{"class":163},"sU2Wk","https://",[96,166,167],{"class":109},"${{ BASE_URL }}/${{ AUTH_PATH }}\n",[10,169,170,171,174,175,178],{},"Two details bite people. The service name is the name on your project canvas, exactly as displayed, and it's case sensitive: ",[14,172,173],{},"${{ postgres.DATABASE_URL }}"," won't resolve if the service is called ",[14,176,177],{},"Postgres",". And references compose with plain text, so you build full URLs inline rather than storing a pre-assembled string that goes stale when a domain changes.",[180,181,182],"tip-box",{},[10,183,184,185,188],{},"Referencing beats copying for one reason that shows up months later: rotation. If four services each hold their own copy of a Stripe key, rotating it means four edits and one you'll forget. Point all four at ",[14,186,187],{},"${{ shared.STRIPE_SECRET_KEY }}"," and rotation is a single field.",[72,190,16],{"id":191},"railway_public_domain",[10,193,194,196,197,200],{},[14,195,16],{}," is injected into every build and deployment with no setup. It holds the public domain of the service, something like ",[14,198,199],{},"example.up.railway.app",".",[10,202,203,204,206],{},"It contains the host only. No ",[14,205,164],{},", no trailing slash. So this is the shape you want:",[83,208,210],{"label":209},"Building URLs from the built-in",[87,211,213],{"className":89,"code":212,"language":91,"meta":92,"style":92},"# On the backend service itself\nWEBHOOK_URL=https://${{ RAILWAY_PUBLIC_DOMAIN }}/webhooks/stripe\n\n# On the frontend service, pointing at the backend\nAPI_URL=https://${{ backend.RAILWAY_PUBLIC_DOMAIN }}\n",[14,214,215,220,232,236,241],{"__ignoreMap":92},[96,216,217],{"class":98,"line":99},[96,218,219],{"class":102},"# On the backend service itself\n",[96,221,222,225,227,229],{"class":98,"line":106},[96,223,224],{"class":109},"WEBHOOK_URL",[96,226,114],{"class":113},[96,228,164],{"class":163},[96,230,231],{"class":109},"${{ RAILWAY_PUBLIC_DOMAIN }}/webhooks/stripe\n",[96,233,234],{"class":98,"line":120},[96,235,124],{"emptyLinePlaceholder":123},[96,237,238],{"class":98,"line":127},[96,239,240],{"class":102},"# On the frontend service, pointing at the backend\n",[96,242,243,246,248,250],{"class":98,"line":133},[96,244,245],{"class":109},"API_URL",[96,247,114],{"class":113},[96,249,164],{"class":163},[96,251,252],{"class":109},"${{ backend.RAILWAY_PUBLIC_DOMAIN }}\n",[10,254,255],{},"That second line is the pattern worth internalizing. A frontend and backend deployed together in one project no longer need a hardcoded API URL per environment, because each environment resolves its own.",[257,258,259],"warning-box",{},[10,260,261,263,264,267,268,271,272,275],{},[14,262,16],{}," does not follow your custom domain. Attach ",[14,265,266],{},"api.yourapp.com"," to a service and the variable still returns the ",[14,269,270],{},"up.railway.app"," address. If you send that value out in an email link, a webhook registration, or an OAuth redirect URI, users see the Railway domain and OAuth providers reject the mismatch. Keep a separate ",[14,273,274],{},"APP_URL"," variable for the canonical address and reference that instead.",[277,278,280],"h3",{"id":279},"railway_private_domain-and-when-to-prefer-it","RAILWAY_PRIVATE_DOMAIN, and when to prefer it",[10,282,283,284,287,288,290],{},"Railway also gives each service a private DNS name in the form ",[14,285,286],{},"service-name.railway.internal",", exposed as ",[14,289,56],{},". Traffic between services over that network stays inside Railway and is Wireguard encrypted.",[10,292,293],{},"For a backend calling a worker, or an app talking to its own internal API, reference the private domain. There's no reason to route an internal call out to the public internet and back, and doing so puts an endpoint on a public hostname that you then have to authenticate against the whole world instead of against one network.",[83,295,297],{"label":296},"Internal service call",[87,298,300],{"className":89,"code":299,"language":91,"meta":92,"style":92},"# Public: goes out to the internet and back in\nWORKER_URL=https://${{ worker.RAILWAY_PUBLIC_DOMAIN }}\n\n# Private: stays inside Railway\nWORKER_URL=http://${{ worker.RAILWAY_PRIVATE_DOMAIN }}:8080\n",[14,301,302,307,319,323,328],{"__ignoreMap":92},[96,303,304],{"class":98,"line":99},[96,305,306],{"class":102},"# Public: goes out to the internet and back in\n",[96,308,309,312,314,316],{"class":98,"line":106},[96,310,311],{"class":109},"WORKER_URL",[96,313,114],{"class":113},[96,315,164],{"class":163},[96,317,318],{"class":109},"${{ worker.RAILWAY_PUBLIC_DOMAIN }}\n",[96,320,321],{"class":98,"line":120},[96,322,124],{"emptyLinePlaceholder":123},[96,324,325],{"class":98,"line":127},[96,326,327],{"class":102},"# Private: stays inside Railway\n",[96,329,330,332,334,337],{"class":98,"line":133},[96,331,311],{"class":109},[96,333,114],{"class":113},[96,335,336],{"class":163},"http://",[96,338,339],{"class":109},"${{ worker.RAILWAY_PRIVATE_DOMAIN }}:8080\n",[72,341,343],{"id":342},"the-git-variables","The git variables",[10,345,346],{},"When a deployment comes from a connected GitHub repo, Railway injects the commit metadata:",[348,349,350,363],"table",{},[351,352,353],"thead",{},[354,355,356,360],"tr",{},[357,358,359],"th",{},"Variable",[357,361,362],{},"Contains",[364,365,366,376,386,396],"tbody",{},[354,367,368,373],{},[369,370,371],"td",{},[14,372,20],{},[369,374,375],{},"Full commit hash that triggered the deploy",[354,377,378,383],{},[369,379,380],{},[14,381,382],{},"RAILWAY_GIT_BRANCH",[369,384,385],{},"Branch the deploy came from",[354,387,388,393],{},[369,389,390],{},[14,391,392],{},"RAILWAY_GIT_AUTHOR",[369,394,395],{},"Commit author",[354,397,398,403],{},[369,399,400],{},[14,401,402],{},"RAILWAY_GIT_COMMIT_MESSAGE",[369,404,405],{},"Commit message text",[10,407,408,410],{},[14,409,20],{}," earns its keep in error tracking. Pass it as your release identifier and every stack trace maps to exact source, which turns \"this broke sometime last week\" into a diff.",[83,412,414],{"label":413},"Sentry release stamping",[87,415,419],{"className":416,"code":417,"language":418,"meta":92,"style":92},"language-javascript shiki shiki-themes github-dark","Sentry.init({\n  dsn: process.env.SENTRY_DSN,\n  release: process.env.RAILWAY_GIT_COMMIT_SHA,\n  environment: process.env.RAILWAY_ENVIRONMENT_NAME,\n});\n","javascript",[14,420,421,433,445,454,464],{"__ignoreMap":92},[96,422,423,426,430],{"class":98,"line":99},[96,424,425],{"class":109},"Sentry.",[96,427,429],{"class":428},"svObZ","init",[96,431,432],{"class":109},"({\n",[96,434,435,438,442],{"class":98,"line":106},[96,436,437],{"class":109},"  dsn: process.env.",[96,439,441],{"class":440},"sDLfK","SENTRY_DSN",[96,443,444],{"class":109},",\n",[96,446,447,450,452],{"class":98,"line":120},[96,448,449],{"class":109},"  release: process.env.",[96,451,20],{"class":440},[96,453,444],{"class":109},[96,455,456,459,462],{"class":98,"line":127},[96,457,458],{"class":109},"  environment: process.env.",[96,460,461],{"class":440},"RAILWAY_ENVIRONMENT_NAME",[96,463,444],{"class":109},[96,465,466],{"class":98,"line":133},[96,467,468],{"class":109},"});\n",[10,470,471,472,53,475,53,477,57,480,483],{},"Alongside those, ",[14,473,474],{},"RAILWAY_PROJECT_NAME",[14,476,461],{},[14,478,479],{},"RAILWAY_SERVICE_NAME",[14,481,482],{},"RAILWAY_DEPLOYMENT_ID"," are available in every deployment. The environment name is the useful one for conditional behavior, since it tells a PR environment apart from production without you maintaining a flag.",[485,486,487],"danger-box",{},[10,488,489,490,66,492,494],{},"Don't put ",[14,491,402],{},[14,493,392],{}," on a public-facing page or a client-side error widget. Commit messages routinely name internal ticket IDs, unreleased features, and occasionally the vulnerability you just patched. Author strings are real email addresses.",[72,496,498],{"id":497},"the-build-time-leak","The build-time leak",[10,500,501],{},"Here's the failure this page exists for.",[10,503,504,505,507,508,510,511,200],{},"Railway provides variables in two phases: to the build process for each deployment, and to the running deployment. Both. Frontend bundlers treat a specific prefix as \"safe to publish\" and perform a literal text substitution at build time. Vite uses ",[14,506,31],{},", Next.js uses ",[14,509,65],{},", SvelteKit uses ",[14,512,69],{},[10,514,515],{},"Put those two facts together and a reference variable resolves during the build, then gets written into your JavaScript as a string constant:",[83,517,519,539],{"label":518},"What ships to the browser",[87,520,522],{"className":89,"code":521,"language":91,"meta":92,"style":92},"# You wrote this in the Railway dashboard\nVITE_DATABASE_URL=${{ Postgres.DATABASE_URL }}\n",[14,523,524,529],{"__ignoreMap":92},[96,525,526],{"class":98,"line":99},[96,527,528],{"class":102},"# You wrote this in the Railway dashboard\n",[96,530,531,534,536],{"class":98,"line":106},[96,532,533],{"class":109},"VITE_DATABASE_URL",[96,535,114],{"class":113},[96,537,538],{"class":109},"${{ Postgres.DATABASE_URL }}\n",[87,540,542],{"className":416,"code":541,"language":418,"meta":92,"style":92},"// What lands in dist/assets/index-a3f9c2.js\nconst l = \"postgresql://postgres:hT7nQ2xW@containers-us-west-42.railway.app:6543/railway\";\n",[14,543,544,549],{"__ignoreMap":92},[96,545,546],{"class":98,"line":99},[96,547,548],{"class":102},"// What lands in dist/assets/index-a3f9c2.js\n",[96,550,551,554,557,560,563],{"class":98,"line":106},[96,552,553],{"class":113},"const",[96,555,556],{"class":440}," l",[96,558,559],{"class":113}," =",[96,561,562],{"class":163}," \"postgresql://postgres:hT7nQ2xW@containers-us-west-42.railway.app:6543/railway\"",[96,564,565],{"class":109},";\n",[10,567,568,569,571,572,575],{},"The reference syntax makes this easier to do by accident than a plain paste would. Pasting a database password into a field named ",[14,570,533],{}," at least looks alarming while you're doing it. ",[14,573,574],{},"${{ Postgres.DATABASE_URL }}"," looks like an abstraction, like a pointer that stays a pointer. It doesn't.",[485,577,578],{},[10,579,580,581,583],{},"Sealed variables do not protect you here. Sealing means the value is never visible in the Railway UI and can't be retrieved through the API, and sealing is permanent. But the docs are explicit that a sealed value \"is provided to builds and deployments\". The build is where the inlining happens. A sealed secret referenced behind a ",[14,582,31],{}," prefix is a secret nobody on your team can read and every visitor can.",[10,585,586],{},"We see the downstream version of this constantly in scans: a live app serving a bundle with a working Postgres URL or a Stripe secret key in it, and an owner who is certain the value was \"in environment variables, not in the code\". It was in environment variables. Then it was compiled.",[277,588,590],{"id":589},"checking-your-own-bundle","Checking your own bundle",[10,592,593],{},"The check takes a minute and doesn't need any tooling:",[595,596,598,601],"step",{"number":597},"1",[10,599,600],{},"Build locally the way Railway does, then grep the output for something private you know exists, like your database password or the first several characters of a Stripe key.",[87,602,604],{"className":89,"code":603,"language":91,"meta":92,"style":92},"npm run build\ngrep -ri \"postgresql://\" dist/ .next/ build/ 2>/dev/null\n",[14,605,606,617],{"__ignoreMap":92},[96,607,608,611,614],{"class":98,"line":99},[96,609,610],{"class":428},"npm",[96,612,613],{"class":163}," run",[96,615,616],{"class":163}," build\n",[96,618,619,622,625,628,631,634,637,640],{"class":98,"line":106},[96,620,621],{"class":428},"grep",[96,623,624],{"class":440}," -ri",[96,626,627],{"class":163}," \"postgresql://\"",[96,629,630],{"class":163}," dist/",[96,632,633],{"class":163}," .next/",[96,635,636],{"class":163}," build/",[96,638,639],{"class":113}," 2>",[96,641,642],{"class":163},"/dev/null\n",[595,644,646],{"number":645},"2",[10,647,648],{},"If the app is already deployed, open it in a browser, view source, and search the loaded JS chunks. Whatever a browser can read, a stranger can read. There is no authentication step in front of a bundle.",[595,650,652],{"number":651},"3",[10,653,654],{},"For each hit, delete the public-prefixed variable and move the call server side. A browser should be talking to your API, not to Postgres.",[656,657,658,663,667,671,675],"checklist-section",{},[659,660],"checklist-item",{"description":661,"label":662},"List each VITE_, NEXT_PUBLIC_ or PUBLIC_ variable in Railway and confirm the resolved value is genuinely publishable.","Audit every public-prefixed variable",[659,664],{"description":665,"label":666},"Service-to-service traffic should reference RAILWAY_PRIVATE_DOMAIN, not the public one.","Prefer the private domain for internal calls",[659,668],{"description":669,"label":670},"RAILWAY_PUBLIC_DOMAIN ignores custom domains, which breaks OAuth redirects and email links.","Keep a separate APP_URL for custom domains",[659,672],{"description":673,"label":674},"RAILWAY_GIT_COMMIT_SHA as your error tracker's release ID turns vague reports into diffs.","Stamp releases with the commit SHA",[659,676],{"description":677,"label":678},"A secret compiled into shipped JavaScript is public from the moment it deployed. Rotate it, do not just remove it.","Rotate anything already in a bundle",[72,680,682],{"id":681},"faq","FAQ",[684,685,686,703,723,736,749],"faq-section",{},[687,688,690],"faq-item",{"question":689},"What is the RAILWAY_PUBLIC_DOMAIN environment variable?",[10,691,692,693,695,696,699,700,702],{},"It's a variable Railway injects into every build and deployment automatically, holding the service's public domain such as ",[14,694,199],{},". There's no scheme in it, so write ",[14,697,698],{},"https://${{ RAILWAY_PUBLIC_DOMAIN }}"," for a full URL. You can't set or override it. It also does not track custom domains, so keep your own ",[14,701,274],{}," variable for the canonical address.",[687,704,706],{"question":705},"How do I reference another service's variable in Railway?",[10,707,708,709,712,713,716,717,720,721,200],{},"Use ",[14,710,711],{},"${{ SERVICE_NAME.VARIABLE_NAME }}"," in the value field. Railway's documented example is ",[14,714,715],{},"DATABASE_URL=${{ Clickhouse.DATABASE_URL }}",". The service name must match the name on your project canvas exactly, including case. For project-level shared variables the namespace is the literal word ",[14,718,719],{},"shared",", as in ",[14,722,187],{},[687,724,726],{"question":725},"What is RAILWAY_GIT_COMMIT_SHA used for?",[10,727,728,729,53,731,57,733,735],{},"It's the full commit hash that triggered the deployment, injected when the service deploys from a connected GitHub repo. The standard use is passing it as the release identifier to an error tracker so stack traces resolve against exact source. Railway injects ",[14,730,382],{},[14,732,392],{},[14,734,402],{}," alongside it.",[687,737,739],{"question":738},"Do Railway reference variables resolve at build time?",[10,740,741,742,53,744,66,746,748],{},"Yes. Railway supplies variables to the build process and to the running deployment. Anything with a ",[14,743,31],{},[14,745,65],{},[14,747,69],{}," prefix gets resolved and written into your JavaScript bundle as a literal string. Sealing the source variable doesn't help, because sealed values are still provided to builds.",[687,750,752],{"question":751},"Should I use RAILWAY_PRIVATE_DOMAIN or RAILWAY_PUBLIC_DOMAIN?",[10,753,754,755,757,758,760],{},"Private for anything inside your project, public for anything a browser or third party has to reach. ",[14,756,56],{}," resolves to ",[14,759,286],{}," and inter-service traffic on it is Wireguard encrypted, so a backend calling a worker never touches the public internet.",[762,763,764,770,775],"related-articles",{},[765,766],"related-card",{"description":767,"href":768,"title":769},"The dashboard walkthrough: adding variables, shared vars, sealed secrets, PR environments, and the CLI.","/blog/how-to/railway-env-vars","Railway Environment Variables: Complete Setup Guide",[765,771],{"description":772,"href":773,"title":774},"Why linking is manual, what a shared variable puts in every build, and the four places sealed variables do not follow.","/blog/how-to/railway-shared-variables","Railway Shared Variables Explained",[765,776],{"description":777,"href":778,"title":779},"Find the leak, rotate the key, scrub git history, and move the secret server side.","/blog/how-to/fix-railway-api-key-exposure","Fix Railway API Key Exposure",[781,782,785,789],"cta-box",{"href":783,"label":784},"/","Start Free Scan",[72,786,788],{"id":787},"not-sure-what-your-bundle-is-shipping","Not sure what your bundle is shipping?",[10,790,791],{},"CheckYourVibe scans your deployed app for secrets compiled into client-side JavaScript, exposed endpoints, and missing security headers.",[793,794,795],"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 pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}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);}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}",{"title":92,"searchDepth":106,"depth":106,"links":797},[798,799,802,803,806,807],{"id":74,"depth":106,"text":75},{"id":191,"depth":106,"text":16,"children":800},[801],{"id":279,"depth":120,"text":280},{"id":342,"depth":106,"text":343},{"id":497,"depth":106,"text":498,"children":804},[805],{"id":589,"depth":120,"text":590},{"id":681,"depth":106,"text":682},{"id":787,"depth":106,"text":788},"how-to","2026-08-13","How Railway reference variables work, what RAILWAY_PUBLIC_DOMAIN and RAILWAY_GIT_COMMIT_SHA contain, and the build-time gotcha that leaks them into your client bundle.",false,"md",[814,816,818,821,824],{"question":689,"answer":815},"RAILWAY_PUBLIC_DOMAIN is a variable Railway injects into every build and deployment automatically. It holds the service's public domain, like example.up.railway.app, with no scheme attached, so you write https://${{ RAILWAY_PUBLIC_DOMAIN }} when you need a full URL. You cannot set or override it yourself. If you have attached a custom domain, RAILWAY_PUBLIC_DOMAIN still returns the railway.app domain, so create your own APP_URL variable for the canonical address.",{"question":705,"answer":817},"Use ${{ SERVICE_NAME.VARIABLE_NAME }} in the value field. Railway's own example is DATABASE_URL=${{ Clickhouse.DATABASE_URL }}. The service name is the name shown in your project canvas, and it is case sensitive. For project-level shared variables the namespace is the literal word shared, as in ${{ shared.STRIPE_SECRET_KEY }}.",{"question":819,"answer":820},"What is RAILWAY_GIT_COMMIT_SHA?","RAILWAY_GIT_COMMIT_SHA is the full commit hash that triggered the deployment, injected automatically when the service deploys from a connected GitHub repo. Railway provides RAILWAY_GIT_BRANCH, RAILWAY_GIT_AUTHOR and RAILWAY_GIT_COMMIT_MESSAGE alongside it. The common use is stamping a release version into error reports so a stack trace maps back to exact source.",{"question":822,"answer":823},"Do Railway reference variables work at build time?","Yes, and that is the part that catches people out. Railway supplies variables to both the build process and the running deployment. Any bundler that inlines env vars at build time, which means anything with a VITE_, NEXT_PUBLIC_ or PUBLIC_ prefix, will resolve the reference and write the literal value into your JavaScript. Sealing the source variable does not prevent this, because sealed values are still provided to builds.",{"question":825,"answer":826},"What is the difference between RAILWAY_PRIVATE_DOMAIN and RAILWAY_PUBLIC_DOMAIN?","RAILWAY_PUBLIC_DOMAIN is the internet-facing domain of the service. RAILWAY_PRIVATE_DOMAIN is the internal DNS name on Railway's private network, in the form service-name.railway.internal, and all inter-service traffic on it is Wireguard encrypted. For backend-to-backend calls inside one project, reference the private domain so the request never leaves Railway's network.","yellow",null,"railway_public_domain environment variable, railway reference variables, railway_git_commit_sha, railway private domain, railway shared variables, railway built-in variables",{},"The reference syntax, every built-in RAILWAY_ variable worth knowing, and why a reference behind a VITE_ prefix ends up in your JavaScript bundle.","/blog/how-to/railway-reference-variables","8 min read","[object Object]","HowTo",{"title":5,"description":810},{"loc":832},"blog/how-to/railway-reference-variables",[],"summary_large_image","7u3BjjWXUDQJ57XkTzDOizyef4ASJVsJlR64v1WO-Nc",1787602618912]