Configuring Previews
Overview
Preview URLs work out of the box: every endpoint declared on a sandbox or route group gets a URL on Signadot's domain, authenticated and served for you.
Starting with Operator v1.4.0, previews are also configurable per cluster, in the operator's Helm values:
- Preview URLs on your own domain. By pointing an ingress you control at the
operator's preview server, previews can be served on a domain of yours (for example
*.preview.example.com) with your own TLS certificates and your own access controls. - Turning previews off for a cluster entirely, so that Signadot serves no preview traffic for it.
Both are set in the chart — the API and the Dashboard report them, read-only.
Declaring preview endpoints
Nothing changes in how endpoints are declared. They are defined on a sandbox's
defaultRouteGroup or on a
standalone Route Group, exactly as
before:
name: my-sandbox
spec:
cluster: my-cluster
forks:
- forkOf:
kind: Deployment
namespace: hotrod
name: frontend
customizations:
images:
- image: my-registry/frontend:pr-42
defaultRouteGroup:
endpoints:
- name: frontend
target: http://frontend.hotrod.svc:8080
The configuration below only changes what domain the resulting URL is on and whether it is served.
Configuring previews
Previews are configured per cluster, in the operator's Helm values:
previewServer:
enabled: true # serve previews for this cluster
customDomain: preview.example.com # optional; your own base domain
helm upgrade signadot-operator signadot/operator \
--namespace signadot --reuse-values \
--set previewServer.customDomain=preview.example.com
| Value | Behavior |
|---|---|
previewServer.enabled: true (default) | Previews are served for this cluster. |
previewServer.enabled: false | Preview traffic is refused for this cluster — including through your own ingress (see warnings below). |
previewServer.customDomain | The base domain used in the Preview URLs reported for the cluster's endpoints. You serve that domain yourself (see setup). |
The chart validates the values when it renders: customDomain must be a bare domain of
at least two labels (no scheme, port, path, wildcard or trailing dot, at most 255
characters), and setting it with enabled: false is rejected — with previews off there
is nothing behind the domain to serve it.
Changing the values is a helm upgrade; the Signadot Control Plane picks them up from
the cluster's cluster-config ConfigMap.
Checking what a cluster is doing
The API reports each cluster's previews, and is read-only — the chart values above are where previews are configured:
curl -H "signadot-api-key: $SIGNADOT_API_KEY" \
"https://api.signadot.com/api/v2/orgs/${ORG}/clusters/${CLUSTER}/preview"
The report has three fields, plus notes when they do not speak for themselves:
previews— whether previews are served:in-cluster(operator v1.4.0+),control-plane(an older operator, or a cluster that has never connected), oroff.customDomain— the cluster's custom domain, when one is set.configured— whether the chart carriespreviewServervalues at all.falsemeans the chart predates them; such a cluster behaves as it always has.
The same report backs the Previews card on the cluster's page in the Dashboard.
Version requirements and effect on existing setups
- Operator v1.4.0 or later is required for custom domains and for turning previews off. Nothing else needs to be installed.
- Clusters on older operators are unaffected. They keep the behavior they have always had; upgrading the operator is what makes the configuration available.
- Existing Preview URLs keep working. A custom domain changes which URL is reported for an endpoint, not which URLs resolve.
- The traffic source inside your cluster changes on upgrade. From v1.4.0 the
operator serves previews with a component of its own,
previewserver, so preview requests reach your workloads from thepreviewserverPod (labelapp=previewserver, in the operator's namespace) rather than from the Signadot tunnel agent. If you use NetworkPolicies, a service mesh with strict mTLS, or source-IP-based controls that restrict which Pods may call your services, allow traffic frompreviewserverbefore upgrading, or hold the cluster on its current operator version until you have. A blocked hop surfaces as502responses on preview URLs.
Serving previews on your own domain
To serve previews through your own ingress:
-
Upgrade the operator to v1.4.0 or later.
-
Choose a base domain (for example
preview.example.com) and provision a wildcard TLS certificate for*.preview.example.com. TLS terminates at your ingress;previewserverspeaks plain HTTP behind it. -
Point your ingress at
previewserver: thepreviewserverService, port8080, in the operator's namespace. See the per-controller examples below. -
Set
previewServer.customDomainto your base domain andhelm upgrade. Preview URLs reported by the API and Dashboard then use it, in the formhttps://<endpoint-id>.preview.example.com— the leading DNS label is the endpoint's opaque ID — and can be called like any other URL you serve:curl https://ep2fbmvlpr9zw.preview.example.com/api/status
Your ingress configuration must satisfy two requirements, and both matter:
- Forward the original
Hostheader unchanged.previewserverresolves each request from the leading DNS label ofHost. Most controllers (ingress-nginx, Gateway API) preserveHostby default. - Strip any client-supplied
X-Forwarded-Hostheader before setting your own.X-Forwarded-Hostis used as a fallback identity source, so an ingress that blindly forwards a client's value turns it into a forgeable input.
Ingress configuration by controller
All of the examples below satisfy both requirements when applied as written. Use the one matching the controller you already run; each tab notes the controller-specific settings that must hold for the example to stay correct.
Each example addresses the previewserver Service by port number (8080) rather
than by port name. That is deliberate: the single listener carries both HTTP/1.1 and
cleartext HTTP/2 (gRPC previews arrive on it too), and a service mesh selects the
protocol it speaks to a backend from the port's name, so the name is a protocol
declaration rather than a stable address.
- ingress-nginx
- Gateway API
- Istio
Both requirements are covered by nginx's default proxy template with no annotations needed:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: previewserver
namespace: signadot
spec:
ingressClassName: nginx
tls:
- hosts:
- "*.preview.example.com"
secretName: preview-wildcard-tls
rules:
- host: "*.preview.example.com"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: previewserver
port:
number: 8080
This is safe only while the controller's use-forwarded-headers setting is false
(nginx's default). With use-forwarded-headers: true, common when nginx sits behind
another L7 proxy or cloud load balancer, nginx substitutes a client-supplied
X-Forwarded-Host into its own routing decisions, which breaks both requirements at
once: a request could be served another endpoint's content by forging a single header.
This is a controller-wide setting that cannot be fixed per-Ingress; if your main
controller needs it enabled, run a separate dedicated controller instance for previews
with the setting left at its default. Confirm your controller's actual setting before
relying on this example.
Host is preserved by default, and the RequestHeaderModifier filter removes any
client-supplied X-Forwarded-Host. Works with any conformant implementation (Envoy
Gateway, Istio's Gateway API mode, GKE Gateway, and others); set gatewayClassName to
your installed class.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: previewserver
namespace: signadot
spec:
gatewayClassName: <your-gateway-class>
listeners:
- name: preview-https
protocol: HTTPS
port: 443
hostname: "*.preview.example.com"
tls:
mode: Terminate
certificateRefs:
- name: preview-wildcard-tls
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: previewserver
namespace: signadot
spec:
parentRefs:
- name: previewserver
hostnames:
- "*.preview.example.com"
rules:
- filters:
- type: RequestHeaderModifier
requestHeaderModifier:
remove:
- X-Forwarded-Host
backendRefs:
- name: previewserver
port: 8080
Authority (Host) is preserved as long as route.rewrite.authority stays unset on the
route, and headers.request.remove strips any client-supplied X-Forwarded-Host.
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: previewserver
namespace: signadot
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 443
name: https-preview
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: preview-wildcard-tls
hosts:
- "*.preview.example.com"
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: previewserver
namespace: signadot
spec:
hosts:
- "*.preview.example.com"
gateways:
- previewserver
http:
- headers:
request:
remove:
- x-forwarded-host
route:
- destination:
host: previewserver.signadot.svc.cluster.local
port:
number: 8080
# Do not set rewrite.authority here: that is the one setting that
# would replace the inbound Host before it reaches previewserver.
Requests arriving through your ingress are served by previewserver directly and are
not authenticated by Signadot. Access control for your domain is yours, at your
ingress. Preview URLs on Signadot's domain remain authenticated by the Signadot Control
Plane as before.
Turning previews off and on
Setting previewServer.enabled: false makes Signadot refuse preview traffic for the
cluster (and uninstalls the previewserver component). Things to know before flipping
it:
- Preview requests are refused with
404(gRPC:NOT_FOUND) for every endpoint on the cluster, including previously shared links. - Reported URLs do not change. Endpoints keep listing their addresses in the API, the Dashboard and pull request comments — links to them are already in circulation — they just stop working.
- Your own domain stops working too. Uninstalling
previewserverremoves the backend your ingress points at. - Re-enabling restores serving. Endpoint IDs are stable, so links that existed before the toggle work again after it.
Both directions take a helm upgrade and take effect once the Signadot Control Plane
picks up the cluster's updated configuration.
Effect on sandboxes and route groups
- The configuration is per cluster, per endpoint. Each endpoint follows the configuration of the cluster its target lives in, so a route group that spans clusters can mix URLs on different domains, and endpoints Signadot serves with endpoints it does not.
- Status is unchanged. An endpoint that cannot be resolved in the cluster is
reported on the sandbox's and route group's readiness status, and a preview URL
returns
503until the sandbox's routing is ready — the same readiness that has always gated sandbox traffic. See Sandbox Status.
Use cases
- Previews behind your own access controls: serve preview URLs on your domain behind your SSO/VPN/ingress policies, keeping preview traffic off shared external infrastructure.
- Custom-branded preview links: PR comments and notifications carry
https://<id>.preview.your-domain.comlinks that live entirely within your DNS and certificates. - Compliance and network isolation: serve previews through your own ingress, on your own domain. Note this governs what is advertised, not a closed path: while previews are enabled, the same endpoints stay reachable on Signadot's domain as well.
See also
- Preview URLs — declaring and accessing preview endpoints.
- Sandbox Specification — the
defaultRouteGroupfield. - Route Groups — standalone route groups and their endpoints.
- Request Routing — the in-cluster routing that preview traffic relies on.
- Set up Context Propagation — how routing keys travel between your services.