Skip to content

Exposing Applications to the Internet

This guide explains how to expose your application running in the cluster to the public internet securely.

Conceptual Overview

This is a practical, step-by-step guide. For a deeper understanding of the underlying Kubernetes resources like Services and Ingress, please see the Networking in Kubernetes documentation.

The platform provides an ingress stack built on modern, cloud-native tools:

  • Ingress Controller: Contour is used to manage incoming traffic. It supports the standard Ingress and Gateway API resources, as well as its own custom HTTPProxy resource.
  • Automatic TLS Certificates: cert-manager is integrated to automatically provision and renew TLS certificates from Let's Encrypt, enabling HTTPS by default.
  • Automatic DNS Records: ExternalDNS automatically creates DNS A-records for your services, pointing your hostname to the cluster's public IP address.

This guide will walk you through the recommended methods of using HTTPProxy or the modern Kubernetes Gateway API to expose your service with secure, automated HTTPS.

This is a standard, recommended workflow for exposing any web-based application. It involves creating two resources: one to request the TLS certificate, and one to configure the traffic routing.

Step 1: Request a TLS Certificate

First, create a Certificate resource. This tells cert-manager to obtain a TLS certificate from Let's Encrypt for your domain and store it in a Kubernetes Secret.

# my-app-certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: my-app-tls-cert
spec:
  # The name of the Kubernetes Secret where the certificate will be stored.
  # This is what your HTTPProxy will reference.
  secretName: my-app-tls-secret

  # The domain name for your application.
  dnsNames:
  - my-app.your-domain.com

  # References the platform's pre-configured Let's Encrypt issuer.
  issuerRef:
    name: letsencrypt-cluster-issuer
    kind: ClusterIssuer

Step 2: Create an HTTPProxy Resource

Next, create an HTTPProxy resource. This tells the Contour ingress controller how to route traffic for your domain, where to send it, and which TLS secret to use for HTTPS.

# my-app-httpproxy.yaml
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: my-app-httpproxy
spec:
  virtualhost:
    # This must match one of the dnsNames from your Certificate resource.
    fqdn: my-app.your-domain.com
    tls:
      # This must match the secretName from your Certificate resource.
      secretName: my-app-tls-secret
  routes:
    - conditions:
      - prefix: / # Route all traffic from the root path.
      services:
        - name: my-app-service # The name of your application's Service.
          port: 8080 # The port number of your Service.

When you deploy these two resources, the following happens automatically:

  1. ExternalDNS creates a DNS record for my-app.your-domain.com.
  2. Cert-manager validates that it controls the DNS and gets a certificate from Let's Encrypt.
  3. Cert-manager saves the certificate and key in a Secret named my-app-tls-secret.
  4. Contour detects the HTTPProxy and the Secret, and begins serving encrypted traffic for your domain.

Step 3: Allow Ingress Traffic to Your Pod

Finally, ensure your application's pod has the correct label to allow the ingress controller to send traffic to it. This is a mandatory security requirement.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    metadata:
      labels:
        # This label is required to allow traffic from the ingress controller.
        netic.dk/network-ingress: "contour"
    # ... rest of your pod spec

Modern Method: Secure HTTPS with Gateway API

As a modern alternative to Ingress and HTTPProxy, the platform fully supports the official Kubernetes Gateway API. This is a powerful, standardized, and expressive API for managing traffic routing. It separates the responsibility of the cluster operator (who manages the Gateway) from the application developer (who manages HTTPRoute resources).

Separation of Roles

You do not need to create the Gateway resource yourself. The platform team manages the Gateway as a piece of shared infrastructure, which represents the central, secure entry point to the cluster.

Your responsibility is simply to create an HTTPRoute to tell the existing Gateway how to route traffic to your specific application.

This method involves creating a single HTTPRoute resource. TLS is handled automatically at the Gateway level.

Step 1: Create an HTTPRoute Resource

The HTTPRoute resource defines how to match and route traffic from the Gateway to your backend Service.

# my-app-httproute.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: my-app-httproute
spec:
  # This section attaches your route to the platform-provided Gateway.
  parentRefs:
  - name: contour
    namespace: projectcontour
    kind: Gateway

  # The domain name for your application.
  # ExternalDNS will automatically create a record for this.
  hostnames:
  - "my-app.your-domain.com"

  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: my-app-service
      port: 8080

When you deploy this resource, the following happens automatically:

  1. ExternalDNS creates a DNS record for my-app.your-domain.com.
  2. The Gateway resource is already configured with a TLS listener. cert-manager monitors it, sees your new hostname, and automatically provisions a TLS certificate for it.
  3. Contour configures its underlying Envoy proxy to route traffic for my-app.your-domain.com to your service, with HTTPS enabled.

Step 2: Allow Ingress Traffic to Your Pod

Just like with HTTPProxy, you must label your pod to allow the ingress controller to send traffic to it.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    metadata:
      labels:
        # This label is required to allow traffic from the ingress controller.
        netic.dk/network-ingress: "contour"
    # ... rest of your pod spec

Advanced Configurations with HTTPProxy

HTTPProxy makes advanced configurations simple and declarative without needing complex annotations.

Enabling WebSockets

To enable WebSockets for a specific route, add the enableWebsockets: true field to the route.

spec:
  # ... virtualhost config
  routes:
    - conditions:
      - prefix: / # Enable websockets for the root path
      services:
        - name: my-websocket-service
          port: 8080
      enableWebsockets: true

Enabling gRPC

To route gRPC traffic, simply specify h2c or h2 (for TLS) as the protocol on your service. Contour will handle the rest.

spec:
  # ... virtualhost config (with TLS for h2)
  routes:
    - conditions:
      - prefix: /
      services:
        - name: my-grpc-service
          port: 9000
          protocol: h2 # Use 'h2' for gRPC over TLS

Alternative Method: Using the Standard Ingress Resource

Legacy Method

While the standard Kubernetes Ingress resource is supported, we recommend using HTTPProxy or Gateway API for their superior features, safety, and validation. The following examples are provided for reference.

When using the Ingress resource, cert-manager is controlled via annotations directly on the Ingress object, and a separate Certificate resource is not needed.

Minimal HTTP Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress-http
spec:
  rules:
    - host: my-app.your-domain.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-app-service
                port:
                  name: http

Minimal HTTPS Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress-https
  annotations:
    # Tell cert-manager to use the letsencrypt issuer for this ingress.
    cert-manager.io/cluster-issuer: letsencrypt
spec:
  tls:
    - hosts:
      - my-app.your-domain.com
      # cert-manager will create/update a secret with this name.
      secretName: my-app-tls-secret-from-ingress
  rules:
    - host: my-app.your-domain.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-app-service
                port:
                  name: http