Docs

Documentation / Features

Preview apps

Open a preview for every pull request on GitHub, GitLab and Azure DevOps in an isolated Kubernetes namespace with a public URL, removed automatically when the PR closes.

What is a preview?#

A preview (Review App) is a running copy of a pull request (merge request on GitLab). For every PR, EJO creates a separate namespace in your shared cluster, deploys the application there and writes a URL of the form https://<preview-name>.preview.example.com into a PR comment. When the PR closes, the namespace is deleted.

Previews work on GitHub, GitLab and Azure DevOps. On this page "PR" also covers GitLab merge requests.

  • The preview name (also the namespace name) starts with ejo-, contains the repository name and ends in -pr-<number>.
  • New pushes to the same PR update the same namespace.
  • Each PR gets a single comment, updated on every push.

Per SCM#

GitHub GitLab Azure DevOps
Event that opens a preview PR opened, updated, reopened or labeled Merge request opened, updated, reopened or labeled Pull request created or updated
Event that removes a preview PR closed or label removed Merge request merged, closed or label removed Pull request completed or abandoned
Preview label Yes Yes No, every PR gets a preview
Where the URL is written PR comment Merge request note Pull request thread
Preview status Commit status ejo/preview Commit status ejo/preview Genre ejo, name preview

On GitLab and Azure DevOps, EJO needs a GitLab token or an Azure DevOps PAT to write comments and statuses; without one, no comment or status is written and you see the preview under Delivery > Previews. See Connect your source control.

Prerequisites#

  • An environment of type Kubernetes.
  • An agent installed with the Preview namespaces (Review Apps) option. This lets the agent create and delete preview namespaces. See Installing an agent.
  • An ingress controller in the cluster and a wildcard DNS record for the preview domain, for example *.preview.example.com.
  • A CNI that enforces NetworkPolicy (for example Calico or Cilium; k3s's built-in controller does too). Plain flannel does not.

Setup#

  1. On the environment page, press Set up previews. The preview setup wizard opens.
  2. In Preview base domain, enter the domain your wildcard DNS points at, for example preview.example.com. The wizard shows an example URL.
  3. Set the ingress class, TLS secret, TTL, quota and egress as needed (see the table below).
  4. Tick My cluster's CNI enforces NetworkPolicy.
  5. On the repository binding, turn on Preview on PR (isolated name, tear down when the PR closes). This also turns on the PR trigger. See Triggers and bindings.

You can also set the binding options with the preview: block of the .ejo file. See The .ejo file.

Environment settings#

Setting Description
Preview base domain Every preview gets a hostname under this domain.
Ingress class (optional) When your cluster uses a non-default ingress class for preview Ingress objects.
TLS secret name (optional) When you terminate HTTPS on the ingress, the name of the TLS secret in the agent's namespace. It is copied into each preview namespace.
Preview TTL hours (0 = off) Previews that are open but idle are removed after this time. A new push to the PR creates the preview again.
Max live preview namespaces (0 = unlimited) How many previews may exist at the same time.
Quota CPU, memory, pods The ResourceQuota applied to each preview namespace. A field left empty means no limit.
Auth secret A Secret in the agent's namespace (for example basic-auth), copied into each preview namespace.
Ingress annotations Annotations added to preview Ingress objects (a JSON object).
Egress allowlist Which destinations previews may reach (see below).

Binding settings#

Setting Description
Preview label (optional) Only PRs with this label get a preview; removing the label tears it down. Empty means every PR. Works on GitHub and GitLab; Azure DevOps ignores labels.
Preview health path (optional) The path requested on the Service before the preview counts as ready. Any status below 500 passes. Empty means /.
Preview environment The environment whose agents and secrets PR and teardown jobs use. Empty means the bound environment.

Previews in the pipeline#

In preview runs, K8S_DEPLOY and HELM_UPGRADE steps deploy into the PR namespace and create an Ingress automatically. K8S_DEPLOY waits for the rollout, then requests the health path for wait_seconds (default 60); if nothing answers, the step fails.

Several services in one PR can share the same preview host. Give each step a name with preview_service and a path with preview_path:

steps:
  - name: web
    kind: K8S_DEPLOY
    config:
      image: "{{.WebImage}}"
      container_port: 3000
      preview_service: web
  - name: api
    kind: K8S_DEPLOY
    config:
      image: "{{.ApiImage}}"
      container_port: 8080
      preview_service: api
      preview_path: /api
      preview_health_path: /api/healthz

SECRET variables defined for the environment are written to a Secret named ejo-preview-vars in each preview namespace, and K8S_DEPLOY hands these values to the container.

Isolation and egress#

Preview namespaces are isolated with NetworkPolicy: by default they can reach only their own namespace and DNS. Open up what the apps really need with the egress allowlist:

Field Description
Allow internet egress Allows public addresses. 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16 stay blocked.
Allowed CIDRs One CIDR per line, at most 20. For example the network of a shared database.
Allowed namespaces One namespace per line, at most 20.

Egress changes reach existing previews on their next push.

Warning: If your CNI does not enforce NetworkPolicy, previews are not isolated. Check this carefully if production and previews share a cluster.

With the merge gate#

With Also wait for the Review App URL on the binding, the merge gate waits until the PR run has a preview URL. This wait runs on the EJO side on every SCM; on the SCM side:

  • On GitHub, the merge gate ruleset also requires the ejo/preview status.
  • On Azure DevOps, EJO adds a second blocking status policy for the name preview.
  • On GitLab, EJO changes nothing extra.

Manage previews#

The Delivery > Previews page lists previews across every Kubernetes environment. From a preview's detail panel you can:

  • Pin: keep the preview out of TTL cleanup.
  • Extend TTL (+24h): extend its lifetime. An extension can be between 1 and 168 hours.
  • Redeploy: rerun its latest pull request deployment.
  • Restart, Sleep, Wake: restart the workloads, scale them to zero, or bring them back to their earlier replica count.
  • View pods, logs, events and quota usage.
  • Tear down: remove the preview right away.

Pinning, extending and tearing down need the developer or admin role.

Teardown#

A preview is removed when the PR closes (merged or closed on GitLab, completed or abandoned on Azure DevOps), when its TTL expires, when its label is removed, or when it is removed from the console. The namespace is deleted and the PR comment is updated with the heading ### EJO preview removed and the reason.

Next steps#

Last updated: October 16, 2026