Skip to content

TLS Web App

In this tutorial you will deploy a TLS-secured nginx application on a local Kubernetes cluster. You will push a container image to the Local Registry, expose it through Envoy Gateway with HTTPS termination, use cert-manager to issue a self-signed TLS certificate, and rely on MetalLB to assign the gateway an external IP — all without any manual addon installation.

  • kinder installed — see Installation
  • Docker (or Podman) installed and running
  • kubectl installed and on PATH
  • curl installed (HTTPS verification uses the -k flag to accept the self-signed certificate)
Terminal window
kinder create cluster

Expected output:

Creating cluster "kind" ...
✓ Ensuring node image (kindest/node:v1.32.0) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
✓ Installing addons
metallb 1.8s
local-registry 0.3s
envoy-gateway 4.2s
cert-manager 38.1s
Set kubectl context to "kind-kind"

Check that all cert-manager components are running:

Terminal window
kubectl get pods -n cert-manager

Expected output:

NAME READY STATUS RESTARTS AGE
cert-manager-... 1/1 Running 0 60s
cert-manager-cainjector-... 1/1 Running 0 60s
cert-manager-webhook-... 1/1 Running 0 60s

Check that the Envoy Gateway controller is running:

Terminal window
kubectl get pods -n envoy-gateway-system

Expected output:

NAME READY STATUS RESTARTS AGE
envoy-gateway-... 1/1 Running 0 60s

Check that MetalLB is running:

Terminal window
kubectl get pods -n metallb-system

Expected output:

NAME READY STATUS RESTARTS AGE
controller-... 1/1 Running 0 60s
speaker-... 1/1 Running 0 60s

Verify the local registry container is running:

Terminal window
docker ps --filter name=kind-registry

Expected output:

CONTAINER ID IMAGE ... PORTS NAMES
abc123 registry:2 ... 0.0.0.0:5001->5000/tcp kind-registry

Step 3: Push an image to the local registry

Section titled “Step 3: Push an image to the local registry”

Pull the nginx Alpine image, tag it for the local registry, and push it:

Terminal window
docker pull nginx:alpine
docker tag nginx:alpine localhost:5001/myapp:v1
docker push localhost:5001/myapp:v1

Expected output:

v1: digest: sha256:a1b2c3d4e5f6... size: 8192

Apply the Deployment and Service in one step:

Terminal window
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: localhost:5001/myapp:v1
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 80
EOF

Verify the pod is running:

Terminal window
kubectl get pods

Expected output:

NAME READY STATUS RESTARTS AGE
myapp-... 1/1 Running 0 15s

Apply a Certificate resource that cert-manager will fulfill using the pre-installed selfsigned-issuer:

Terminal window
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: myapp-tls
namespace: default
spec:
secretName: myapp-tls
issuerRef:
name: selfsigned-issuer
kind: ClusterIssuer
dnsNames:
- myapp.local
EOF

Wait for the certificate to become ready:

Terminal window
kubectl get certificate myapp-tls

Expected output:

NAME READY SECRET AGE
myapp-tls True myapp-tls 15s

Apply a Gateway that terminates TLS using the certificate secret:

Terminal window
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: myapp-gateway
spec:
gatewayClassName: eg
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- name: myapp-tls
EOF

Wait for MetalLB to assign an external IP and for the gateway to become programmed:

Terminal window
kubectl get gateway myapp-gateway

Expected output:

NAME CLASS ADDRESS PROGRAMMED AGE
myapp-gateway eg 172.20.255.200 True 20s

Route incoming HTTPS traffic to the myapp service:

Terminal window
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: myapp-route
spec:
parentRefs:
- name: myapp-gateway
rules:
- backendRefs:
- name: myapp
port: 80
EOF

Get the gateway’s external IP and send an HTTPS request:

Terminal window
GATEWAY_IP=$(kubectl get gateway myapp-gateway -o jsonpath='{.status.addresses[0].value}')
curl -k https://$GATEWAY_IP

Expected output:

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</html>

The -k flag tells curl to accept the self-signed certificate.

Delete the cluster when you are done:

Terminal window
kinder delete cluster