Kubernetes: NGINX NodePort Deployment
A NodePort Service is the most direct way to reach an application running inside Kubernetes from outside the cluster. Kubernetes reserves one port on every node and forwards anything arriving on it to the pods behind the Service, so the application answers on <any node IP>:<nodePort> — including nodes that are not currently running a replica.
This guide deploys NGINX, scales it to three replicas, exposes it with a NodePort Service, and tests it from both outside and inside the cluster. It assumes you already have a cluster running; if you do not, start with installing a Kubernetes cluster on Ubuntu.
How NodePort relates to the other Service types
The three main Service types are layered, each adding to the one before it:
- ClusterIP — the default. A virtual IP that only resolves inside the cluster.
- NodePort — everything ClusterIP does, plus one port opened on every node.
- LoadBalancer — everything NodePort does, plus an external load balancer with an address of its own. That is covered separately in exposing NGINX with a LoadBalancer Service.
Creating a NodePort Service therefore also creates a ClusterIP behind it. A request arriving from outside travels from the node IP and node port, to the Service's ClusterIP, to one of the pods — with kube-proxy on the receiving node picking the pod and forwarding the packet, even if the chosen pod lives on a different node.
Step 1: Optional — allow deployments on control plane nodes
Control plane nodes carry a taint that stops ordinary workloads being scheduled on them. On a single-node or small lab cluster you may want to remove it so your three replicas have somewhere to run:
kubectl taint nodes --all node-role.kubernetes.io/control-plane-
The trailing hyphen is what removes the taint. On a multi-node cluster with dedicated workers, skip this step.
Step 2: Create the Deployment
Deploy the NGINX application:
kubectl create deployment nginx --image=nginx
Step 3: Scale the Deployment
Scale to three replicas so there is something for the Service to load balance across:
kubectl scale deployment/nginx --replicas=3
Confirm all three are running before continuing:
kubectl get pods -o wide
Step 4: Expose the Deployment with a NodePort
kubectl expose deployment/nginx --type=NodePort --port=8888 --target-port=80 --name=nginx-np
This is the step people most often misread, because --port is not the port you connect to from outside:
--port=8888is the port on the Service's ClusterIP, used by other pods inside the cluster.--target-port=80is the container port NGINX listens on.- The node port is not specified here at all. Kubernetes allocates a free one automatically from the range 30000–32767 (set cluster-wide by the API server's
--service-node-port-rangeflag).
Step 5: Find the allocated node port
kubectl get service nginx-np
The PORT(S) column shows both numbers:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-np NodePort 10.96.24.7 <none> 8888:31234/TCP 30s
Read that as Service port 8888, node port 31234. The number after the colon is the one to use from outside. To script it rather than read it off:
kubectl get svc nginx-np -o jsonpath='{.spec.ports[0].nodePort}'
Step 6: Test it from outside the cluster
List the node addresses, then connect to any one of them on the node port:
kubectl get nodes -o wide
curl http://<node ip>:31234
You should get the NGINX welcome page back. Try a different node IP with the same port — it works from every node, not just the ones hosting a pod.
Step 7: Test it from inside the cluster
Inside the cluster the ClusterIP and Service port apply instead. Get a pod name, open a shell in it, and curl the Service:
kubectl get pod
kubectl exec -it <pod name> -- /bin/bash
Then, from inside the pod:
curl http://nginx-np:8888
curl http://10.96.24.7:8888
Both work: cluster DNS resolves the Service name to its ClusterIP, which is why in-cluster clients should use the name rather than a hard-coded address.
Step 8: Pinning a specific node port (optional)
An automatically allocated port is fine for testing, but it changes if the Service is recreated. To fix it, set nodePort explicitly in a manifest:
apiVersion: v1
kind: Service
metadata:
name: nginx-np
spec:
type: NodePort
selector:
app: nginx
ports:
- port: 8888
targetPort: 80
nodePort: 30080
The value must sit inside the cluster's node port range, and must not already be taken by another Service.
Common mistakes
- Connecting to the Service port from outside.
http://<node ip>:8888will not answer — 8888 only exists on the ClusterIP, inside the cluster. - A firewall in the way. Cloud security groups and host firewalls such as
ufwrarely allow 30000–32767 by default. If the curl times out rather than being refused, suspect the firewall first. - Assuming the node must host a pod. Every node listens on the node port and forwards as needed.
- Leaving the Service in place. A NodePort is open on every node in the cluster, so remove it when you have finished testing.
When to use NodePort — and when not to
NodePort is ideal for labs, bare-metal clusters with no cloud integration, and quick verification that a Deployment actually serves traffic. It is a poor fit for production web traffic: the port numbers are unfriendly, there is no TLS termination or host and path routing, and clients need to know node addresses that change as the cluster changes. For real traffic, put something in front of it — either a LoadBalancer Service, or an external NGINX load balancer pointing at the cluster.
Step 9: Cleanup
Remove the Deployment and Service when you are done:
kubectl delete deployment nginx
kubectl delete service nginx-np