Introduction: Real-World Port Mapping Challenges

In the previous lesson, you learned how NodePort Services expose applications externally using a three-port system: targetPort (where your container listens), port (for internal cluster access), and nodePort (for external access). Those examples used single containers with single ports, which works great for simple applications. However, real-world applications are rarely that simple. You might run a web server alongside a metrics exporter in the same Pod, or expose both an HTTP API and an admin interface on different ports. You might need to standardize your Service ports across different applications while each application uses different internal ports.

This lesson teaches you advanced port mapping patterns that handle these complex scenarios. You'll learn how to expose multiple ports from multi-container Pods, use named port references to decouple configuration from implementation, and debug port misconfigurations when things don't work as expected. By the end, you'll be able to design flexible Service architectures that support sidecars, observability tools, and multiple protocols running together.

Multi-Container Pods with Multiple Ports

Let's start with a common real-world scenario: you have a web application that needs to expose both its main HTTP interface and a metrics endpoint for monitoring. The best practice is to run these as separate containers in the same Pod — your main application container and a sidecar container that exports metrics. Each container listens on its own port, and you need a Service that can route traffic to both ports. This is where multi-port Services come in. A multi-port Service defines multiple port entries, each with its own targetPort that routes to a specific container in your Pods.

Let's build this step by step. First, we'll create a Pod with two containers: an nginx web server and a Prometheus node exporter for metrics. The nginx container will listen on port 80, and the metrics exporter will listen on port 9100. Save this as deployment-multi-container.yaml:

We're creating a Pod (not a Deployment) to keep the example simple, but the same principles apply to Deployments. The app: multi-demo label is crucial — our Service will use this to find the Pod.

The first container runs nginx on port 80. The declaration tells Kubernetes that this container exposes port . While this declaration is technically optional for numeric port routing (the container will listen on port regardless), it's a best practice because it documents which ports your container uses and enables named port references.

Creating a Multi-Port Service

Now we need a Service that can route traffic to both ports. This is where the multi-port configuration comes in. Save this as service-multi-port.yaml:

This looks like a standard Service so far. The selector matches our Pod's app: multi-demo label, so the Service will route traffic to our multi-container Pod. We're using the ClusterIP type, but the same multi-port pattern works with NodePort and LoadBalancer types too.

Here's the key difference: we're defining two port entries instead of one. Each entry has a name (required when you have multiple ports), a port (what clients connect to), and a targetPort (which container port to route to). The first entry named http listens on port 8080 and routes to port 80 (the nginx container). The second entry named metrics listens on port and routes to port (the metrics exporter container). When you have multiple ports, Kubernetes requires you to name each one so you can reference them unambiguously.

Testing Multi-Port Service Connectivity

Let's test each port independently. First, let's test the web server:

In a new terminal, test the HTTP endpoint:

Perfect! The web server is accessible. Now stop the port-forward (Ctrl+C) and test the metrics port:

In a new terminal, test the metrics endpoint:

Excellent! You're seeing Prometheus metrics from the node exporter. This demonstrates that a single Service can route to multiple containers in the same Pod, each listening on different ports. This pattern is essential for sidecars, where you run supporting containers (like metrics exporters, log shippers, or proxies) alongside your main application container.

Defining Named Ports in Containers

In the previous section, we used hardcoded port numbers like targetPort: 80 and targetPort: 9100. This works, but it creates a tight coupling between your Service configuration and your container implementation. If you decide to change the port your application listens on, you have to update both the Deployment (where the container is defined) and the Service (where the targetPort is specified). This becomes error-prone as your application grows. Named ports solve this problem by letting you assign a name to a port in your container spec, then reference that name in your Service. This way, if you change the actual port number, you only update it in one place — the container spec — and the Service continues to work.

Let's see this in action. We'll create a Deployment where the container defines a named port, then create a Service that references that port by name instead of by number. Save this as deployment-named-port.yaml:

This is a standard Deployment with two replicas. The app: named-port-demo label will be used by our Service selector.

Here's the important part: instead of just declaring containerPort: 80, we're also giving it a name: http-web. This name is arbitrary — you can call it whatever makes sense for your application. The key is that this name becomes a stable reference point. We're using nginx which listens on port by default, and we're giving that port a meaningful name. In production applications, you might have containers that listen on non-standard ports (like , , or ), and naming those ports makes your configuration more maintainable.

Referencing Named Ports in Services

Now let's create a Service that references the port by name. Save this as service-named-port.yaml:

Standard Service metadata and selector. Nothing special here.

Here's where named ports shine: instead of targetPort: 80, we're using targetPort: http-web. This references the port name we defined in the container spec. Kubernetes will look at the Pods matched by the selector, find the port named http-web, and route traffic to whatever port number that name corresponds to. If we later decide to change the container to listen on port 9000 instead of 80, we only need to update the containerPort in the Deployment. The Service configuration stays exactly the same because it references the name, not the number.

Let's create the Service:

Let's verify the Service found the correct port:

Perfect! The endpoints show port 80, which is the actual port number from our container spec. The Service successfully resolved the name to port . Let's test connectivity:

Demonstrating Named Port Flexibility

Now let's demonstrate the power of named ports. Imagine you need to change your application to listen on port 8080 instead of 80. With hardcoded port numbers, you'd have to update both the Deployment and the Service. With named ports, you only update the Deployment. Let's simulate this by editing the Deployment:

Find the containerPort section and change it from 80 to 8080:

Save and exit. Kubernetes will roll out new Pods with the updated port. Wait a moment for the rollout to complete, then check the endpoints again:

The endpoints now show port 8080! The Service automatically picked up the change because it references the port by name, not by number. You didn't have to touch the Service configuration at all. This is the key benefit of named ports: they decouple your Service configuration from your container implementation, making your application more maintainable and less error-prone.

One common mistake to watch out for: the port name in your Service's targetPort must exactly match the port name in your container spec. If you have name: http-web in the container but targetPort: http-app in the Service, Kubernetes won't be able to resolve the port and your Service will fail. Always double-check that the names match exactly, including capitalization and hyphens.

Port Mapping Patterns

Now that you understand multi-port Services and named ports, let's explore some practical patterns. One common pattern is standardizing Service ports across different applications while allowing each application to use whatever internal port makes sense. For example, you might decide that all HTTP services in your cluster should expose port 80 on their Service, regardless of what port the actual container listens on. This makes it easier for other services to connect — they always use port 80, and the Service handles the translation to the correct container port.

Let's say you have three different applications: one listens on port 8080, one on port 3000, and one on port 5000. You can create Services for all three that expose port 80, using targetPort to route to the correct container port. Here's what that looks like conceptually:

All three Services expose port 80, so other Pods can connect to app1-svc:80, app2-svc:80, and app3-svc:80 without needing to know the internal port numbers. This standardization makes your service mesh more consistent and easier to understand. You can combine this with named ports for even more flexibility — expose port 80 on the Service, but use to reference a named port in the container.

Supporting Multiple Transport Protocols

Some applications need to handle both TCP and UDP traffic on the same port number. DNS servers are a perfect example — they typically listen on port 53 for both TCP (used for large queries and zone transfers) and UDP (used for standard queries). By default, Kubernetes Services use the TCP protocol, but you can explicitly specify the protocol using the protocol field in your port configuration. When you need to expose the same port number with different protocols, you create separate port entries, each with its own protocol field:

Notice that both entries use the same port number (53) and the same targetPort (53). The only difference is the protocol field. This is different from the multi-port examples we saw earlier, where we used different port numbers for different services. Here, we're using the same port number but different transport protocols. The protocol field accepts two values: TCP (the default) and UDP. Most HTTP/HTTPS services use TCP because it provides reliable, ordered delivery. UDP is used when you need lower latency and can tolerate occasional packet loss, like DNS queries, real-time gaming, or voice/video streaming. When working with protocols, remember that your container must actually listen on both protocols — declaring them in the Service won't make a TCP-only application suddenly accept UDP traffic.

Debugging Port Configuration Issues

When a Service isn't working, the problem is usually one of three things: wrong targetPort, selector mismatch, or the container isn't actually listening where you think it is. Here's a systematic debugging workflow. First, describe the Service to verify its configuration:

Look at the Port, TargetPort, and Endpoints sections. The Port should be what you expect clients to connect to. The TargetPort should match either a port number or a port name from your container spec. The Endpoints should show one or more IP:port combinations. If Endpoints is empty, your selector isn't matching any Pods. If Endpoints shows the wrong port number, your targetPort is incorrect.

Let's simulate a common mistake: a Service with the wrong targetPort. Create a Service that tries to route to port 9999, which doesn't exist:

Now describe it:

Summary: Building Flexible Service Architectures

You've now mastered advanced port mapping patterns that enable complex application architectures in Kubernetes. You learned how to expose multiple ports from multi-container Pods using multi-port Services, where each port entry routes to a different container. You discovered how named ports decouple your Service configuration from container implementation, making your applications more maintainable by letting you change port numbers in one place. You explored practical patterns like standardizing Service ports across applications and following protocol-specific conventions for consistency.

Finally, you learned a systematic debugging workflow for troubleshooting port configuration issues using kubectl describe, kubectl get endpoints, and kubectl port-forward. These skills prepare you to design Services for real-world microservices architectures with sidecars, observability tools, and multiple protocols running together. In the upcoming practice exercises, you'll create your own multi-container setups, experiment with named port references, and debug intentional misconfigurations to solidify your understanding.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal