[{"data":1,"prerenderedAt":701},["ShallowReactive",2],{"blog-how-to/railway-secrets":3},{"id":4,"title":5,"body":6,"category":666,"date":667,"dateModified":667,"description":668,"draft":669,"extension":670,"faq":671,"featured":669,"headerVariant":685,"image":686,"keywords":687,"meta":688,"navigation":689,"ogDescription":690,"ogTitle":686,"path":691,"readTime":692,"schemaOrg":693,"schemaType":694,"seo":695,"sitemap":696,"stem":697,"tags":698,"twitterCard":699,"__hash__":700},"blog/blog/how-to/railway-secrets.md","Railway Secrets: How Sealed Variables Work and What They Miss (2026)",{"type":7,"value":8,"toc":651},"minimark",[9,13,16,27,32,35,102,105,109,120,126,136,142,145,155,159,162,167,175,186,192,196,199,305,308,312,318,337,348,351,362,464,467,471,478,482,485,509,516,522,526,529,573,576,616,635,647],[10,11,12],"p",{},"Sealing a Railway variable hides it from you. That is the honest one-line description, and it is more useful than the phrase \"secret management,\" because it tells you what the feature is for and where it stops.",[10,14,15],{},"Railway's own docs are precise about the boundary: a sealed variable's value \"is provided to builds and deployments but is never visible in the UI nor can it be retrieved via the API.\" The value is still there. It still runs. You just cannot read it back.",[17,18,19],"tldr",{},[10,20,21,22,26],{},"Railway does not ship a separate secrets manager. Secrets are environment variables you mark ",[23,24,25],"strong",{},"Sealed",", which removes them from the dashboard, the API, and the CLI while still injecting them into builds and deployments. Sealing is one-way and cannot be undone. It closes the \"someone screenshots the dashboard\" path and closes nothing else, so the five gaps in the second half of this page are where your actual exposure lives.",[28,29,31],"h2",{"id":30},"the-three-kinds-of-variable","The Three Kinds of Variable",[10,33,34],{},"Railway gives you three places to put a value, and picking the wrong one is the most common mistake we see in Railway projects.",[36,37,38,57],"table",{},[39,40,41],"thead",{},[42,43,44,48,51,54],"tr",{},[45,46,47],"th",{},"Kind",[45,49,50],{},"Scope",[45,52,53],{},"Readable later",[45,55,56],{},"Use it for",[58,59,60,75,88],"tbody",{},[42,61,62,66,69,72],{},[63,64,65],"td",{},"Service variable",[63,67,68],{},"One service",[63,70,71],{},"Yes",[63,73,74],{},"Config, feature flags, non-secret URLs",[42,76,77,80,83,85],{},[63,78,79],{},"Shared variable",[63,81,82],{},"Project, linked per service",[63,84,71],{},[63,86,87],{},"Values several services genuinely need",[42,89,90,93,96,99],{},[63,91,92],{},"Sealed variable",[63,94,95],{},"Wherever you sealed it",[63,97,98],{},"No, ever",[63,100,101],{},"Live credentials with money or data behind them",[10,103,104],{},"Sealed is not a fourth storage system. It is a flag on a variable that happens to remove every read path.",[28,106,108],{"id":107},"how-to-seal-a-variable","How to Seal a Variable",[110,111,113],"step",{"number":112},"1",[10,114,115,116,119],{},"Open the service, then the ",[23,117,118],{},"Variables"," tab.",[110,121,123],{"number":122},"2",[10,124,125],{},"Click the three-dot menu on the right side of the variable you want to protect.",[110,127,129],{"number":128},"3",[10,130,131,132,135],{},"Choose ",[23,133,134],{},"Seal",". Railway confirms, and the value disappears from the row.",[110,137,139],{"number":138},"4",[10,140,141],{},"Store the plaintext somewhere you control first, or accept that a rotation is your only way to recover it.",[10,143,144],{},"That last step is not optional advice. Sealing is permanent.",[146,147,148],"warning-box",{},[10,149,150,151,154],{},"Railway's docs state that sealed variables ",[23,152,153],{},"cannot be un-sealed"," and cannot be updated through the Raw Editor. Seal a typo and there is no read-back to catch it. You rotate the credential upstream and add a new variable.",[28,156,158],{"id":157},"what-sealing-does-not-protect-you-from","What Sealing Does Not Protect You From",[10,160,161],{},"This is the part the docs-lookup queries never ask for and everybody needs. Sealing removes a human's read path. Your secret still has to be used, and every use is a chance to escape.",[163,164,166],"h3",{"id":165},"_1-your-build-runs-with-the-secret-in-its-environment","1. Your build runs with the secret in its environment",[10,168,169,170,174],{},"Railway injects variables into \"the build process for each service deployment,\" and sealed variables are explicitly included in that injection. So while your build container is up, the value is a plain string in ",[171,172,173],"code",{},"process.env",".",[10,176,177,178,181,182,185],{},"Now consider what a Node build actually does during that window. ",[171,179,180],{},"npm install"," executes ",[171,183,184],{},"postinstall"," scripts from your dependency tree, transitively, as ordinary code with ordinary environment access. A single compromised package deep in the graph reads your Stripe live key and posts it somewhere, and your dashboard still shows a tidy row of dots.",[187,188,189],"danger-box",{},[10,190,191],{},"If a secret does not need to exist during the build, do not let it. Split build-time config from runtime credentials and keep the credentials on the runtime side, where a dependency's install script never sees them.",[163,193,195],{"id":194},"_2-your-own-code-prints-it","2. Your own code prints it",[10,197,198],{},"The single most common way we find a live credential is not an attacker. It is a log line.",[200,201,206],"pre",{"className":202,"code":203,"language":204,"meta":205,"style":205},"language-js shiki shiki-themes github-dark","// All three of these put a sealed value into plaintext logs.\nconsole.log(\"config:\", process.env)\nlogger.debug(`connecting with ${process.env.DATABASE_URL}`)\nthrow new Error(`Stripe auth failed for key ${process.env.STRIPE_SECRET_KEY}`)\n","js","",[171,207,208,217,238,272],{"__ignoreMap":205},[209,210,213],"span",{"class":211,"line":212},"line",1,[209,214,216],{"class":215},"sAwPA","// All three of these put a sealed value into plaintext logs.\n",[209,218,220,224,228,231,235],{"class":211,"line":219},2,[209,221,223],{"class":222},"s95oV","console.",[209,225,227],{"class":226},"svObZ","log",[209,229,230],{"class":222},"(",[209,232,234],{"class":233},"sU2Wk","\"config:\"",[209,236,237],{"class":222},", process.env)\n",[209,239,241,244,247,249,252,255,257,260,262,266,269],{"class":211,"line":240},3,[209,242,243],{"class":222},"logger.",[209,245,246],{"class":226},"debug",[209,248,230],{"class":222},[209,250,251],{"class":233},"`connecting with ${",[209,253,254],{"class":222},"process",[209,256,174],{"class":233},[209,258,259],{"class":222},"env",[209,261,174],{"class":233},[209,263,265],{"class":264},"sDLfK","DATABASE_URL",[209,267,268],{"class":233},"}`",[209,270,271],{"class":222},")\n",[209,273,275,279,282,285,287,290,292,294,296,298,301,303],{"class":211,"line":274},4,[209,276,278],{"class":277},"snl16","throw",[209,280,281],{"class":277}," new",[209,283,284],{"class":226}," Error",[209,286,230],{"class":222},[209,288,289],{"class":233},"`Stripe auth failed for key ${",[209,291,254],{"class":222},[209,293,174],{"class":233},[209,295,259],{"class":222},[209,297,174],{"class":233},[209,299,300],{"class":264},"STRIPE_SECRET_KEY",[209,302,268],{"class":233},[209,304,271],{"class":222},[10,306,307],{},"Railway's log viewer keeps those. So does whatever you forward them to.",[163,309,311],{"id":310},"_3-your-error-tracker-captures-the-environment","3. Your error tracker captures the environment",[10,313,314,315,317],{},"Sentry, Rollbar, and most APM agents can attach environment context to an event. That is useful right up until the event ships your whole ",[171,316,173],{}," to a third party, at which point your secret now lives in two products instead of one, under someone else's retention policy.",[10,319,320,321,324,325,328,329,332,333,336],{},"Check what your tracker's default scrub list covers. ",[171,322,323],{},"PASSWORD"," and ",[171,326,327],{},"SECRET"," are usually caught. ",[171,330,331],{},"RAILWAY_TOKEN"," and your custom ",[171,334,335],{},"PARTNER_KEY"," usually are not.",[163,338,340,341,324,344,347],{"id":339},"_4-next_public_-and-vite_-ignore-sealing-entirely","4. ",[171,342,343],{},"NEXT_PUBLIC_",[171,345,346],{},"VITE_"," ignore sealing entirely",[10,349,350],{},"This is the one that hurts, because the dashboard actively reassures you.",[10,352,353,354,357,358,361],{},"Those prefixes instruct Next.js and Vite to inline the value into the client bundle at build time. Sealing controls what Railway shows you in a web page. It has no opinion about what your bundler writes into ",[171,355,356],{},"main-a3f9c1.js",". Seal a ",[171,359,360],{},"NEXT_PUBLIC_STRIPE_SECRET_KEY"," and you have a secret that is invisible to you and public to everyone else.",[363,364,366],"code-block",{"label":365},"Check your own deployed bundle",[200,367,371],{"className":368,"code":369,"language":370,"meta":205,"style":205},"language-bash shiki shiki-themes github-dark","# Pull every JS asset the live site loads, then look for key-shaped strings.\ncurl -s https://your-app.up.railway.app | grep -oE '/(_next/static|assets)/[^\"]+\\.js' | \\\n  while read f; do curl -s \"https://your-app.up.railway.app$f\"; done | \\\n  grep -oE '(sk_live_|sk_test_|rk_live_|AKIA|ghp_|xoxb-)[A-Za-z0-9_-]{10,}' | sort -u\n","bash",[171,372,373,378,406,446],{"__ignoreMap":205},[209,374,375],{"class":211,"line":212},[209,376,377],{"class":215},"# Pull every JS asset the live site loads, then look for key-shaped strings.\n",[209,379,380,383,386,389,392,395,398,401,403],{"class":211,"line":219},[209,381,382],{"class":226},"curl",[209,384,385],{"class":264}," -s",[209,387,388],{"class":233}," https://your-app.up.railway.app",[209,390,391],{"class":277}," |",[209,393,394],{"class":226}," grep",[209,396,397],{"class":264}," -oE",[209,399,400],{"class":233}," '/(_next/static|assets)/[^\"]+\\.js'",[209,402,391],{"class":277},[209,404,405],{"class":264}," \\\n",[209,407,408,411,414,417,420,423,426,428,431,434,437,439,442,444],{"class":211,"line":240},[209,409,410],{"class":277},"  while",[209,412,413],{"class":233}," read",[209,415,416],{"class":233}," f",[209,418,419],{"class":222},"; ",[209,421,422],{"class":277},"do",[209,424,425],{"class":226}," curl",[209,427,385],{"class":264},[209,429,430],{"class":233}," \"https://your-app.up.railway.app",[209,432,433],{"class":222},"$f",[209,435,436],{"class":233},"\"",[209,438,419],{"class":222},[209,440,441],{"class":277},"done",[209,443,391],{"class":277},[209,445,405],{"class":264},[209,447,448,451,453,456,458,461],{"class":211,"line":274},[209,449,450],{"class":226},"  grep",[209,452,397],{"class":264},[209,454,455],{"class":233}," '(sk_live_|sk_test_|rk_live_|AKIA|ghp_|xoxb-)[A-Za-z0-9_-]{10,}'",[209,457,391],{"class":277},[209,459,460],{"class":226}," sort",[209,462,463],{"class":264}," -u\n",[10,465,466],{},"If that prints anything, the variable is already public. Rotate before you read further.",[163,468,470],{"id":469},"_5-the-value-existed-somewhere-before-railway","5. The value existed somewhere before Railway",[10,472,473,474,477],{},"Sealing today says nothing about the ",[171,475,476],{},".env"," file you committed in March. Git keeps it, GitHub keeps forks and cached views of it, and a rotated-then-sealed key is only safe if you actually rotated.",[28,479,481],{"id":480},"which-values-should-be-sealed","Which Values Should Be Sealed",[10,483,484],{},"Seal anything where the blast radius is money, customer data, or the ability to impersonate you.",[486,487,488,493,497,501,505],"checklist-section",{},[489,490],"checklist-item",{"description":491,"label":492},"Full read/write on everything your app stores.","Database passwords and connection strings",[489,494],{"description":495,"label":496},"Stripe, Paddle, Lemon Squeezy. Direct financial exposure.","Payment provider live keys",[489,498],{"description":499,"label":500},"Lets an attacker complete a login flow as your application.","OAuth client secrets",[489,502],{"description":503,"label":504},"OpenAI, Anthropic, Twilio, Resend. Someone else's bill becomes yours.","Third-party keys with billing attached",[489,506],{"description":507,"label":508},"JWT signing keys, cookie secrets, webhook signing secrets.","Signing and session secrets",[10,510,511,512,515],{},"Leave the rest readable. A sealed ",[171,513,514],{},"LOG_LEVEL"," buys you nothing and costs you a rotation the next time someone needs to know what it is set to.",[517,518,519],"tip-box",{},[10,520,521],{},"Seal in one direction: staging first, production last. If your sealing workflow is wrong, you would rather discover it on the environment nobody is paying you for.",[28,523,525],{"id":524},"a-five-minute-audit","A Five-Minute Audit",[10,527,528],{},"Run this before your next deploy.",[530,531,532,552,555,561,564],"ol",{},[533,534,535,536,539,540,539,542,539,545,547,548,551],"li",{},"List every variable on every service. Anything with ",[171,537,538],{},"KEY",", ",[171,541,327],{},[171,543,544],{},"TOKEN",[171,546,323],{},", or ",[171,549,550],{},"DSN"," in the name that is not sealed is your working list.",[533,553,554],{},"For each one, ask whether the build needs it. If not, confirm nothing in your build step references it.",[533,556,557,558,560],{},"Grep your source for ",[171,559,173],{}," inside log, error, and debug calls.",[533,562,563],{},"Open your deployed site and run the bundle check above.",[533,565,566,567,569,570,572],{},"Check that every ",[171,568,343],{}," or ",[171,571,346],{}," variable is a value you would put on a billboard.",[10,574,575],{},"Step 5 is where most Railway projects fail. It is also the fastest to fix, because the answer is always the same: move the call server-side and stop shipping the credential to the browser.",[577,578,579,589,598,604,610],"faq-section",{},[580,581,583],"faq-item",{"question":582},"Does Railway have a secrets manager?",[10,584,585,586,588],{},"Not as a separate product. Secrets in Railway are environment variables that you mark as ",[23,587,25],{},". The value is still provided to builds and deployments, but Railway's docs state it is never visible in the UI nor retrievable via the API, and the CLI will not print it either.",[580,590,592],{"question":591},"How do I seal a variable in Railway?",[10,593,594,595,597],{},"Open the service's Variables tab, click the three-dot menu on the right side of the variable, and choose ",[23,596,134],{},". Railway keeps injecting the value where it is needed and stops showing it to you.",[580,599,601],{"question":600},"Can you un-seal a Railway variable?",[10,602,603],{},"No. Railway's docs say sealed variables cannot be un-sealed, and a sealed variable cannot be edited through the Raw Editor. If you need the plaintext back, rotate the credential at its source and add a fresh variable. Store the value somewhere you control before you seal it.",[580,605,607],{"question":606},"Is a sealed NEXT_PUBLIC_ or VITE_ variable safe?",[10,608,609],{},"No, and it is the worst case on this page. Those prefixes tell Next.js and Vite to inline the value into the client bundle at build time. Sealing changes what your dashboard shows, not what ships to the browser, so the value is hidden from you and readable by anyone with view-source.",[580,611,613],{"question":612},"Do sealed variables work in Railway PR environments?",[10,614,615],{},"No. Railway excludes sealed variables from PR environments, environment duplication, and service duplication. The variable name is absent rather than empty, so the service usually fails at startup instead of quietly running with the wrong value.",[617,618,619,625,630],"related-articles",{},[620,621],"related-card",{"description":622,"href":623,"title":624},"The full variables workflow, from the dashboard UI to the CLI and reference syntax.","/blog/how-to/railway-env-vars","Railway Environment Variables: Complete Setup Guide",[620,626],{"description":627,"href":628,"title":629},"Why linking is manual, what a shared variable puts in every build, and where sealed values stop following.","/blog/how-to/railway-shared-variables","Railway Shared Variables Explained",[620,631],{"description":632,"href":633,"title":634},"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",[636,637,640,644],"cta-box",{"href":638,"label":639},"/","Start Free Scan",[28,641,643],{"id":642},"see-what-your-deployed-app-exposes","See What Your Deployed App Exposes",[10,645,646],{},"We fetch your live JavaScript bundles and flag credentials that made it to the browser, sealed or not.",[648,649,650],"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 .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}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":205,"searchDepth":219,"depth":219,"links":652},[653,654,655,663,664,665],{"id":30,"depth":219,"text":31},{"id":107,"depth":219,"text":108},{"id":157,"depth":219,"text":158,"children":656},[657,658,659,660,662],{"id":165,"depth":240,"text":166},{"id":194,"depth":240,"text":195},{"id":310,"depth":240,"text":311},{"id":339,"depth":240,"text":661},"4. NEXT_PUBLIC_ and VITE_ ignore sealing entirely",{"id":469,"depth":240,"text":470},{"id":480,"depth":219,"text":481},{"id":524,"depth":219,"text":525},{"id":642,"depth":219,"text":643},"how-to","2026-08-24","Railway stores secrets as sealed environment variables. Here is how to seal one, exactly what sealing hides, and the five ways a sealed secret still leaves your project.",false,"md",[672,674,676,678,681,683],{"question":582,"answer":673},"Not as a separate product. Secrets in Railway are environment variables that you mark as Sealed. A sealed variable's value is still provided to builds and deployments, but Railway's docs state it is never visible in the UI nor can it be retrieved via the API, and the CLI will not print it either.",{"question":591,"answer":675},"Open the service's Variables tab, click the three-dot menu on the right side of the variable, and choose Seal. Railway keeps injecting the value into builds and deployments, and stops showing it to you.",{"question":600,"answer":677},"No. Railway's docs say plainly that sealed variables cannot be un-sealed, and a sealed variable cannot be edited through the Raw Editor. If you need the plaintext back, rotate the credential at its source and add a fresh variable.",{"question":679,"answer":680},"Does sealing a variable stop it from leaking?","It stops one leak path: a person reading it out of the dashboard, the API, or the CLI. It does nothing about build scripts that run with the value in their environment, a console.log that prints it, an error tracker that captures process.env, or a NEXT_PUBLIC_ variable that gets inlined into your JavaScript bundle.",{"question":606,"answer":682},"No, and this is the worst combination on the list. Those prefixes tell the framework to inline the value into the client bundle at build time. Sealing changes what your dashboard shows you, not what ships to the browser. Anyone can read it with view-source.",{"question":612,"answer":684},"No. Railway excludes sealed variables from PR environments, environment duplication, and service duplication. The variable name is simply absent, so the service usually fails at startup rather than silently using a stale value.","yellow",null,"railway secrets, railway environment variables secrets, railway sealed variables, railway secrets documentation, railway secret management, railway seal variable",{},true,"Sealing a Railway variable hides it from the dashboard and the API. It does not stop build scripts, log lines, or client bundles from carrying the value out.","/blog/how-to/railway-secrets","9 min read","[object Object]","HowTo",{"title":5,"description":668},{"loc":691},"blog/how-to/railway-secrets",[],"summary_large_image","zpQ-bLIgolOMXRwSTbhLGp-nip4il306NIOD4SjEIow",1787602606790]