---
# === IDENTITY ===
id: software/devops/k8s-deployment-service-ingress/2026
canonical_question: "Kubernetes reference: basic Deployment + Service + Ingress"
aliases:
  - "k8s deployment service ingress YAML"
  - "Kubernetes expose application externally"
  - "kubectl create deployment service ingress"
  - "Kubernetes networking basics deployment to ingress"
  - "how to deploy and expose an app on Kubernetes"
  - "k8s service types ClusterIP NodePort LoadBalancer"
  - "Kubernetes Ingress TLS configuration"
  - "Kubernetes deployment rolling update YAML"
entity_type: software_reference
domain: software > devops > Kubernetes Deployment + Service + Ingress
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === VERIFICATION ===
last_verified: 2026-02-27
confidence: 0.94
version: 1.0
first_published: 2026-02-27

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "Ingress API frozen (networking.k8s.io/v1 GA since k8s 1.19); community Ingress NGINX retiring March 2026; Gateway API recommended for new clusters"
  next_review: 2026-08-26
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "An Ingress controller MUST be deployed in the cluster for Ingress resources to have any effect -- creating an Ingress alone does nothing"
  - "Always use apps/v1 for Deployments and networking.k8s.io/v1 for Ingress -- older beta APIs are removed in k8s 1.22+"
  - "spec.selector in Deployment is immutable after creation -- you cannot change it without deleting and recreating the Deployment"
  - "Never use the :latest image tag in production -- pin to specific versions for reproducibility and rollback"
  - "Resource requests and limits MUST be set on production Pods -- omitting them risks OOMKills and noisy-neighbor issues"
  - "The Ingress API is frozen -- no new features will be added; use Gateway API for new greenfield projects"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Need to configure Gateway API (HTTPRoute, Gateway) instead of Ingress"
    use_instead: "software/devops/k8s-gateway-api/2026"
  - condition: "Need Helm chart packaging or Kustomize overlays"
    use_instead: "software/devops/helm-kustomize-packaging/2026"
  - condition: "Need to configure HPA (Horizontal Pod Autoscaler) or VPA"
    use_instead: "software/devops/k8s-autoscaling/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: "exposure_type"
    question: "How do you need to expose your application?"
    type: choice
    options: ["Internal only (ClusterIP)", "Direct node access (NodePort)", "Cloud load balancer (LoadBalancer)", "HTTP/HTTPS routing (Ingress)", "Not sure"]
  - key: "tls_required"
    question: "Do you need TLS/HTTPS termination?"
    type: choice
    options: ["Yes, with cert-manager (Let's Encrypt)", "Yes, with pre-existing certificate", "No, HTTP only"]
  - key: "ingress_controller"
    question: "Which Ingress controller are you using?"
    type: choice
    options: ["NGINX Ingress Controller", "Traefik", "HAProxy", "AWS ALB Ingress", "GKE Ingress (GCE)", "Other/Not sure"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/devops/k8s-deployment-service-ingress/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-02-27)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/devops/k8s-gateway-api/2026"
      label: "Kubernetes Gateway API Reference"
    - id: "software/devops/k8s-autoscaling/2026"
      label: "Kubernetes Autoscaling (HPA/VPA)"
  solves: []
  alternative_to:
    - id: "software/devops/docker-compose-reference/2026"
      label: "Docker Compose Reference"
  often_confused_with:
    - id: "software/devops/k8s-gateway-api/2026"
      label: "Kubernetes Gateway API (successor to Ingress)"

# === SOURCES (7 authoritative sources) ===
sources:
  - id: src1
    title: "Deployments"
    author: Kubernetes Documentation
    url: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
    type: official_docs
    published: 2025-12-01
    reliability: authoritative
  - id: src2
    title: "Service"
    author: Kubernetes Documentation
    url: https://kubernetes.io/docs/concepts/services-networking/service/
    type: official_docs
    published: 2025-12-01
    reliability: authoritative
  - id: src3
    title: "Ingress"
    author: Kubernetes Documentation
    url: https://kubernetes.io/docs/concepts/services-networking/ingress/
    type: official_docs
    published: 2025-12-01
    reliability: authoritative
  - id: src4
    title: "7 Common Kubernetes Pitfalls (and How I Learned to Avoid Them)"
    author: Kubernetes Blog
    url: https://kubernetes.io/blog/2025/10/20/seven-kubernetes-pitfalls-and-how-to-avoid/
    type: technical_blog
    published: 2025-10-20
    reliability: high
  - id: src5
    title: "Annotated Ingress resource -- cert-manager Documentation"
    author: cert-manager
    url: https://cert-manager.io/docs/usage/ingress/
    type: official_docs
    published: 2025-06-01
    reliability: high
  - id: src6
    title: "Kubernetes Ingress vs Gateway API: What to Use in 2026"
    author: OneUptime
    url: https://oneuptime.com/blog/post/2026-02-20-kubernetes-ingress-vs-gateway-api/view
    type: technical_blog
    published: 2026-02-20
    reliability: moderate_high
  - id: src7
    title: "Kubernetes Deployment Antipatterns -- part 1"
    author: Codefresh
    url: https://codefresh.io/blog/kubernetes-antipatterns-1/
    type: technical_blog
    published: 2025-03-15
    reliability: moderate_high
---

# Kubernetes Deployment + Service + Ingress Reference

## TL;DR

- **Bottom line**: A Deployment manages Pod replicas with rolling updates, a Service provides stable networking to those Pods, and an Ingress routes external HTTP/HTTPS traffic to Services -- these three resources form the standard pattern for exposing applications on Kubernetes.
- **Key tool/command**: `kubectl apply -f deployment.yaml -f service.yaml -f ingress.yaml`
- **Watch out for**: Creating an Ingress without an Ingress controller deployed -- the resource will exist but do nothing.
- **Works with**: Kubernetes 1.19+ (apps/v1 Deployment, networking.k8s.io/v1 Ingress). Ingress API is frozen; Gateway API is the recommended successor for new projects.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- An Ingress controller MUST be deployed in the cluster for Ingress resources to have any effect -- creating an Ingress alone does nothing
- Always use apps/v1 for Deployments and networking.k8s.io/v1 for Ingress -- older beta APIs are removed in k8s 1.22+
- spec.selector in Deployment is immutable after creation -- you cannot change it without deleting and recreating the Deployment
- Never use the :latest image tag in production -- pin to specific versions for reproducibility and rollback
- Resource requests and limits MUST be set on production Pods -- omitting them risks OOMKills and noisy-neighbor issues
- The Ingress API is frozen -- no new features will be added; use Gateway API for new greenfield projects

## Quick Reference

### Resource Type Overview

| Resource | API Version | Purpose | Key Spec Fields | Namespaced |
|---|---|---|---|---|
| Deployment | apps/v1 | Manages Pod replicas with rolling updates | replicas, selector, template, strategy | Yes |
| ReplicaSet | apps/v1 | Ensures N identical Pods run (managed by Deployment) | replicas, selector, template | Yes |
| Service | v1 | Stable network endpoint for a set of Pods | type, selector, ports, clusterIP | Yes |
| Ingress | networking.k8s.io/v1 | HTTP/HTTPS routing from external to Services | ingressClassName, rules, tls | Yes |
| IngressClass | networking.k8s.io/v1 | Defines which controller handles an Ingress | controller, parameters | No |
| ConfigMap | v1 | Non-sensitive configuration data for Pods | data, binaryData | Yes |
| Secret | v1 | Sensitive data (passwords, TLS certs, tokens) | data, type | Yes |
| HorizontalPodAutoscaler | autoscaling/v2 | Auto-scales Deployment replicas by metrics | minReplicas, maxReplicas, metrics | Yes |

### Service Type Comparison

| Type | Scope | Port Range | Load Balancing | Cost | Use Case |
|---|---|---|---|---|---|
| ClusterIP (default) | Internal only | Any | kube-proxy (iptables/IPVS) | Free | Microservice-to-microservice communication |
| NodePort | External via node IP | 30000-32767 | None (single node hit) | Free | Dev/testing, on-prem without LB |
| LoadBalancer | External via cloud LB | Any | Cloud provider LB | $$$ per LB | Production single-service exposure |
| ExternalName | DNS alias (CNAME) | N/A | N/A | Free | Proxy to external services (e.g., RDS) |
| Headless (clusterIP: None) | Internal DNS only | Any | None (direct Pod IPs) | Free | StatefulSets, service discovery |

### Ingress Path Types

| pathType | Matching Behavior | Example Path | Matches /foo? | Matches /foo/bar? |
|---|---|---|---|---|
| Exact | Case-sensitive exact match | /foo | Yes | No |
| Prefix | Path prefix match (segment boundary) | /foo | Yes | Yes |
| ImplementationSpecific | Controller-dependent | /foo | Depends | Depends |

## Decision Tree

> Full script: [decision-tree.txt](scripts/decision-tree.txt) (28 lines)

```
START: How should external traffic reach your application?
|
+-- Internal microservice only (no external traffic)?
|   +-- YES --> Use Service type: ClusterIP (default)
|   +-- NO  |
# ... (see full script)
```

## Step-by-Step Guide

### 1. Create a Deployment

A Deployment declares the desired state for your application Pods, including the container image, replicas, and update strategy. [src1]

```yaml
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp          # Immutable after creation
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1         # Max extra Pods during update
      maxUnavailable: 0   # Zero downtime: old Pod removed only after new is Ready
  template:
    metadata:
      labels:
        app: myapp        # Must match selector.matchLabels
    spec:
      containers:
      - name: myapp
        image: myapp:1.2.3          # Pin version, never use :latest
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 500m
            memory: 256Mi
        readinessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 10
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 20
```

**Verify**: `kubectl apply -f deployment.yaml && kubectl rollout status deployment/myapp` --> `deployment "myapp" successfully rolled out`

### 2. Create a Service

A Service provides a stable ClusterIP and DNS name (`myapp.default.svc.cluster.local`) for the set of Pods matched by the selector. [src2]

```yaml
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  type: ClusterIP               # Default; change to NodePort/LoadBalancer if needed
  selector:
    app: myapp                   # Must match Pod labels from Deployment
  ports:
  - name: http
    protocol: TCP
    port: 80                     # Service port (what clients connect to)
    targetPort: 8080             # Pod port (where container listens)
```

**Verify**: `kubectl get svc myapp` --> Shows ClusterIP assigned. `kubectl run curl --image=curlimages/curl --rm -it -- curl http://myapp` --> Gets response from app.

### 3. Create an Ingress

An Ingress defines HTTP routing rules that map external hostnames and paths to backend Services. Requires an Ingress controller to be running. [src3]

```yaml
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"  # Optional: auto-TLS
spec:
  ingressClassName: nginx        # Must match your installed Ingress controller
  tls:
  - hosts:
    - myapp.example.com
    secretName: myapp-tls        # cert-manager creates this Secret
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: myapp          # References the Service name
            port:
              number: 80         # References the Service port
```

**Verify**: `kubectl get ingress myapp` --> Shows ADDRESS and HOST. `curl -H "Host: myapp.example.com" http://<INGRESS-IP>/` --> Gets response.

### 4. Configure cert-manager for automatic TLS

cert-manager watches for Ingress resources with the `cert-manager.io/cluster-issuer` annotation and automatically provisions TLS certificates. [src5]

```yaml
# cluster-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v2.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
    - http01:
        ingress:
          ingressClassName: nginx
```

**Verify**: `kubectl get certificate -A` --> Shows certificate status as `Ready: True`. `kubectl describe secret myapp-tls` --> Shows tls.crt and tls.key data.

### 5. Perform a rolling update

Update the container image to trigger a zero-downtime rolling update. [src1]

```bash
# Update the image (triggers rolling update)
kubectl set image deployment/myapp myapp=myapp:1.3.0

# Watch rollout progress
kubectl rollout status deployment/myapp

# Roll back if something goes wrong
kubectl rollout undo deployment/myapp
```

**Verify**: `kubectl rollout history deployment/myapp` --> Shows revision history. `kubectl get pods -l app=myapp` --> All Pods running new version.

## Code Examples

### Complete 3-file deployment: Deployment + ClusterIP Service + Ingress

> Full script: [complete-deployment-service-ingress.yaml](scripts/complete-deployment-service-ingress.yaml) (62 lines)

```yaml
# Input:  Container image myapp:1.2.3 listening on port 8080
# Output: App accessible at https://myapp.example.com via Ingress
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels: { app: myapp }
spec:
  replicas: 3
  selector: { matchLabels: { app: myapp } }
  template:
    metadata: { labels: { app: myapp } }
    spec:
      containers:
      - name: myapp
        image: myapp:1.2.3
        ports: [{ containerPort: 8080 }]
        resources:
          requests: { cpu: 100m, memory: 128Mi }
          limits: { cpu: 500m, memory: 256Mi }
        readinessProbe:
          httpGet: { path: /healthz, port: 8080 }
---
apiVersion: v1
kind: Service
metadata: { name: myapp }
spec:
  selector: { app: myapp }
  ports: [{ port: 80, targetPort: 8080 }]
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls: [{ hosts: [myapp.example.com], secretName: myapp-tls }]
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service: { name: myapp, port: { number: 80 } }
```

### NodePort Service (for on-prem / no cloud LB)

```yaml
# Input:  Internal app that needs direct external access without cloud LB
# Output: App accessible at <NodeIP>:30080
apiVersion: v1
kind: Service
metadata:
  name: myapp-nodeport
spec:
  type: NodePort
  selector:
    app: myapp
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 30080          # Optional: auto-assigned from 30000-32767 if omitted
```

### LoadBalancer Service (for cloud environments)

```yaml
# Input:  App that needs a dedicated external IP on cloud (AWS/GCP/Azure)
# Output: App accessible at cloud-provisioned external IP on port 443
apiVersion: v1
kind: Service
metadata:
  name: myapp-lb
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"  # AWS: use NLB
spec:
  type: LoadBalancer
  selector:
    app: myapp
  ports:
  - name: https
    port: 443
    targetPort: 8080
```

### Multi-service Ingress with path-based routing

```yaml
# Input:  Frontend (/) and API (/api) running as separate Deployments
# Output: Single domain routes to different backend Services by path
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: multi-service
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  ingressClassName: nginx
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service: { name: frontend, port: { number: 80 } }
      - path: /api(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service: { name: api, port: { number: 80 } }
```

## Anti-Patterns

### Wrong: Missing resource requests and limits

```yaml
# BAD -- no resource requests/limits; Pod can OOMKill others or get evicted first
spec:
  containers:
  - name: myapp
    image: myapp:1.2.3
    # No resources block -- Kubernetes treats this as BestEffort QoS
```

### Correct: Always set resource requests and limits

```yaml
# GOOD -- explicit resource boundaries; Pod gets Burstable or Guaranteed QoS
spec:
  containers:
  - name: myapp
    image: myapp:1.2.3
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: 500m
        memory: 256Mi
```

### Wrong: Using :latest image tag

```yaml
# BAD -- :latest is mutable; rollback is impossible, and you don't know what's deployed
spec:
  containers:
  - name: myapp
    image: myapp:latest    # What version is this? Nobody knows.
```

### Correct: Pin image to specific version or digest

```yaml
# GOOD -- immutable version tag; rollback works, audit trail is clear
spec:
  containers:
  - name: myapp
    image: myapp:1.2.3
    # Even better: use image digest
    # image: myapp@sha256:abc123...
```

### Wrong: No readiness probe with rolling update

```yaml
# BAD -- without readinessProbe, traffic is sent to Pods before they're ready
spec:
  containers:
  - name: myapp
    image: myapp:1.2.3
    # No readinessProbe -- kube-proxy adds Pod to Service endpoints immediately
```

### Correct: Configure readiness probe

```yaml
# GOOD -- Pod only receives traffic after probe succeeds
spec:
  containers:
  - name: myapp
    image: myapp:1.2.3
    readinessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 10
      failureThreshold: 3
```

### Wrong: Selector mismatch between Deployment and Service

```yaml
# BAD -- Service selector doesn't match Deployment Pod labels; no traffic reaches Pods
# Deployment labels: app: myapp
# Service selector:
spec:
  selector:
    app: my-app            # Typo! "my-app" != "myapp"
  ports:
  - port: 80
    targetPort: 8080
```

### Correct: Ensure labels match exactly

```yaml
# GOOD -- Service selector matches Deployment Pod template labels exactly
# Deployment template.metadata.labels: app: myapp
# Service selector:
spec:
  selector:
    app: myapp             # Exact match with Pod labels
  ports:
  - port: 80
    targetPort: 8080
```

### Wrong: Ingress without ingressClassName

```yaml
# BAD -- omitting ingressClassName may cause the wrong controller (or none) to handle it
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp
spec:
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service: { name: myapp, port: { number: 80 } }
```

### Correct: Always specify ingressClassName

```yaml
# GOOD -- explicitly declares which controller handles this Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp
spec:
  ingressClassName: nginx    # Matches IngressClass resource in cluster
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service: { name: myapp, port: { number: 80 } }
```

## Common Pitfalls

- **Selector mismatch between Deployment, Service, and Ingress**: Pod labels, Service selector, and Ingress backend service name must all align. A single typo means zero traffic. Fix: Use `kubectl get endpoints myapp` to verify the Service has Pod IPs listed. [src2]
- **Ingress created but no controller running**: The Ingress resource sits idle with no ADDRESS assigned. Fix: Install an Ingress controller first (`helm install ingress-nginx ingress-nginx/ingress-nginx`). Check with `kubectl get ingressclass`. [src3]
- **TLS secret not found or not in same namespace**: Ingress TLS references a Secret that doesn't exist or is in the wrong namespace. Fix: Ensure the Secret is in the same namespace as the Ingress. With cert-manager, verify the ClusterIssuer is configured. [src5]
- **NodePort range conflict**: NodePort must be in 30000-32767. Specifying a port outside this range or one already in use fails silently. Fix: Omit nodePort to let Kubernetes auto-assign, or check existing allocations with `kubectl get svc --all-namespaces -o jsonpath='{.items[*].spec.ports[*].nodePort}'`. [src2]
- **Rolling update blocked by PodDisruptionBudget**: A PDB with maxUnavailable=0 combined with maxSurge=0 creates a deadlock. Fix: Set maxSurge >= 1 when maxUnavailable is 0, or adjust the PDB. [src1]
- **Image pull errors (ErrImagePull / ImagePullBackOff)**: Wrong image name, tag, or missing imagePullSecrets for private registries. Fix: `kubectl describe pod <name>` to see the exact error; verify image exists and auth is configured. [src4]
- **Resource limits too low causing OOMKilled**: Container exceeds memory limit and is killed. Fix: Monitor actual usage with `kubectl top pod`, then set limits 20-50% above observed peak. [src4]
- **Ingress path ordering**: More specific paths must come before less specific ones in some controllers. Fix: Order paths from most specific to least specific (e.g., `/api/v1` before `/api` before `/`). [src3]

## Diagnostic Commands

```bash
# Check Deployment rollout status
kubectl rollout status deployment/myapp

# View rollout history and revisions
kubectl rollout history deployment/myapp

# Check Service endpoints (empty = selector mismatch)
kubectl get endpoints myapp

# Verify Ingress has an ADDRESS assigned (empty = no controller)
kubectl get ingress myapp

# Debug Pod startup issues
kubectl describe pod -l app=myapp
kubectl logs -l app=myapp --tail=50

# Check which Ingress controller is installed
kubectl get ingressclass

# Test Service connectivity from inside the cluster
kubectl run curl --image=curlimages/curl --rm -it -- curl http://myapp.default.svc.cluster.local

# Check Ingress controller logs for routing errors
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx --tail=100

# View resource usage (requires metrics-server)
kubectl top pods -l app=myapp

# Check events for scheduling/pull/probe failures
kubectl get events --sort-by=.metadata.creationTimestamp --field-selector reason!=Pulled
```

## Version History & Compatibility

| API/Resource | Version | Status | Key Change |
|---|---|---|---|
| Deployment (apps/v1) | k8s 1.9+ | Stable (GA) | apps/v1beta2 removed in 1.16; apps/v1beta1 removed in 1.16 |
| Service (v1) | k8s 1.0+ | Stable (GA) | Dual-stack IPv4/IPv6 GA in 1.23 |
| Ingress (networking.k8s.io/v1) | k8s 1.19+ | Frozen (GA) | extensions/v1beta1 removed in 1.22; API frozen, no new features |
| IngressClass | k8s 1.18+ | Stable (GA) | Required to specify which controller handles Ingress |
| Gateway API (v1) | k8s 1.26+ | Stable (GA) | HTTPRoute, Gateway, GatewayClass; recommended successor to Ingress |
| Gateway API (v1alpha2) | k8s 1.24+ | Experimental | TCPRoute, TLSRoute, GRPCRoute |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Deploying stateless HTTP/HTTPS applications | Running stateful workloads (databases, queues) | StatefulSet + Headless Service |
| Need rolling updates with zero downtime | Need batch processing or scheduled jobs | Job / CronJob |
| Routing external HTTP traffic by host/path | Exposing non-HTTP TCP/UDP services externally | Service type: LoadBalancer or Gateway API TCPRoute |
| Existing cluster already uses Ingress | Starting a new greenfield project in 2026+ | Gateway API (HTTPRoute + Gateway) |
| Single-team, simple routing rules | Need cross-team role separation for networking | Gateway API (role-oriented model) |

## Important Caveats

- The Ingress API is frozen and will receive no new features -- only bug fixes and security patches; plan migration to Gateway API for new projects [src3] [src6]
- Community Ingress NGINX controller is retiring March 2026 -- no releases, bug fixes, or security updates after retirement; migrate to NGINX Inc's controller, Traefik, or Gateway API [src6]
- Each LoadBalancer Service provisions a separate cloud load balancer (costs $15-25/month on AWS/GCP) -- use a single Ingress controller with ClusterIP Services to consolidate [src2]
- maxSurge and maxUnavailable cannot both be 0 -- Kubernetes rejects the configuration; set at least one to a positive value [src1]
- Ingress TLS only terminates HTTPS at the Ingress controller -- traffic from controller to Service/Pod is HTTP by default; use service mesh or Pod-level TLS for end-to-end encryption
- Labels in spec.selector.matchLabels are immutable on Deployments -- changing them requires deleting and recreating the Deployment; use annotations or additional labels in template.metadata.labels for mutable metadata [src1]

## Related Units
<!-- Generated from related_kos frontmatter -->

- [Kubernetes Gateway API Reference](/software/devops/k8s-gateway-api/2026)
- [Kubernetes Autoscaling (HPA/VPA)](/software/devops/k8s-autoscaling/2026)
- [Docker Compose Reference](/software/devops/docker-compose-reference/2026)
