Deploying a React Router 7 App to Google Cloud Run with Docker
React Router 7 in framework mode builds to a Node server, which means it needs somewhere to run. Cloud Run is a great fit: give it a container and it handles the rest. In this post, we'll go from a working app to a live URL.
Why Cloud Run?
- Scales to zero—you pay nothing when nobody visits
- Scales up automatically when traffic spikes
- All it needs is a Dockerfile
The Server
A React Router 7 template already comes with everything we need. The build outputs a server bundle, and react-router-serve runs it:
{"scripts": {"build": "react-router build","start": "react-router-serve ./build/server/index.js"}}
One detail matters for Cloud Run: it tells your container which port to use through the PORT env variable. react-router-serve respects it out of the box, so there's nothing to configure.
The Dockerfile
A two-stage build keeps the final image small—dev dependencies are used to build, then left behind:
FROM node:20-alpine AS buildWORKDIR /appCOPY package*.json ./RUN npm ciCOPY . .RUN npm run buildFROM node:20-alpineWORKDIR /appENV NODE_ENV=productionCOPY package*.json ./RUN npm ci --omit=devCOPY --from=build /app/build ./buildCMD ["npm", "run", "start"]
And a .dockerignore so local artifacts don't leak into the image:
node_modulesbuild.git.env
Deploying
With the gcloud CLI installed and authenticated, one command builds and deploys straight from source:
gcloud run deploy my-app \--source . \--region us-central1 \--allow-unauthenticated
Cloud Run uploads your code, builds the Dockerfile in the cloud, and hands back a public URL. Every deploy after this is the same one command.
Conclusion
A Dockerfile, a .dockerignore, and one gcloud command—that's the whole deployment. Cloud Run takes care of scaling, HTTPS, and the infrastructure you'd rather not manage.
Don't forget your env variables—your local .env doesn't come along for the ride. Pass them with --set-env-vars on deploy, or set them on the service in the Cloud Run console. 😉