Full lesson
Explore the full explanation, examples, and visuals at your own pace.
The broken setup
containerPort doesn't open anything. Here, the Node app listens on 3000, but the Deployment declares containerPort 80, hoping for a Docker-style mapping. The Service then sends traffic to port 80. Nothing listens there, so you get connection refused.
# app code: server.listen(3000)
# deployment.yaml
containers:
- name: web
image: shop-api
ports:
- containerPort: 80
---
# service.yaml
ports:
- port: 80
targetPort: 80Traffic lands on port 80
Follow the request. The client calls the Service on port 80, and the Service forwards it to the pod IP on port 80, because targetPort is 80. If you omit targetPort, it defaults to the Service port, so you get the same result. Nothing is bound to 80, so the pod refuses the connection. The app on 3000 never sees it.
Docker mental model vs Kubernetes reality
With docker -p 80:3000, Docker actively translates traffic from host port 80 to container port 3000, so the app answers. Kubernetes does no such translation. Traffic arrives on exactly the port it was sent to, port 80, where nobody listens, so it's refused while the app sits alone on 3000.
What containerPort actually does
containerPort is mostly informational. It documents which port your app uses and gives that port a name targetPort can reference. Leaving it out doesn't block access, and setting it to 80 doesn't move your app off 3000.
- Documents the port your app uses
- Names a port targetPort can reference
- Never opens, maps, or blocks ports
The fix: aim at 3000
The essential change is targetPort: 3000 in the Service. That's what sends traffic to where the app actually listens. Setting containerPort to 3000 keeps the Deployment honest. And since the port is named http, the Service could use targetPort: http instead of the number.
# deployment.yaml
ports:
- name: http
containerPort: 3000
---
# service.yaml
ports:
- port: 80
targetPort: 3000The 80-to-3000 mapping lives in the Service
Now the request works. The client still calls the Service on port 80. The Service forwards it to the pod on port 3000, where the app is actually listening. The 200 OK travels back the same way. That's the docker -p style translation you wanted, and it lives in the Service, not in containerPort.
containerPort is a sign on the door, not the door.
containerPort is a sign on the door, not the door. When you hit connection refused, check that the Service's targetPort matches the port your app actually listens on.
