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#
-
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 acmeoracme/platform(organization/team)GitLab group acme, oracme/platformfor a subgroupAzure DevOps project org/projectEJO 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 configuredorazure devops token is not configured. See Connect your source control. -
EJO scans the group's repositories. For each one it picks the SCM's default branch, else
main, elsemaster, and looks for.ejoat the root of that branch. -
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,oncallandargocdblocks is an error, so a typo such aslable: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.
Point to a separate file (recommended)#
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,.ymlor.json(at most 512 characters). - No other key may sit next to
file. - The file is read from the same repository and branch as
.ejoand 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 examplepipeline 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:
- Turn on Create bindings from .ejo under Settings > Organization.
- 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
.ejoin 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 withqueryisprometheusand one withurlishttp. - Prometheus checks need a
comparator(<,<=,>,>=) and a numericthreshold. - HTTP checks need a
urlstarting withhttp://orhttps://and anexpect_statusbetween 100 and 599. - With an empty
environmentthe check applies in every environment. queryandurlmay 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
docsonly accepts Markdown (.md) files in the repository. An entry is a plain path or{title, path}; addrunbook: trueto mark a document as the runbook.provides[].nameandconsumes[].apimay use lowercase letters, digits,.,_and-.oncallneeds at least one ofpagerduty_service(for exampleP1A2B3C) oropsgenie_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