Web Hosting
Following my previous exercise to expose many web apps behind a single domain with Kind and with K3s, I wanted to try the alternative scenario where each web app is exposed on its own domain, and ingress rules are defined for each workload.
graph LR
client1["HTTP https://**orange**.neoteroi.xyz"] --> orange_ingress["Orange Ingress"]
client2["HTTP https://**teal**.neoteroi.xyz"] --> teal_ingress["Teal Ingress"]
subgraph "Ingress Rules"
direction TB
orange_ingress
teal_ingress
end
subgraph "Servers"
A["Orange Web App"]
B["Teal Web App"]
end
orange_ingress -->| /* | A
teal_ingress -->| /* | B
The single-domain, path-based routing scenario works well in setups involving central
API Gateways or HTTP Proxies used to expose multiple web APIs or applications. In such
cases, I would use ExternalName
services to organize the common ingress rules in a
dedicated namespace, while placing each application in its own separate namespace.
The multi-domain scenario is more common when hosting public websites or applications that need to be accessed directly by users. Each application can have its own domain name, allowing users to access them without going through a central gateway or proxy. In this case, there is no need to use ExternalName services and centralized ingress rules —teams only need to agree on domain names to avoid conflicts. Ingress rules can be defined within each application's namespace.
Example¶
The folder ./examples/08-k3s-multi-domain
contains a working example of the
multi-domain ingress setup, that works well on a default K3s
installation.
To test locally, clone the repository and follow the instructions below.
Requirements¶
Since we are testing locally, set this in your hosts
file:
127.0.0.1 orange.neoteroi.xyz
127.0.0.1 www.orange.neoteroi.xyz
127.0.0.1 teal.neoteroi.xyz
127.0.0.1 www.teal.neoteroi.xyz
On Linux, this file is located at /etc/hosts
.
On Windows, this file is located at C:\Windows\System32\drivers\etc\hosts
.
Deploy¶
From the root of the repository:
cd examples/08-k3s-multi-domain
cd orange
chmod +x deploy.sh
./deploy.sh
cd ../teal
chmod +x deploy.sh
./deploy.sh
The deploy scripts look like this:
NAMESPACE="orange"
kubectl create namespace $NAMESPACE
cd ssl
# Check if certificate file doesn't exist, generate it
if [ ! -f "$NAMESPACE-neoteroi-xyz-tls.crt" ]; then
echo "Generating self-signed certificate…"
chmod +x gen.sh && ./gen.sh
fi
kubectl create secret tls $NAMESPACE-neoteroi-xyz-tls \
--cert=$NAMESPACE-neoteroi-xyz-tls.crt \
--key=$NAMESPACE-neoteroi-xyz-tls.key \
-n $NAMESPACE
cd ../
kubectl apply -f $NAMESPACE.yaml
They create a namespace, generate a self-signed TLS certificate (if it doesn't exist already), create a TLS secret, and apply the deployment and ingress resources.
The deployment files look like this:
apiVersion: apps/v1
kind: Deployment
metadata:
name: orange-app
namespace: orange
spec:
replicas: 1
selector:
matchLabels:
app: orange-app
template:
metadata:
labels:
app: orange-app
spec:
tolerations:
- key: "CriticalAddonsOnly"
operator: "Exists"
effect: "NoSchedule"
containers:
- name: orange-app
image: robertoprevato/mvcdemo:latest
env:
- name: BG_COLOR
value: "#fd7e14"
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 15
periodSeconds: 20
---
apiVersion: v1
kind: Service
metadata:
name: orange-app
namespace: orange
spec:
ports:
- port: 80
targetPort: 80
selector:
app: orange-app
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: orange
namespace: orange
spec:
ingressClassName: traefik
tls:
- hosts:
- orange.neoteroi.xyz
- www.orange.neoteroi.xyz
secretName: orange-neoteroi-xyz-tls
rules:
- host: orange.neoteroi.xyz
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: orange-app
port:
number: 80
- host: www.orange.neoteroi.xyz
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: orange-app
port:
number: 80
This YAML file defines three Kubernetes resources:
-
Deployment: Runs a single replica of a web application container with an orange background color (
#fd7e14
), including readiness and liveness probes -
Service: Creates a ClusterIP service that exposes the application internally on port 80
-
Ingress: Configures routing for two domains (
orange.neoteroi.xyz
andwww.orange.neoteroi.xyz
) with TLS support using the Traefik ingress controller
The teal
folder contains a similar setup, but with a different background color
(#20c997
).
To test the setup, you can use curl
or a web browser to access the two domains:
curl -k https://orange.neoteroi.xyz
curl -k https://www.orange.neoteroi.xyz
curl -k https://teal.neoteroi.xyz
curl -k https://www.teal.neoteroi.xyz
The positive side of this approach is that each team can manage their own ingress rules
and TLS certificates independently, without needing to coordinate with a central team.
Additionally, each application can handle web requests at the root path (/
), as not
all applications support being served from a subpath (this is generally not an issue
for web APIs, while it is usually a problem for web applications that include HTML
pages).
Summary¶
The multi-domain ingress approach is easy to set up in K3s and can be an excellent way to host non-production environments on single-node or smaller clusters. Since K3s supports production workloads, this approach can also be used in larger clusters.
Last modified on: 2025-09-17 16:31:04