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.
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.
TL;DR
Railway does not ship a separate secrets manager. Secrets are environment variables you mark 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.
The Three Kinds of Variable
Railway gives you three places to put a value, and picking the wrong one is the most common mistake we see in Railway projects.
| Kind | Scope | Readable later | Use it for |
|---|---|---|---|
| Service variable | One service | Yes | Config, feature flags, non-secret URLs |
| Shared variable | Project, linked per service | Yes | Values several services genuinely need |
| Sealed variable | Wherever you sealed it | No, ever | Live credentials with money or data behind them |
Sealed is not a fourth storage system. It is a flag on a variable that happens to remove every read path.
How to Seal a Variable
Open the service, then the Variables tab.
Click the three-dot menu on the right side of the variable you want to protect.
Choose Seal. Railway confirms, and the value disappears from the row.
Store the plaintext somewhere you control first, or accept that a rotation is your only way to recover it.
That last step is not optional advice. Sealing is permanent.
Railway's docs state that sealed variables 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.
What Sealing Does Not Protect You From
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.
1. Your build runs with the secret in its environment
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 process.env.
Now consider what a Node build actually does during that window. npm install executes 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.
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.
2. Your own code prints it
The single most common way we find a live credential is not an attacker. It is a log line.
// All three of these put a sealed value into plaintext logs.
console.log("config:", process.env)
logger.debug(`connecting with ${process.env.DATABASE_URL}`)
throw new Error(`Stripe auth failed for key ${process.env.STRIPE_SECRET_KEY}`)
Railway's log viewer keeps those. So does whatever you forward them to.
3. Your error tracker captures the environment
Sentry, Rollbar, and most APM agents can attach environment context to an event. That is useful right up until the event ships your whole process.env to a third party, at which point your secret now lives in two products instead of one, under someone else's retention policy.
Check what your tracker's default scrub list covers. PASSWORD and SECRET are usually caught. RAILWAY_TOKEN and your custom PARTNER_KEY usually are not.
4. NEXT_PUBLIC_ and VITE_ ignore sealing entirely
This is the one that hurts, because the dashboard actively reassures you.
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 main-a3f9c1.js. Seal a NEXT_PUBLIC_STRIPE_SECRET_KEY and you have a secret that is invisible to you and public to everyone else.
# Pull every JS asset the live site loads, then look for key-shaped strings.
curl -s https://your-app.up.railway.app | grep -oE '/(_next/static|assets)/[^"]+\.js' | \
while read f; do curl -s "https://your-app.up.railway.app$f"; done | \
grep -oE '(sk_live_|sk_test_|rk_live_|AKIA|ghp_|xoxb-)[A-Za-z0-9_-]{10,}' | sort -u
If that prints anything, the variable is already public. Rotate before you read further.
5. The value existed somewhere before Railway
Sealing today says nothing about the .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.
Which Values Should Be Sealed
Seal anything where the blast radius is money, customer data, or the ability to impersonate you.
Leave the rest readable. A sealed LOG_LEVEL buys you nothing and costs you a rotation the next time someone needs to know what it is set to.
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.
A Five-Minute Audit
Run this before your next deploy.
- List every variable on every service. Anything with
KEY,SECRET,TOKEN,PASSWORD, orDSNin the name that is not sealed is your working list. - For each one, ask whether the build needs it. If not, confirm nothing in your build step references it.
- Grep your source for
process.envinside log, error, and debug calls. - Open your deployed site and run the bundle check above.
- Check that every
NEXT_PUBLIC_orVITE_variable is a value you would put on a billboard.
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.
Does Railway have a secrets manager?
Not as a separate product. Secrets in Railway are environment variables that you mark as Sealed. 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.
How do I seal a variable in Railway?
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 where it is needed and stops showing it to you.
Can you un-seal a Railway variable?
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.
Is a sealed NEXT_PUBLIC_ or VITE_ variable safe?
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.
Do sealed variables work in Railway PR environments?
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.
See What Your Deployed App Exposes
We fetch your live JavaScript bundles and flag credentials that made it to the browser, sealed or not.