Railway environment variables and shared variables
Railway gives builds and deployments service, shared and reference variables, with sealed values for secrets. Staged changes, railway run and Crunu explained.
Part of the guide
RailwayAt a glance
- Env files, highest priority first
- No .env files: values come from the process environment or the platform.
- Public prefix
- None
No names are copied into browser code by a prefix.
- Values applied
- Build and runtime
Some values are fixed at build time, the rest are read when the app runs.
- Built-in secret store
- Service and shared variables per environment, optionally sealed
- Crunu command
crunu run --env api/production -- npm startRecipeCrunu has no dedicated integration here: this is the general setup.
Common pitfalls
- Every variable change is staged. Nothing applies until you review the staged changes and deploy them.
- Dockerfile builds see a variable only if the Dockerfile declares it with ARG, in the stage that uses it.
- Sealed values never come back through railway run or railway variables, so local runs with the Railway CLI miss them.
- Sealed variables are not copied into PR environments or duplicated environments, so those start without them.
- Variables reach the build as well as the running service, so build scripts and install hooks can read them too.
On this page
Railway stores variables per environment of a project and hands them to your service as environment variables in two places: the build of each deployment and the running deployment. You define them on a service, share them across services as shared variables, or point one variable at another with reference syntax. Railway adds its own variables, such as the service's domain. Changes don't apply immediately: they're staged, and you deploy them. For secrets, a variable can be sealed, which keeps it out of the dashboard, the API and the CLI while still delivering it to builds and deployments. These rules come from the Railway variables guide, checked October 30, 2026.
Kinds of variables
| Kind | Defined in | Scope |
|---|---|---|
| Service variables | the service's Variables tab | one service in one environment |
| Shared variables | project settings, per environment | any service that adds them |
| Reference variables | a service variable whose value points elsewhere | resolved by Railway |
| Railway-provided variables | set by Railway | every build and deployment |
Reference syntax uses double braces: ${{ shared.API_KEY }} for a shared variable, ${{ Postgres.DATABASE_URL }} for a variable of another service, and ${{ OTHER_NAME }} within the same service. Adding a shared variable to a service creates such a reference, not a copy, so changing the shared value changes every service that uses it.
Railway-provided variables describe where the code runs: RAILWAY_PUBLIC_DOMAIN, RAILWAY_PRIVATE_DOMAIN, RAILWAY_ENVIRONMENT_NAME, RAILWAY_SERVICE_NAME and others. Git details like RAILWAY_GIT_COMMIT_SHA appear only when the deploy came from a GitHub trigger. The variables reference lists them all, plus a few variables you set to change platform behavior, such as RAILWAY_DOCKERFILE_PATH or RAILWAY_DEPLOYMENT_DRAINING_SECONDS, the time between SIGTERM and SIGKILL when a deployment stops.
Changes are staged
Adding, editing or deleting a variable produces staged changes that you review and then deploy. Until then, the running service keeps its old values. That's useful when you rotate several related values, such as a database password and the connection string that contains it, because they land in one deployment. It's also an easy step to forget, and the symptom is a service that still uses a key you believe you've replaced.
Builds see variables too
Railway exposes variables to the build process of every deployment, not only to the running service. That has two consequences.
With a Dockerfile, you must ask for them. The Dockerfile guide says to declare each variable with ARG, and in a multi-stage build, in the stage that needs it. An undeclared variable simply isn't there during the build.
Without a Dockerfile, every build step can read them. Build scripts, package install hooks and anything they download run with the service's variables available. If a service holds a payment provider's secret key, so does its build. Keep build-only needs in mind when you decide which variables a service gets, and declare as ARG only what the build truly needs.
Sealed variables
Sealing is Railway's answer to "nobody should be able to read this back". A sealed variable's value is provided to builds and deployments but is never visible in the UI and can't be retrieved through the API. The guide lists the edges:
- Sealing can't be undone, and sealed variables can't be edited in the raw editor.
railway variablesandrailway rundon't return sealed values.- Sealed variables aren't copied into PR environments or into a duplicated environment, and they don't show up in environment diffs.
The third point matters for preview deployments. A PR environment starts without the sealed secrets of its parent, which is safe by default and means previews need their own values.
Local development: railway run
railway run npm run dev starts your command locally with the variables of a Railway environment, and railway shell opens a shell with them. It's convenient, and it copies production-grade values onto a laptop whenever someone runs it against the production environment, unless they're sealed, in which case the command simply doesn't get them.
If your team keeps using the Railway CLI locally, give it a development environment with development credentials, and keep production values sealed. Then a laptop that runs railway run against the wrong environment gets an app that fails to connect, not a copy of production secrets. The same reasoning applies to anyone who can open the production environment in the dashboard: unsealed values there can be read by people who can open that environment's variables.
Where crunu run fits on Railway
Crunu has no built-in sync into Railway variables. It can still be the single place where a team edits and shares values. The recommended setup leaves deployments to Railway, and running crunu inside the service is a fallback.
Recommended: Crunu for development, Railway for deployments. Developers run crunu run --env api/development -- npm run dev, so development values never come from Railway at all, and production values stay sealed in Railway. This is the smallest change, and it removes the most common leak: production variables copied to laptops with railway run.
Fallback: Railway holds one token. The service's start command becomes crunu run, and Railway stores only a service token.
- Create a Crunu service token that can read only
api/production, without a network rule. - Add it to the Railway service as
CRUNU_TOKENand seal it right away, so it can't be read back from the dashboard, the API orrailway run. AddCRUNU_SERVER, your Crunu server address, as an ordinary variable. - Install
crunuduring the build with the install script's--user-installmode, and set the start command tocrunu run --env api/production --restart on-change -- npm start. - Deploy the staged change. Later value changes in Crunu restart the process inside the deployment without a new Railway deploy.
Crunu documents service tokens for CI and short-lived environments, and recommends the agent on a machine you control for long-running servers. Before you use a token this way, accept three limits:
- Railway can decrypt this environment. Railway stores the token and delivers it to every build and deployment, and the token decrypts the environment it was granted. For this environment, the promise that the server can't read your values no longer holds: the platform holding the token can.
- An expired lease stops the service from starting. The token's lease is a window of inactivity from 1 hour to 30 days, and every sign-in extends it. If the service doesn't start for longer than the lease, the token is refused at the next start,
crunu runexits before your program starts, and the service stays down until an owner or admin resumes the token. Choose a lease longer than the longest time the service may stay stopped. - One token per environment, with the smallest grant and no network rule. Give each service and environment its own token that reads only that environment. Don't bind it to networks: the addresses Railway connects from can change between deploys, and a network rule would then lock the service out.
The sealed token is still available to the build, as every variable is, so don't declare it as an ARG in a Dockerfile and don't let build scripts use it. PR environments won't inherit the sealed token, which is a good moment to give previews their own token for a staging environment. crunu run never passes CRUNU_TOKEN on to your program, and a process that is already running keeps the values it has until it restarts.
Related reading
- Render and Fly.io for other platform stores.
- Node.js for the runtime side of
npm start. - Service tokens and any CI with a service token.
- How to share .env files with your team.
Frequently asked questions
Something else? Write to support@crunu.com
What is the difference between service variables and shared variables on Railway?
Service variables belong to one service. Shared variables are defined per environment in project settings and can be used by several services. Adding a shared variable to a service creates a reference variable in that service rather than a copy.
Does Railway pass variables to the build?
Yes. Railway exposes variables to the build of each deployment and to the running deployment. With a Dockerfile, declare each one you need with ARG in the right stage.
What does sealing a variable do?
A sealed value is still provided to builds and deployments, but it can't be seen in the dashboard or read through the API, and the Railway CLI doesn't return it. Sealing can't be undone.
Should I use railway run or crunu run locally?
railway run gives your command the variables of a Railway environment, except sealed ones. crunu run gives it the variables of a Crunu environment. Use one source of truth per value, or the two drift apart.
Sources
Every source this page relies on, with the date we last checked it.
- Railway Docs: Using variables · checked October 30, 2026
- Railway Docs: Variables reference · checked October 30, 2026
- Railway Docs: Dockerfiles · checked October 30, 2026