From localhost to Production: Deploy Your React App on Self-Hosted Kubernetes

From localhost to Production: Deploy Your React App on Self-Hosted Kubernetes
You’ve got a Kubernetes cluster humming along. Certificates are green. Ingress is configured. And yet, every project you build still lives at localhost:5173 like it’s paying rent there. Time to evict it.
This guide walks through the actual deployment pipeline: from scaffolding a React app to pushing a container image to GitHub Container Registry (GHCR) and orchestrating it on your self-hosted cluster with TLS termination. No managed services. No cloud vendor lock-in. Just you, your cluster, and a working production URL.
Cloud providers offer convenience at the cost of control and recurring fees. A self-hosted cluster gives you full ownership of your infrastructure, predictable costs, and the satisfaction of knowing exactly where your bits live. The trade-off is operational responsibility—but if you're reading this, you've already accepted that bargain.
Init
What you need
Before diving in, ensure you have:
A working Kubernetes cluster (k3s, kubeadm, or similar)
kubectlconfigured and pointing to your clusterdockerinstalled locally for building imagesGitHub account
Node.jsandyarnfor local developmentA domain pointing to your cluster’s ingress controller
cert-managerconfigured with a ClusterIssuer (for TLS)
Apply
Create and Test Your React Project Locally
Spin up a fresh React project using Vite. The --template react flag gives you a minimal, modern setup without the bloat of create-react-app.
yarn create vite react --template react
When prompted, accept the defaults. Vite will scaffold the project, install dependencies, and drop you into a ready-to-run state.
To start project again after scaffolding
cd react
yarn dev
Then open http://localhost:5173 in your browser. You should see the default Vite + React welcome page. Congratulations—your project works. Locally. Like every other project you’ve abandoned in your ~/projects graveyard.
Prepare the Project for Docker
To ship this beyond your machine, you need a container. Create two files in your project root: a Dockerfile for the build process and a .dockerignore to keep the image lean.
This Dockerfile uses a multi-stage build. Stage one compiles your React app with Node. Stage two copies only the static output into an NGINX image. The result is a production image under 50MB instead of 1GB+ of node_modules debris.
Dockerfile
# Stage 1: Build
FROM node:22-alpine AS builder
WORKDIR /app
# Copy dependency files
COPY package.json yarn.lock ./
# Install dependencies
RUN yarn install --frozen-lockfile
# Copy source
COPY . .
# Build static files
RUN yarn build
# Stage 2: Runtime (NGINX)
FROM nginx:1.27-alpine
# Copy compiled React build to NGINX
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
.dockerignore
node_modules
build
dist
.git
.gitignore
*.log
.DS_Store
.env
.env.local
Build and Push to GitHub Container Registry
GHCR provides free container hosting tied to your GitHub account. Authenticate Docker with your Personal Access Token, build the image, and push it.