DevOps

Kubernetes NodePort Minikube: React Not Loading Fix

Troubleshoot Kubernetes NodePort not accessible on Minikube for React frontend served by Nginx. Fix blank pages, config substitution, networking issues with minikube service, port-forward, and proxy solutions.

1 answer 1 view

Kubernetes NodePort Not Accessible on Minikube: React Frontend Not Loading

I’m deploying a React app frontend and backend on Minikube using Kubernetes. The frontend is served via Nginx in a Docker container, but nothing loads when accessing <minikube ip>:30100. How can I troubleshoot and fix NodePort access issues?

Frontend Dockerfile

dockerfile
# build
FROM node:18.19.1 AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# runtime
FROM nginx:alpine
# install envsubst
RUN apk add --no-cache gettext
COPY --from=build /app/build /usr/share/nginx/html
COPY public/config.js /usr/share/nginx/html/config.js
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 80
CMD ["/entrypoint.sh"]

Entrypoint Script (entrypoint.sh)

bash
#!/bin/sh
set -e

if [ -f /usr/share/nginx/html/config.js ]; then
 envsubst '${BACKEND_URL}' < /usr/share/nginx/html/config.js > /tmp/config.js
 mv /tmp/config.js /usr/share/nginx/html/config.js
fi

exec nginx -g 'daemon off;'

config.js in React App

javascript
window.RUNTIME_CONFIG = {
 BACKEND_URL: "${BACKEND_URL}"
};

Usage in React App

javascript
const x = window.RUNTIME_CONFIG.BACKEND_URL;

Frontend Deployment Env Var

yaml
env:
- name: BACKEND_URL
 value: http://server-service:8081

Frontend Service Ports

yaml
ports:
 - protocol: TCP
 port: 3000
 targetPort: 80
 nodePort: 30100

Backend CORS Allowed Origins (Go)

go
var allowedOrigins = map[string]bool{
 "http://localhost:3000": true,
 "http://localhost:30100": true,
 os.Getenv("CLIENT_IP"): true,
 
 "http://<nodeip>:30100": true,
 "http://app-service:3000": true,
}

Issue: Page is blank or not loading at minikube ip:30100. Backend is at server-service:8081 internally. New to Kubernetes—need help debugging NodePort, service exposure, or config substitution.

Kubernetes NodePort Minikube: If your React frontend served by Nginx shows a blank page at :30100, the usual causes are (a) the NodePort not being reachable from your host/VM and (b) the client-side BACKEND_URL being set to an internal cluster hostname (like server-service) that the browser can’t resolve. Start by checking service endpoints, pod logs, and the runtime-substituted /usr/share/nginx/html/config.js in the container, then choose one of three fixes: expose the backend externally, proxy API calls through Nginx, or use port-forward/minikube service for local dev.


Contents


Kubernetes NodePort and Minikube networking

NodePort opens a port on each Kubernetes node and maps it to your service targetPort. In a real cluster that normally means you can hit : from outside the cluster. Minikube complicates this: depending on the driver (docker, hyperkit, virtualbox, Hyper-V) the Node IP and how it’s reachable vary. On macOS/Windows with the Docker driver the node IP is inside a container/VM and the NodePort isn’t always reachable from your host the way you expect. The minikube docs explain these network limitations and offer minikube-specific access methods: see the minikube “Accessing apps” guide for details and caveats.

Common symptoms when NodePort isn’t reachable:

  • curl :30100 times out from your host
  • browser shows no network requests to static assets (or 504/timeout)
  • the NodePort is present in the Service but Endpoints is empty (no pods matched)

What to check first

  • Is the NodePort assigned and in the right range (30000–32767)? Run:
bash
kubectl get svc frontend -o wide
kubectl describe svc frontend
  • Are there endpoints? If Endpoints is empty your Service selector doesn’t match the Deployment labels:
bash
kubectl get endpoints frontend
kubectl get pods --show-labels
  • Is minikube driver blocking host access to the node IP? Try the minikube helper:
bash
minikube ip
minikube service frontend --url

The minikube service command gives a working URL (or starts a tunnel) appropriate for your environment; the docs explain nuances for different drivers: https://minikube.sigs.k8s.io/docs/handbook/accessing/.

If you get a connection from inside the minikube VM but not from your host, the problem is network reachability, not Kubernetes wiring.


Nginx Kubernetes and runtime config substitution

Two traps are common when serving a client bundle via Nginx inside Kubernetes.

  1. Wrong BACKEND_URL value for the browser
    Your entrypoint does envsubst into /usr/share/nginx/html/config.js at container start. That yields a config file that the browser will fetch. In your Deployment you set:
yaml
env:
- name: BACKEND_URL
 value: http://server-service:8081

But note: server-service is an internal DNS name that resolves only inside the cluster (pods). The React app runs in the user’s browser outside the cluster, so requests to http://server-service:8081 will fail. That commonly produces a blank page when the app hits unhandled runtime errors or when API calls error out and UI never initializes.

Check the actual value served to the browser:

  • Open in your browser: http://:30100/config.js
  • Or exec into the frontend pod and inspect:
bash
kubectl exec -it <frontend-pod> -- cat /usr/share/nginx/html/config.js

If the file contains “http://server-service:8081”, that’s the root problem for browser-to-API connectivity.

  1. Nginx not serving files / permission or entrypoint issues
    Look at the frontend pod logs. If entrypoint fails, nginx won’t serve content:
bash
kubectl logs <frontend-pod>

Confirm Nginx serves index.html from the expected path:

bash
kubectl exec -it <frontend-pod> -- curl -sS -I http://localhost/

Quick diagnosis question: does config.js load in the browser? Check Network tab in DevTools for /config.js and for requests to your BACKEND_URL. Browser console will show CORS or network errors.


Step-by-step troubleshooting checklist

  1. Verify pods and service exist
bash
kubectl get pods -o wide
kubectl get svc frontend -o wide
  1. Check the Service wiring
bash
kubectl describe svc frontend
kubectl get endpoints frontend
  • If Endpoints is empty -> Service selector mismatch. Compare Deployment labels to the Service selector.
  1. Check that Nginx built and the entrypoint ran
bash
kubectl logs <frontend-pod>
kubectl exec -it <frontend-pod> -- ls -la /usr/share/nginx/html
kubectl exec -it <frontend-pod> -- cat /usr/share/nginx/html/config.js
  1. Test connectivity from inside the cluster (pod -> backend)
bash
kubectl exec -it <frontend-pod> -- curl -sS http://server-service:8081/health || true

If this works, the cluster DNS and service are fine.

  1. Test NodePort from the minikube node
bash
minikube ip # get node IP
curl -v http://$(minikube ip):30100 || true
# or, from inside minikube VM:
minikube ssh -- curl -v http://localhost:30100 || true

If curl inside minikube returns content but host curl times out, it’s a host→VM networking issue.

  1. Check browser DevTools
  • Look for 404/500 for static files, missing /config.js, or CORS errors when calling the API.
  1. Inspect backend CORS settings
    Your Go allowedOrigins map must contain the exact origin the browser uses, e.g. http://:30100. For local dev you can temporarily relax CORS to simplify debugging.

Fixes: quick (dev) and robust (prod)

Option A — Fast dev workaround (no config changes)

  • Use minikube’s helpers:
bash
minikube service frontend --url
# or port-forward:
kubectl port-forward svc/frontend 30100:80
# then open http://localhost:30100

These make the frontend reachable without changing app config. For backend testing:

bash
kubectl port-forward svc/server-service 8081:8081
# point the browser or config.js to http://localhost:8081

Reference: https://minikube.sigs.k8s.io/docs/handbook/accessing/

Option B — Make the frontend’s API URL resolvable by the browser
Update the front-end config at runtime to an address the browser can reach:

bash
kubectl set env deployment/frontend BACKEND_URL="http://$(minikube ip):<backend-nodePort>"
# This triggers a rolling restart so envsubst runs with the new value.

Caveat: this is fine for local testing, not ideal for production.

Option C — Proxy API through Nginx (recommended for single-origin behavior)
Modify your Nginx to proxy /api to the internal service. Then set BACKEND_URL to a relative path (e.g. “/api”). The browser remains same-origin and CORS is avoided.

Example nginx snippet to include in the image:

nginx
server {
 listen 80;
 location / {
 try_files $uri $uri/ /index.html;
 }
 location /api/ {
 proxy_pass http://server-service:8081/;
 proxy_set_header Host $host;
 proxy_set_header X-Real-IP $remote_addr;
 }
}

Then set config.js to:

javascript
window.RUNTIME_CONFIG = { BACKEND_URL: "/api" };

This is robust: client calls /api, Nginx forwards to the internal service name that only needs to resolve inside the cluster.

Option D — Use Ingress or LoadBalancer

  • Enable ingress in minikube and create an Ingress resource to route a host to services. Ingress avoids NodePort pain and lets you use hostnames:
bash
minikube addons enable ingress
# apply an Ingress manifest mapping host/myapp.local -> frontend service and /api -> backend

Alternatively use minikube tunnel with a LoadBalancer service. See minikube issues and examples where NodePort is not accessible under some drivers: https://github.com/kubernetes/minikube/issues/9499 and the Stack Overflow threads on expose/NodePort behavior.


Quick command reference

  • Show services, ports, NodePort:
bash
kubectl get svc
kubectl describe svc frontend
  • Show endpoints (should not be empty):
bash
kubectl get endpoints frontend
  • View runtime config.js in the pod:
bash
kubectl exec -it $(kubectl get pods -l app=frontend -o jsonpath='{.items[0].metadata.name}') -- cat /usr/share/nginx/html/config.js
  • Test cluster-internal connectivity:
bash
kubectl exec -it <frontend-pod> -- curl -sS http://server-service:8081/health
  • Make frontend reachable on localhost (dev):
bash
kubectl port-forward svc/frontend 30100:80
# or
minikube service frontend --url
  • Set BACKEND_URL to minikube IP (dev):
bash
kubectl set env deployment/frontend BACKEND_URL="http://$(minikube ip):<backend-nodePort>"
  • Enable ingress on minikube:
bash
minikube addons enable ingress

Sources


Conclusion

Kubernetes NodePort Minikube problems usually boil down to networking reachability or incorrect runtime configuration for browser clients—especially when the frontend’s BACKEND_URL points at an internal service name. Inspect endpoints, pod logs, and the substituted /config.js; for local workarounds use minikube service or kubectl port-forward, and for a robust solution proxy API calls through Nginx or use Ingress so the browser talks to an address it can actually resolve.

Authors
Verified by moderation
Moderation
Kubernetes NodePort Minikube: React Not Loading Fix