TL;DR: Railway's build cache permanently misdetected my Next.js app as Deno, forcing me to delete the entire project. Now I build Docker images locally and deploy via GitHub Container Registry for full control over the build process.
After spending an entire day debugging Railway's build system, I discovered why Docker with GitHub Container Registry (GHCR) can be a better choice than platform-native builds. Here's what I learned and how you can avoid my mistakes.
The Problem That Started Everything
My Next.js app refused to build on Railway. The platform had detected my project as "Deno" (due to a stray test file) and cached this decision permanently. Even after:
- Removing all Deno files from my repository
- Adding proper Node.js configuration files
- Manually deleting Deno from Railway's dashboard (Build > Providers section)
- Multiple redeploys and cache clears
Railway continued trying to build with Deno. The platform was building phantom files that no longer existed, completely ignoring my current repository state.
The only fix? Delete the entire Railway project and start over.
This experience taught me that "build once, deploy anywhere" with containers beats platform-specific auto-detection when things go wrong.
The GHCR Solution
Instead of relying on Railway's auto-detection, I now build Docker images locally and deploy them via GitHub Container Registry. Here's how:
Prerequisites
-
Configure Next.js for standalone output in
next.config.js
:
module.exports = {
output: 'standalone', // Essential - creates the server.js file
// ... your other config
}
2. Set up GitHub authentication (one-time)
# Create a personal access token at github.com/settings/tokens
# with 'write:packages' permission
# Login to GitHub Container Registry
echo $GITHUB_TOKEN | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdin
1. Create your Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm ci
# Build application
COPY . .
RUN npm run build
# Production stage
FROM node:18-alpine
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
# Copy built application
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"]
2. Build and push to GHCR
# Build your image
docker build -t myapp:latest .
# Tag for GHCR
docker tag myapp:latest ghcr.io/YOUR_GITHUB_USERNAME/myapp:latest
# Push to registry
docker push ghcr.io/YOUR_GITHUB_USERNAME/myapp:latest
3. Configure Railway
Create railway.toml
in your repository:
[deploy]
image = "ghcr.io/YOUR_GITHUB_USERNAME/myapp:latest"
Railway now deploys your pre-built image instead of building from source.
Why This Approach Works Better
Control: You define the build process, not the platform. What works locally works in production.
Portability: Your Docker image deploys anywhere—Railway today, AWS tomorrow. No vendor lock-in.
Debugging: Test the exact production image locally with docker run -p 3000:3000 myapp:latest
. Build errors surface immediately on your machine.
Predictability: No cached detection states or mystery failures. Docker builds are explicit and reproducible.
Quick Comparison
Railway Native Builds | Docker + GHCR |
---|---|
Instant setup | Requires Docker setup |
Platform controls build | You control everything |
Limited debugging | Test locally first |
Railway-only | Deploy anywhere |
When to Use Each Approach
Railway native builds: Best for standard apps with clean project structures, prototypes, and when you trust the auto-detection to work correctly.
Docker + GHCR: Choose this when you need explicit build control, have hit detection issues, or prefer testing exact production images locally.
The Verdict
Railway's native builds optimise for the happy path—when they work, they're delightfully simple. But when detection goes wrong or you need specific configurations, the magic becomes a liability.
After my debugging marathon, I've learned to value explicit over implicit, control over convenience. Docker with GHCR gives you that control while still deploying to Railway's excellent infrastructure. Your mileage may vary, but for me, the peace of mind is worth the extra setup.
Getting Started
If you're experiencing Railway build issues or want more deployment control:
- Start with the Dockerfile above
- Adjust for your framework's requirements
- Test locally before pushing to GHCR
- Enjoy predictable deployments
The extra setup time pays off in deployment confidence and debugging capability.
Useful Resources
Have you switched from platform builds to Docker? What drove your decision? Let me know in the comments.
Top comments (0)