Docs

Documentation / Pipelines

The .ejo file

Describe your service, its owner, its pipeline and settings such as previews and verification in the .ejo file at the repository root.

What .ejo does#

.ejo is a YAML file at the repository root that describes the service. EJO reads it to:

  • list the repository as a service under Services > Catalog,
  • match the service owner to EJO users or groups,
  • bind the repository by itself when the file has a pipeline (if the organization allows it),
  • apply settings such as previews, the automatic scan and deploy verification to the binding and the environment.

The simplest .ejo has just a name and a description:

name: web
description: Acme web frontend
owner: team-web

Where EJO reads it from#

  1. Under Settings > Organization, add an SCM group to your organization in the SCM groups card. The Provider and Group fields:

    Provider Group
    GitHub organization or team acme or acme/platform (organization/team)
    GitLab group acme, or acme/platform for a subgroup
    Azure DevOps project org/project

    EJO reads GitHub groups through the EJO Cloud GitHub App. To scan GitLab and Azure DevOps groups, EJO needs a GitLab token or an Azure DevOps PAT; without one the group shows gitlab token is not configured or azure devops token is not configured. See Connect your source control.

  2. EJO scans the group's repositories. For each one it picks the SCM's default branch, else main, else master, and looks for .ejo at the root of that branch.

  3. The result shows on Services > Sources: no .ejo, invalid .ejo (with the reason), in the catalog without a pipeline, binding created, or binding failed (with the reason).

The scan runs again on every push to the chosen branch that changes .ejo (GitHub) or on every push (GitLab, Azure DevOps). Each group is also scanned every 30 minutes. Read again on the Sources page starts a scan by hand.

Fields#

Field Required Description
name Yes The service name, at most 255 characters.
description No A short description, at most 2000 characters.
owner No The owner: an EJO user or group. Prefix with user: or group: to pick the type.
environment No Name of the EJO environment the binding deploys to. Empty means the only environment, if there is just one.
pipeline No An inline pipeline, or {file: <path>} pointing at a pipeline file in the repository.
preview No Pull request preview settings: enabled, label, health_path.
metrics No ignore: path globs (gitignore style, at most 50) left out of git metrics such as lines changed and coding impact, on top of lock files and generated code. ignore: [] clears the list.
supply_chain No auto_scan: false keeps this repository's builds out of the automatic SBOM and scan.
verify No Service-specific checks after a deploy (at most 10).
docs No Markdown documents shown on the service page (at most 20).
provides No APIs the service provides: name and a spec path in the repository (at most 20).
consumes No APIs the service uses: api, optional service and version (at most 20).
oncall No pagerduty_service or opsgenie_team.
argocd No The service's Argo CD targets: name, environment, application, namespace (at most 20).

Note: An unknown key in the preview, supply_chain, verify, oncall and argocd blocks is an error, so a typo such as lable: is never silently ignored.

name and service identity#

The name is free text, but to be used as the service identity in pipeline OIDC tokens it must match ^[a-z0-9][a-z0-9._-]{0,62}$ (lowercase letters, digits, ., _, -) and be unique in the organization. The repository that took the name first keeps it; a second repository using the same name shows a warning on the Sources page.

owner#

The owner is matched against the users and groups EJO knows. If there is no match, the service is still added to the catalog with an empty owner, and this shows on the Gaps tab. The match runs again on every scan.

Defining the pipeline#

There are two ways to define a pipeline in .ejo.

name: web
owner: team-web
pipeline:
  file: ci/ejo.yaml
  • The path must be relative to the repository root, must not start with / or contain .., and must end in .yaml, .yml or .json (at most 512 characters).
  • No other key may sit next to file.
  • The file is read from the same repository and branch as .ejo and validated against the pipeline definition schema. If it is missing or invalid, the repository stays in the "binding failed" state and the reason names the path, for example pipeline file ci/ejo.yaml not found.
  • A binding created from it has the repository as its pipeline source; changes to that file reach the pipeline with your pushes.

This keeps the service identity (.ejo) and the frequently changing pipeline in separate files, each protected by its own CODEOWNERS rule.

Inline pipeline#

name: web
owner: team-web
environment: staging
pipeline:
  name: web
  steps:
    - name: Tests
      kind: SHELL
      image: node:20-alpine
      config:
        command: npm ci && npm test

An inline pipeline uses the same schema as ejo.yaml and is published once, as the starting pipeline, when the binding is created. You make later changes in the console.

Automatic binding#

By default .ejo only feeds the catalog and you create the binding. To let EJO create it:

  1. Turn on Create bindings from .ejo under Settings > Organization.
  2. Choose the environments EJO may bind into under Environments open to auto-binding.

On the next scan, for every repository with a pipeline and no binding, EJO creates a binding in the environment named by environment: (or the only one) and publishes the pipeline under the repository name. An existing pipeline with the same name is never overwritten; the reason shows on the Sources page. Existing bindings are not touched. Every automatic binding is written to the audit log.

Tip: With Require a valid .ejo before a binding on, a repository cannot be bound by hand or from a blueprint until a scan has read a valid .ejo in it.

Preview settings#

preview:
  enabled: true          # turns on PR previews on the binding (also turns on the PR trigger)
  label: preview         # only PRs with this label get a preview; empty = every PR
  health_path: /healthz  # path requested before the preview counts as ready

Only the keys you write are applied to the binding; the others stay as set in the console. label can be at most 64 characters, without commas or quotes. health_path must start with / and contain no spaces. Details: Preview apps.

Deploy verification checks#

When deploy verification is on for the environment, the service can add its own checks:

verify:
  - name: error-rate
    environment: production
    type: prometheus
    query: sum(rate(http_requests_total{service="{{.Service}}",code=~"5.."}[5m]))
    comparator: "<"
    threshold: 0.5
  - name: health
    type: http
    url: https://web.example.com/healthz
    expect_status: 200
  • Without type, a check with query is prometheus and one with url is http.
  • Prometheus checks need a comparator (<, <=, >, >=) and a numeric threshold.
  • HTTP checks need a url starting with http:// or https:// and an expect_status between 100 and 599.
  • With an empty environment the check applies in every environment.
  • query and url may use {{.Service}}, {{.Environment}}, {{.Namespace}}, {{.Deployment}}, {{.CommitSHA}} and {{.ImageDigest}}.

Details: Deploy verification and rollback.

Docs, APIs and on-call#

docs:
  - docs/runbook.md
  - title: Architecture decisions
    path: adr/0001.md
provides:
  - name: web-api
    spec: openapi.yaml
consumes:
  - api: payments-api
    service: payments
    version: "2"
oncall:
  pagerduty_service: P1A2B3C
  • docs only accepts Markdown (.md) files in the repository. An entry is a plain path or {title, path}; add runbook: true to mark a document as the runbook.
  • provides[].name and consumes[].api may use lowercase letters, digits, ., _ and -.
  • oncall needs at least one of pagerduty_service (for example P1A2B3C) or opsgenie_team.

Full example#

name: web
description: Acme web frontend
owner: group:team-web
environment: staging
pipeline:
  file: ci/ejo.yaml
preview:
  enabled: true
  label: preview
  health_path: /healthz
metrics:
  ignore:
    - docs/
    - "*.snap"
supply_chain:
  auto_scan: true
verify:
  - name: health
    type: http
    url: https://web.example.com/healthz
    expect_status: 200
docs:
  - docs/runbook.md

Next steps#

Last updated: October 18, 2026