6 days ago
Setup:
Next.js 15 App Router, deployed on Railway via Dockerfile
Multi-stage Docker build (deps → builder → runner)
Railway uses Docker BuildKit with shared remote cache
Symptom:
git push → Railway builds → build logs confirm correct source files
But deployed site always shows old code , never changes
Local build produces completely different hash with correct code
Root cause identified:
Railway's BuildKit shared remote cache caches the runner stage's COPY --from=builder instructions by instruction text, NOT by the actual content hash of what the builder produces. So even when the builder stage rebuilds correctly with new code, the runner stage COPY layers are served from cache containing old static files.
What didn't work:
Changing RUN instruction text
Adding ARG GIT_SHA
Pinning Node.js version (node:20-alpine → node:20.18.0-alpine)
Adding .dockerignore
Adding rm -rf .next before build
3 Replies
Status changed to Open Railway • 6 days ago
6 days ago
Try adding NO_CACHE=1 and redeploy the service. Also try accessing the site from an incognito window as well.
6 days ago
Have you reviewed logs to see what old code is being referenced? As mentioned above, ingotnito window or trying for another browser may fix. Also, if you're running through a service like cloudflare, make sure you are not caching the page or temporarily turn off caching for testing
6 days ago
On top of using NO_CACHE=1, extending on if the case of Cloudflare is proxying the website:
- In Cloudflares dashboard, temporarily enable development mode
- In your browsers devtools, ensure cache is disabled under Network, clean possible Workers/Storage under Application, alternatively use Incognito mode to ensure local no cache is set
- Ensure to redeploy the service
- Test below Dockerfile to ensure runtime only gets explicit built artifacts by avoiding ambiguous copies to keep layers predictable between builder and runner.
Make sure next.config.{js,ts,mjs} has output: 'standalone'
# syntax=docker/dockerfile:1
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json* pnpm-lock.yaml* yarn.lock* ./
RUN npm ci
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
# Use standalone output to keep runtime deterministic
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]