Expose HTTP services
Every Kubernetes cluster can serve your websites and APIs over HTTPS through its managed gateway. Attach an HTTPRoute or an Ingress to it, and the platform takes care of the hostname and the certificate, with no load balancer to set up.
Websites and APIs (HTTP and HTTPS) → use the managed gateway, on the cluster's hostname or on your own domain through a CNAME record. Everything else (databases, TCP or UDP services) → a Service of type LoadBalancer, on a port of its own.
Every cluster has the managed gateway, switched on by default. If the cluster's Services & routes section does not show Managed gateway, it has been switched off for your workspace: contact support.
How it works
The platform creates a Gateway named sahabti in the cluster's sahabti-gateway namespace and keeps it there: you attach routes to it, and you do not edit it. Visitors connect over HTTPS to the platform's edge, which holds the certificates, and their requests reach the gateway as plain HTTP; the gateway then sends each one to the Service your route names. The gateway uses no load balancer, and none of your workspace's load-balancer quota.
Quick start
Save this as hello.yaml: a small web server, its Service, and a route to it.
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
spec:
replicas: 2
selector:
matchLabels:
app: hello
template:
metadata:
labels:
app: hello
spec:
containers:
- name: hello
image: nginx:1.27-alpine
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: hello
spec:
selector:
app: hello
ports:
- port: 80
targetPort: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: hello
spec:
parentRefs:
- name: sahabti
namespace: sahabti-gateway
rules:
- backendRefs:
- name: hello
port: 80
kubectl apply -f hello.yaml
The route names no hostnames, so it answers on the cluster's own hostname, <cluster>.sahabti.app, which the cluster's Services & routes shows under Managed gateway. Once that hostname shows Live, open it:
curl https://<cluster>.sahabti.app/
A route can live in any namespace, as long as its parentRefs names the gateway together with its namespace, as here.
Use an Ingress
Ingresses work too, which suits Helm charts and manifests that already use them. Give them the class sahabti:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop
spec:
ingressClassName: sahabti
rules:
- host: shop.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello
port:
number: 80
sahabtiis the cluster's default IngressClass, unless another class was already the default.- The platform turns each Ingress of this class into HTTPRoutes and keeps them in sync with it.
- Its
tlssection is ignored, because HTTPS is handled at the edge, and the Ingress gets an event saying so. - Its status shows the cluster's hostname, which
kubectl get ingresslists as its address.
The host must be a hostname the cluster holds: see Hostnames.
Hostnames
A cluster serves only the hostnames it holds:
- its own hostname,
<cluster>.sahabti.app; - your own domain, such as
shop.example.com, proven by a CNAME record; - a root domain, such as
example.com, bound through a verified workspace domain; - extra names under
sahabti.app.
The cluster's own hostname
Every cluster has one, <cluster>.sahabti.app, shown in its Services & routes, and it needs no setup. A route without hostnames answers on it, and on any of the cluster's hostnames that no other route claims.
Your own domain
Serving shop.example.com from the cluster takes two steps.
- Add the hostname. In the cluster's Services & routes, under Managed gateway → Use your own domain, enter
shop.example.comand select Add hostname. - Point a CNAME record at the cluster. At your DNS provider, create this record. The console shows it with your cluster's hostname filled in, ready to copy.
| Type | Name | Value |
|---|---|---|
| CNAME | shop.example.com | <cluster>.sahabti.app |
Then name the hostname in a route, as for any other hostname:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: shop
spec:
parentRefs:
- name: sahabti
namespace: sahabti-gateway
hostnames:
- shop.example.com
rules:
- backendRefs:
- name: hello
port: 80
The platform checks DNS every minute. As soon as the record names the cluster, it issues the certificate and serves the hostname; its status in the console moves from Waiting for DNS to Issuing certificate to Live, usually within a few minutes. The first step is optional: a hostname that one of the cluster's routes already uses is listed on its own, with the same record to create.
Good to know:
- No TXT record and no workspace domain is needed: the CNAME record is what proves the hostname is yours.
- If your DNS provider offers to proxy the record, keep it DNS only, so the CNAME stays visible.
- Keep the record in place. The platform keeps checking it; if it stops pointing at the cluster, the hostname shows CNAME missing, and an hour later it is no longer served. Restore the record and it is served again. A hostname that no route uses any more is then removed.
- One hostname belongs to one app, instance or cluster on the whole platform, and it goes to the first workspace that proves it. A hostname another workspace has only added, without its record pointing there, never blocks you.
- Wildcard hostnames, such as
*.example.com, are not supported.
A root domain
A root domain such as example.com cannot hold a CNAME record. To serve it from the cluster:
- On your workspace's Domains page, add
example.comand verify it with the TXT record shown there. - In the cluster's Services & routes, under Managed gateway → More ways to add a hostname, bind
example.comwith Bind a verified domain. - At your DNS provider, point
example.comatapps.sahabti.app, with an ALIAS, ANAME or flattened CNAME record if your provider offers one.
Keep the TXT record in place. The platform checks it again every day. As soon as the record is missing, the workspace's owners and admins get an email and the Domains page asks you to re-add it; if it is still gone three days later, the hostnames under the domain are suspended and another workspace may verify the domain. A domain verified before these daily checks began gets 30 days instead of 3 the first time its record goes missing.
Extra names
To serve more sites under sahabti.app, add extra names in the cluster's Services & routes, under Managed gateway → More ways to add a hostname. Each one is served as <name>--<cluster>.sahabti.app, so the name shop becomes shop--<cluster>.sahabti.app.
A name is 3 to 40 lowercase letters, digits and single dashes. Everything before .sahabti.app must fit in 63 characters, so a cluster with a long name leaves less room for it.
To send an extra name to a particular Service, put the full hostname in a route's hostnames or an Ingress's host.
HTTPS and the client's address
HTTPS needs nothing from you. TLS ends at the platform's HTTPS edge, which issues and renews the certificates, and requests reach the cluster as plain HTTP on the gateway, so your Services serve plain HTTP and need no certificate of their own. The client's address arrives in the X-Forwarded-For header.
Check the status
In the cluster's Services & routes, Managed gateway lists each hostname with its status, and says what to do next:
| Status | Means |
|---|---|
| Live | Served over HTTPS |
| No route yet | Ready, but no HTTPRoute or Ingress names it yet |
| Waiting for DNS | Its CNAME record does not point at the cluster yet; the record to create is shown next to it |
| Issuing certificate | The record is in place and the certificate is on its way |
| CNAME missing | Its record stopped pointing at the cluster; it stops being served an hour later unless the record is back |
| Pending | Not served yet, with the reason, such as waiting for the gateway to start |
| Not allowed | A route uses it, but the cluster cannot hold it, with the reason |
| Off | The managed gateway is turned off |
| Error | Setting it up failed, with what to do |
A route that uses a hostname the cluster does not hold is not served, and gets a HostnameNotRouted event:
kubectl describe httproute shop
Troubleshooting
A hostname stays on Waiting for DNS
Check the record from your own machine:
dig +short CNAME shop.example.com
It must answer with the cluster's hostname, <cluster>.sahabti.app., or one of its extra names.
- A record pointing at
apps.sahabti.appdoes not say which cluster you mean: point it at the cluster's own hostname. - A record your DNS provider proxies answers with addresses instead of the CNAME: make it DNS only.
- A new or changed record can take as long as its TTL to reach every resolver.
A hostname shows Not allowed
The note next to it says why:
- Another workspace proved it first. If the hostname is yours, point its CNAME record at your cluster: the other workspace stops holding it an hour after its record stops pointing at its cluster, or three days after a verified domain's TXT record is gone, and your cluster takes it over.
- A root domain. Bind it through a verified domain.
- The platform's own domain. Add it as an extra name instead.
- The hostname limit. Remove a hostname the cluster no longer needs.
A hostname stays on No route yet
The platform serves it, but no route names it, so the gateway has nowhere to send the request. Add it to an HTTPRoute's hostnames or an Ingress's host.
The certificate does not arrive
A CAA record on your domain may not allow the certificate authority. Allow letsencrypt.org, pki.goog and ssl.com in it, or remove the CAA record, then remove the hostname and add it again.
The gateway shows a problem
The console names it. The most common is a Service of yours using node port 30080, which the managed gateway needs on every node: give that Service another node port.
Limits
| Free plan | Paid plans | |
|---|---|---|
| Hostnames per cluster: your own domains, bound domains and extra names together | 20 | 100 |
The managed gateway itself uses none of your workspace's load balancers.
Leave node port 30080 free: the managed gateway uses it on the cluster's nodes, and a Service of yours that takes it stops the gateway. The console then shows an error naming that Service.
Updates
We keep the managed gateway up to date, security fixes included, with nothing to do on your side. An update replaces the gateway's proxy one copy at a time, and the new copy takes requests before the old one stops, so your hostnames stay served. A connection held open across an update, such as a WebSocket or a long download, may need to reconnect. After each update the platform checks the cluster and puts the previous version back if routes it accepted before are no longer accepted. The gateway's own objects in the cluster are managed by the platform, and changes made to them are reverted.
Turn the managed gateway off
In the cluster's Settings, switch Managed gateway off. This withdraws every hostname from the edge and removes the gateway's objects from the cluster. Your HTTPRoutes and Ingresses stay, but are no longer served. Switch it on again to bring it back.
Run your own gateway or ingress controller
The managed gateway is a default, not a requirement. To serve HTTP with a controller of your own:
- In the cluster's Settings, switch Managed gateway off, so two controllers never answer for the same Ingresses. You can also keep it on and give your Ingresses a class of your own.
- Install the Gateway API implementation or ingress controller you prefer, and expose it with a Service of type
LoadBalancer. It counts against your workspace's load-balancer quota and answers on a public port of your workspace's shared IP; a cluster's load balancers cannot have a dedicated IP yet, so ports 80 and 443 are not available to them. - To have Ingresses without a class use it, mark your IngressClass as the default with the annotation
ingressclass.kubernetes.io/is-default-class: "true". The managed gateway's classsahabtiis made the default only when no other class is. - Point your domain at the load balancer's address and manage its certificates yourself: the platform's edge and its certificates serve only the managed gateway.
When to use a load balancer instead
Use a Service of type LoadBalancer for anything that is not a website or an API, such as databases, game servers, or raw TCP or UDP, or when you need to terminate TLS yourself. Each one counts against your workspace's load-balancer quota: see A load balancer for a Service. On your workspace's shared IP it answers on a public port of its own, so clients connect to its hostname with that port, such as prod-default-db-e5f6a7b8-q2w7ze.lb.sahabti.app:40002; HTTPS without a port is the managed gateway's.
The gateway class sahabti is still there for running a Gateway of your own behind a load balancer, as in Route HTTP by hostname. Mind the names: routes attached to the Gateway sahabti in sahabti-gateway, and Ingresses of class sahabti, use the managed gateway, while a Gateway you create with gatewayClassName: sahabti runs behind a load balancer.