Railway: build- and runtime env vars
If you’re deploying a Next.js app to Railway using a Dockerfile and wondering why your NEXT_PUBLIC_* environment variables are always undefined despite being set in the Railway dashboard — you’re not alone.
The Problem
You set NEXT_PUBLIC_XYZ in Railway, but your app keeps falling back to the default:
// This always uses the fallback, even though the var is set in Railway!
const someUrl = process.env.NEXT_PUBLIC_XYZ || 'https://fallback.com';Why This Happens
Two things are colliding here:
-
Next.js inlines
NEXT_PUBLIC_*variables at build time. Duringnext build, everyprocess.env.NEXT_PUBLIC_*reference gets replaced with the literal string value. If the variable isn’t set when you build, it’s baked in asundefinedforever. -
Railway only auto-injects env vars at runtime, not build time. When building a Docker image, Railway doesn’t automatically pass your dashboard variables to the build process.
So even though the variable exists in Railway, it wasn’t there when next build ran inside Docker, and the bundled JS has undefined hardcoded.
The Fix
You need to explicitly declare the variable as a Docker ARG so Railway knows to pass it during the build.
FROM node:24-alpine AS builder
# ... copy files, install deps ...
# Declare the ARG — Railway will automatically inject the value
# if it exists in your service's environment variables
ARG NEXT_PUBLIC_XYZ="https://fallback.com"
# Convert ARG to ENV so `next build` can see it
ENV NEXT_PUBLIC_XYZ=$NEXT_PUBLIC_XYZ
# Now next build will inline the real value
RUN pnpm run buildKey points:
ARGmakes the variable available during the build. Railway sees this and passes the value from your dashboard.ENVexposes it to the build process (sonext buildcan readprocess.env.NEXT_PUBLIC_*).- Default values in
ARGare fallbacks if Railway doesn’t provide a value — they don’t block Railway from injecting the real one.
Build-time vs Runtime: Quick Reference
| Variable Type | When Read | Needs ARG in Dockerfile? |
|---|---|---|
NEXT_PUBLIC_* | Build time (inlined into JS bundle) | âś… Yes |
Server-side (e.g., DATABASE_URL) | Runtime (read on each request) | ❌ No* |
*Unless your build process needs it (e.g., Drizzle generating types, Prisma generating client).
Common Gotcha
If you add a new NEXT_PUBLIC_* variable to Railway but forget to add the ARG + ENV to your Dockerfile, you’ll need to:
- Add the
ARGandENVdeclarations - Trigger a new deploy so the image rebuilds with the variable present
Just changing the value in Railway’s dashboard doesn’t help — the old build is cached with undefined already baked in.
TL;DR
NEXT_PUBLIC_*= inlined at build time = needsARG+ENVin Dockerfile- Server-side vars = read at runtime = Railway injects automatically
- After adding new
ARGs, redeploy to rebuild the image