I deployed Nexus to Kubernetes. The pod was running. Green checkmarks everywhere. Then someone asked “what’s the URL?” and I realized I had no idea how any of the networking worked. IP addresses, ports, DNS, TLS, ingress – I’d been copy-pasting YAML without understanding what connects a running pod to a URL you can type in a browser.
So I went and learned it. Here’s the mental model – from pod to browser, every layer explained.
The problem – your pod is invisible
You deploy a pod. It gets an IP address like 10.244.3.17. Great. But:
- That IP only works inside the cluster. Your laptop can’t reach it.
- That IP is ephemeral. If the pod restarts, it gets a new one.
- Nobody is going to bookmark
http://10.244.3.17:8081.
You need four things to go from running pod to usable URL:
Pod (has an internal IP, ephemeral)
→ Service (stable internal endpoint)
→ Ingress (external access + hostname)
→ DNS (human-readable URL)
→ TLS (HTTPS, encryption)
Each layer solves one problem. Let’s go through them.
Layer 1: Service – a stable address
A Kubernetes Service gives your pod a stable internal IP and name. Even if the pod dies and a new one starts with a different IP, the Service keeps pointing at whatever’s alive.
apiVersion: v1
kind: Service
metadata:
name: nexus-repository-manager
namespace: nexus
spec:
selector:
app: nexus-repository-manager # "find pods with this label"
ports:
- port: 8081 # the port the Service listens on
targetPort: 8081 # the port the pod listens on
Now instead of 10.244.3.17:8081 (which changes), you can use nexus-repository-manager.nexus.svc.cluster.local:8081 (which doesn’t). That’s the Service’s internal DNS name – Kubernetes creates it automatically.
But this still only works inside the cluster. A pod can reach it. Your browser can’t.
Layer 2: Ingress – the front door
Ingress is the thing that makes your app reachable from outside the cluster. It’s a set of routing rules: “when someone hits this hostname, send them to this Service.”
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nexus-repository-manager
namespace: nexus
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "0" # unlimited uploads
spec:
ingressClassName: nginx
rules:
- host: nexus-falconstaging.mai.microsoft.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nexus-repository-manager
port:
number: 8081
Read it like English: “When someone requests nexus-falconstaging.mai.microsoft.com, send all traffic (/) to the nexus-repository-manager Service on port 8081.”
But Ingress is just a config – it doesn’t do the actual routing. Something needs to read these rules and act on them. That’s the Ingress Controller.
The Ingress Controller – who’s actually listening
The Ingress Controller is a pod running in your cluster (usually nginx or Traefik) that watches for Ingress resources and configures itself accordingly. Think of it as a reverse proxy that auto-configures based on your YAML.
Browser → Load Balancer (Azure/AWS) → Ingress Controller (nginx pod)
│
├── host: nexus.domain.com → nexus Service
├── host: neptune.domain.com → neptune Service
└── host: grafana.domain.com → grafana Service
One Ingress Controller handles all your apps. Each Ingress resource adds a new routing rule. You don’t set up the controller – your cluster admin does. You just create Ingress resources and the controller picks them up.
Layer 3: DNS – names to numbers
Ingress gives you a hostname rule, but someone still needs to tell the internet “this hostname maps to this IP address.” That’s DNS.
DNS (Domain Name System) is the internet’s phone book. Your browser asks “what’s the IP for nexus-falconstaging.mai.microsoft.com?” and DNS responds with the load balancer’s IP.
Browser: "What's nexus-falconstaging.mai.microsoft.com?"
→ DNS: "It's 20.42.xxx.xxx" (the load balancer IP)
→ Browser connects to that IP
→ Load balancer forwards to Ingress Controller
→ Ingress Controller routes to your Service
→ Service routes to your Pod
Who creates DNS records?
Two options:
Manual: Someone creates an A record in your DNS provider (Azure DNS, Route53, Cloudflare) pointing the hostname at your cluster’s load balancer IP. You ask your IT team or cloud admin to do this.
Automatic (external-dns): A tool called external-dns runs in your cluster, watches for Ingress resources, and automatically creates DNS records. Create an Ingress with a hostname, external-dns sees it, creates the DNS record. Delete the Ingress, external-dns deletes the record.
If your cluster has external-dns configured, you don’t need to do anything – just create the Ingress and the DNS record appears. That’s how most production clusters work.
How to tell which one your cluster uses: If other apps in your cluster already have working hostnames (like neptune-falconstaging.mai.microsoft.com), external-dns is almost certainly running. New hostnames under the same domain should just work.
Layer 4: TLS – the lock icon
TLS (Transport Layer Security) is what puts the “S” in HTTPS. It does two things:
- Encrypts traffic between browser and server (nobody can sniff your data)
- Verifies identity (you’re really talking to your server, not an imposter)
TLS uses certificates – a file that says “I am nexus-falconstaging.mai.microsoft.com and here’s cryptographic proof.” Certificates are issued by a Certificate Authority (CA) that browsers trust.
# In your Ingress:
spec:
tls:
- hosts:
- nexus-falconstaging.mai.microsoft.com
secretName: nexus-tls # Kubernetes Secret containing the cert
Where do certificates come from?
cert-manager: A tool running in your cluster that automatically requests and renews certificates from Let’s Encrypt (free, public CA). You create an Ingress with a TLS section, cert-manager sees it, gets a cert, stores it as a Kubernetes Secret.
Ingress controller default cert: Some clusters configure the nginx Ingress Controller with a wildcard certificate (e.g., *.mai.microsoft.com). Any hostname under that domain is automatically covered. You set secretName: null or omit it, and the controller uses its default.
Manual: Your IT team provides a certificate file. You create a Kubernetes Secret with it and reference it in your Ingress.
In our case, the cluster uses the ingress controller’s default cert – other apps like Neptune don’t specify a secretName, so neither do we.
Putting it all together
Here’s the complete path from browser to pod:
You type: https://nexus-falconstaging.mai.microsoft.com
1. Browser asks DNS: "What IP is this?"
DNS says: "20.42.xxx.xxx" (load balancer)
2. Browser connects to load balancer on port 443 (HTTPS)
TLS handshake happens (verify cert, encrypt connection)
3. Load balancer forwards to Ingress Controller (nginx pod)
4. Ingress Controller checks hostname against its rules
Matches: nexus-falconstaging → nexus-repository-manager Service
5. Service finds a healthy pod matching its selector
Routes to pod IP 10.244.3.17:8081
6. Pod responds → travels back through the same chain → browser renders page
Every layer you configured – Service, Ingress, DNS, TLS – is one hop in this chain. If any one breaks, the request doesn’t make it through.
Common failure modes
When your URL doesn’t work, it’s always one of these layers:
| Symptom | Broken layer | Check |
|---|---|---|
DNS_PROBE_FINISHED_NXDOMAIN |
DNS | No DNS record exists for hostname |
ERR_CONNECTION_REFUSED |
Load balancer / Ingress Controller | Ingress Controller isn’t running or LB misconfigured |
404 Not Found |
Ingress rules | Hostname doesn’t match any Ingress |
502 Bad Gateway |
Service → Pod | Pod is down or Service selector doesn’t match |
SSL certificate problem |
TLS | Wrong cert, expired cert, or self-signed |
Debugging order
Go from bottom to top – if the pod isn’t running, nothing above it matters:
# 1. Is the pod running?
kubectl get pods -n nexus
# 2. Is the Service pointing at it?
kubectl get endpoints -n nexus
# 3. Is the Ingress configured?
kubectl get ingress -n nexus
# 4. Does DNS resolve?
nslookup nexus-falconstaging.mai.microsoft.com
# 5. Is TLS working?
curl -v https://nexus-falconstaging.mai.microsoft.com
The “I just want a URL” cheat sheet
If you’re deploying an app and just want a working URL, here’s all you need in your Helm values:
ingress:
enabled: true
ingressClassName: nginx # your cluster's ingress class
hostRepo: myapp-falconstaging.mai.microsoft.com # follow your cluster's naming pattern
annotations:
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
tls:
- hosts:
- myapp-falconstaging.mai.microsoft.com
Find the pattern by looking at how other apps in the same cluster configure their ingress. Copy their structure, change the hostname. That’s it.
The layers still exist underneath – Service, Ingress Controller, DNS, TLS – but if your cluster is already running other apps with working URLs, all that infrastructure is in place. You’re just adding another routing rule on top.