Railway Variables Only Reach Services That Link Them (Diagram)

Defining a variable in Railway puts it in the project. It does not put it in any service. A service sees a value only when its own Variables tab holds a reference to it.

This is the source of most "why is my env var undefined on Railway" threads, and it is also the reason Railway's shared variables are safer than they look. Nothing is broadcast. Every service that holds a secret holds it because someone added the link.

A flowchart of Railway variable scope. The Postgres service, where Railway sets DATABASE_URL, and the project-level Shared Variables holding STRIPE_SECRET_KEY both feed the web service, which added a reference to each. A dotted line from Shared Variables to the worker service, which added no references, is labelled not linked, undefined: the value exists in the project and never reaches that service.
Defining a variable puts it in the project. Referencing it puts it in a service.Full explanationMermaid source

Reading the diagram

The two grey boxes at the top are where values are defined. Neither one is a service you deploy code to.

Postgres service, left. When you add a Railway Postgres database, Railway sets DATABASE_URL on the database service itself. It does not copy it into your app. Plenty of first deploys crash on exactly this, because the value is visibly there in the dashboard, just on the wrong service.

Shared Variables, right. These live at the project level. Putting STRIPE_SECRET_KEY here defines it once, and that is all it does.

The two green boxes at the bottom are your services. Both are server-side, and the difference between them is only what their Variables tabs contain.

web service. It has two references, ${{Postgres.DATABASE_URL}} and ${{shared.STRIPE_SECRET_KEY}}, so both values arrive at build and at runtime. The edge labels are the references you type, which is the point: the arrow is the line in the Variables tab.

worker service. No references, so nothing arrives. process.env.STRIPE_SECRET_KEY is undefined there, even though the key sits one box away in the same project.

The fix for an undefined variable is a reference, not a copy. Pasting the raw value into the worker's Variables tab also works, but now you have two copies to rotate. A ${{shared.KEY}} reference keeps one source and follows rotations on the next deploy.

Why the missing arrow is a feature

The dotted line is the one people hit as a bug, but it is the safe default. A secret you did not link is a secret that service's dependencies cannot read.

That matters because a linked value is present during the build, while npm runs every install script in the dependency tree. Link your database URL to a static frontend "just in case" and it is available to all of those scripts, and if it sits behind a VITE_ or NEXT_PUBLIC_ prefix it gets compiled into the JavaScript you serve. Each arrow you add is a decision about who else gets the value, so add the ones a service actually uses and remove the ones it stopped using.

The Mermaid source

Copy this and rename the services to match your project.

---
title: "A variable only reaches services that link it"
---
flowchart TB
    P["Postgres service<br/>Railway sets <code>DATABASE_URL</code> here"]
    S["Shared Variables<br/><code>STRIPE_SECRET_KEY</code>"]

    W["web service<br/>both references added"]
    K["worker service<br/>no references added"]

    P -->|"<code>${{Postgres.DATABASE_URL}}</code>"| W
    S -->|"<code>${{shared.STRIPE_SECRET_KEY}}</code>"| W
    S -.->|"not linked: <b>undefined</b>"| K

    classDef private fill:#ecfdf5,stroke:#10b981,stroke-width:2px,color:#1c1917
    classDef store fill:#f5f5f4,stroke:#57534e,stroke-width:2px,color:#1c1917

    class P,S store
    class W,K private

    linkStyle 2 stroke:#57534e,stroke-width:3px

Why is DATABASE_URL undefined in my Railway service?

Because Railway sets DATABASE_URL on the Postgres service, not on your app. Your app only gets it when its own Variables tab contains a reference such as DATABASE_URL=${{Postgres.DATABASE_URL}}. Add the reference and Railway redeploys the service with the value.

Do Railway shared variables apply to every service automatically?

No. A shared variable is defined once at the project level and reaches nothing until a service links it, which writes a reference of the form ${{shared.KEY}} into that service. A service you never linked reads undefined.

Is a reference a copy of the value?

No, it is a pointer. Rotate the shared value once and every service that references it picks up the new value on its next deploy. That is the main reason to use shared variables for keys several services need.

Does linking a variable expose it anywhere new?

A linked variable is present during the service's build as well as at runtime, so it is visible to install scripts and, behind a VITE_ or NEXT_PUBLIC_ prefix, gets compiled into your frontend bundle. Link each value only to the services that need it.

Did a Railway variable end up in your frontend?

A scan reads your live site the way a visitor would and reports any keys it finds in the bundle.

diagrams

Railway Variables Only Reach Services That Link Them (Diagram)