Key Insight: The Full Traffic Chain
Internet
↓
Ingress (routes by hostname/path → service name + service port)
↓
Service (port → targetPort)
↓
Pod/Container (containerPort / what the process actually binds)
Each arrow is where a port number must match its neighbour. A mismatch at any link causes traffic to silently fail to route.

containerPort is Documentation Only
spec:
containers:
ports:
- containerPort: 3005 # ← has NO effect on actual network behaviour
- This is purely informational — a hint for humans and tooling.
- Kubernetes does not use it to expose or map any port.
- The container listens on whatever port its process actually binds to.
- The container will still listen on its default port (e.g. 80) regardless of what
containerPortsays.
[!note] Edge case Some tools (e.g.
kubectl port-forward) can use named ports defined here, but the field has zero effect on actual network routing.
Environment Variable PORT Actually Changes the Listening Port
# configMap
data:
PORT: "3005"
- Setting
PORT=3005via a ConfigMap causes the container to actually listen on port 3005. - Visible in the pod logs and verifiable via port-forwarding.
- ⚠️ This only works if the application is coded to respect the
PORTenvironment variable.
audiobookshelfsupports this — many images do not. - Works regardless of what value is set in
containerPort.
Port Numbers Refer to the Container/Cluster Network — Not the Host
- In Docker Compose:
HOST_PORT:CONTAINER_PORT— maps a port from the physical machine into the container. - In Kubernetes: there is (almost) no host port. The single port number always refers to the container-side port.
- Exposure to the outside world is handled by Services and Ingress at a higher level.
[!note] Edge case:
hostPortThere is ahostPortfield in the container spec that mimics Docker’s host-side mapping — but it is considered an anti-pattern in Kubernetes and almost never used.
Service Ports
apiVersion: v1
kind: Service
spec:
ports:
- port: 8080 # Port other pods in the cluster use to reach this Service
targetPort: 3005 # Must match the containerPort (the actual listening port)
selector:
app: audiobookshelf # Must match the pod's label
| Field | What it means |
|---|---|
port |
Port that other pods use to reach this Service (via cluster-internal DNS) |
targetPort |
Port on the pod/container — must match containerPort |
selector |
Connects the Service to the correct pods via matching labels |
Important rules:
targetPortandcontainerPortmust match.- If
targetPortis omitted, Kubernetes uses the value ofport— so in that caseportmust equalcontainerPort. - Always set
targetPortexplicitly to avoid subtle bugs when values change. targetPortcan also be a named port string (defined vianame:incontainerPort), which decouples the service from exact port numbers.
Ingress Port
apiVersion: networking.k8s.io/v1
kind: Ingress
spec:
rules:
- host: audiobookshelf.example.com
http:
paths:
- backend:
service:
name: audiobookshelf-svc # Must match Service name
port:
number: 8080 # Must match Service's `port` field
- The port number here must match the
portfield in the corresponding Service (nottargetPort, notcontainerPort). - The service
namemust also match.
Summary: What Must Match What
Ingress backend port ──► Service port
│
targetPort ──► containerPort / actual process port
▲
set via env var PORT
(if app supports it)
Practical Verification Steps
-
Check what port the container actually listens on → look at pod logs
-
Verify via port-forwarding:
kubectl port-forward pod/<pod-name> 3005:3005 # then curl localhost:3005 -
Describe the pod to see reported
containerPort(remember: this may not reflect reality)