Kubernetes — Service + Ingress

A ClusterIP Service that exposes your Deployment pods internally, paired with an Nginx Ingress resource that routes external HTTPS traffic to it — with automatic TLS certificate provisioning via cert-manager and Let's Encrypt.

🛠 Paste this YAML into the validator to check it instantly.

Open Validator →

Overview

In Kubernetes, a Service gives your pods a stable network identity. Pods come and go during rolling updates or scaling events, and their IP addresses change every time. A Service provides a single, stable virtual IP (called a ClusterIP) and DNS name that other workloads inside the cluster can use to reach your application — regardless of which pod actually handles the request. The Service uses label selectors to identify the pods it should route traffic to, exactly like the Deployment's selector.matchLabels.

An Ingress is an API object that defines how external HTTP and HTTPS traffic reaches Services inside the cluster. Rather than exposing each Service with its own cloud load balancer (which is expensive), the Ingress routes traffic from a single entry point — the Nginx Ingress Controller — to the correct Service based on hostname and path rules. This file combines both resources using YAML's multi-document separator ---, so you can apply them both with a single kubectl apply -f service-ingress.yaml command.

This example requires the Nginx Ingress Controller and cert-manager to be installed in your cluster. Install them with: kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml and refer to the cert-manager documentation for installation steps.

Full YAML Copy-paste ready

service-ingress.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-app
  namespace: default
spec:
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app
  namespace: default
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  tls:
    - hosts:
        - example.com
      secretName: my-app-tls
  rules:
    - host: example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-app
                port:
                  number: 80

Key sections explained

The --- multi-document separator

YAML supports multiple documents in a single file, separated by --- (three dashes on their own line). Kubernetes uses this to allow you to group related resources together in one file — in this case the Service and the Ingress that work together to expose the same application. When you run kubectl apply -f service-ingress.yaml, both resources are created or updated in the same operation. There is no limit to how many documents you can include; large teams often have a single file per application that contains its Deployment, Service, Ingress, ConfigMap, and HPA all in one place.

ClusterIP vs NodePort vs LoadBalancer

Kubernetes Services come in three main types. ClusterIP (the default, used here) creates an internal virtual IP that is only reachable from within the cluster. It is the right choice when another Kubernetes resource — like an Ingress Controller — will handle the external-facing traffic. NodePort exposes the Service on a static port on every node's external IP, which is useful for testing but not recommended for production because it bypasses the Ingress layer. LoadBalancer provisions an external cloud load balancer (on AWS, GCP, Azure, etc.) and is appropriate when you need a dedicated IP per Service — but at significant cost. For most applications, the ClusterIP + Ingress pattern gives you one load balancer shared across all your services, which is both cheaper and easier to manage.

How Ingress routes traffic to the Service

The Ingress rules section maps hostnames and paths to backend Services. In this manifest, any request for example.com matching path prefix / is forwarded to the my-app Service on port 80. The Service then load-balances the request across all healthy pods matching app: my-app. The pathType: Prefix means the rule matches /, /api, /static/logo.png, and any other path — it is a catch-all. Use pathType: Exact if you need a specific path to match without sub-paths.

Multiple rules can be added in the same Ingress to route different hostnames or path prefixes to different Services, enabling you to host many microservices behind a single entry point. For example, you could route /api to an API service and / to a frontend service.

cert-manager annotation for automatic TLS

The annotation cert-manager.io/cluster-issuer: letsencrypt-prod instructs cert-manager to watch this Ingress resource and automatically obtain a TLS certificate from Let's Encrypt for the listed domains. cert-manager uses the HTTP-01 or DNS-01 ACME challenge to prove domain ownership, then stores the resulting certificate in a Kubernetes Secret named by spec.tls[].secretName — in this case my-app-tls. The Nginx Ingress Controller reads that Secret and serves HTTPS traffic using the certificate. cert-manager also handles renewal automatically, so you never need to think about certificate expiry.

The nginx.ingress.kubernetes.io/ssl-redirect: "true" annotation tells the Nginx controller to issue a 308 redirect for any HTTP request to the same URL over HTTPS. Without this, users who visit the plain HTTP URL would not be redirected and might interact with the site insecurely. Note that the value must be a string ("true") not a boolean, because Kubernetes annotations are always strings.

Tips & variations

Route multiple paths to different services

Add additional path entries under http.paths to route different URL prefixes to different backend services — for example, /api to an API service and / to a frontend:

multi-path ingress snippet
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api-service
                port:
                  number: 80
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend-service
                port:
                  number: 80

Use a staging issuer for testing

Let's Encrypt has strict rate limits on production certificate issuance. While testing your cert-manager setup, replace letsencrypt-prod with letsencrypt-staging in the annotation. The staging issuer uses an untrusted root CA, so browsers will show a warning — but it lets you iterate without hitting rate limits. Switch back to letsencrypt-prod when you are confident everything works.

Manage with Helm

The Helm values.yaml example shows how to manage the Ingress configuration — including TLS hosts, path rules, and annotations — through a values.yaml file, making it easy to vary settings across environments without editing the raw manifest.