> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ankra.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate from GitHub Actions

> Convert a GitHub Actions workflow, a GitLab CI file, a Bitbucket pipeline or a CircleCI config into .ankra/pipeline.yaml, read every note the converter leaves, and run both side by side until you trust it.

Ankra converts an existing CI definition into an [Ankra pipeline](https://docs.ankra.ai/guides/ankra-pipelines.md) - the `.ankra/pipeline.yaml` its own engine runs inside your cluster. One converter reads four formats: GitHub Actions workflows, a `.gitlab-ci.yml`, a `bitbucket-pipelines.yml` and a `.circleci/config.yml`.

> [!NOTE]
>
> A converted pipeline is a starting point a person reviews, never a drop-in replacement. The rule the converter holds to: something it cannot map is either carried across as a stage that **fails with an explanation**, or reported as a note with the job or step it came from - never dropped silently. A converted pipeline that quietly skipped a toolchain step would go green having built nothing.

## Migrate an existing application with one call

For an application already connected to Ankra, this is the fast path, and it takes effect the moment you ask. An application that still builds from a GitHub Actions workflow - the one Ankra generated at setup, or one your repository wrote itself - shows **Convert to Ankra Pipelines** on its journey card and **Convert** on its row in the Applications list. The same conversion is one command or one call:

```bash theme={null}
ankra application pipeline convert <application-id>
ankra application pipeline convert <application-id> --keep-workflows

curl -X POST https://platform.ankra.app/api/v1/org/applications/<application-id>/pipeline-migration \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '{}'
```

One click does all of this:

* Reads what builds the application today and converts it into `.ankra/pipeline.yaml` through the same converter [Ankra AI uses](#convert-with-ankra-ai), then stores that as the application's pipeline definition of record.
* Records the application as building on Ankra Pipelines (`pipeline_source` becomes `ankra_pipeline`) right away, so the next push to the tracked branch builds through the pipeline. Nothing waits on a merge.
* Switches Ankra's generated workflow off on GitHub, so the workflow and the pipeline never build the same commit twice. If the GitHub App lacks the Actions permission the answer says so, and the workflow stops building when the pull request that removes it merges.
* Opens a pull request on the fixed branch `ankra/pipeline-migration` that commits the pipeline file and removes the generated workflow file. `--keep-workflows` (`{"remove_workflows": false}`) leaves the file in the repository, still switched off. A workflow your repository wrote itself is never touched either way.

The pull request is the repository catching up with what Ankra already runs: merge it when you are happy with the file. Converting again while it is still open answers the same conversion (`already_open`) and re-checks that the workflow is switched off; if you closed the pull request without merging, converting again proposes it afresh. An application with no detected CI, one already on Ankra Pipelines, or an organisation with no CI cluster to run the result on is refused with the reason rather than opening a pull request with nowhere to go. A service token may convert an application exactly as it may dispatch a run. The conversion needs the repository's stored GitHub credential today.

`GET .../pipeline-migration` reads what builds the application today and what a conversion would act on, without changing anything.

## Convert with Ankra AI

Ask Ankra AI in the portal chat, or call the tool through the [MCP server](https://docs.ankra.ai/platform/mcp-server.md). Example prompts:

* "Convert `.github/workflows/ci.yml` and `.github/workflows/build-deploy.yml` into an Ankra pipeline and show me every note."
* "Convert this `.gitlab-ci.yml` into `.ankra/pipeline.yaml` for the `orders-api` application."

The tool behind it is `cicd_convert_workflow_to_pipeline`:

| Parameter | Meaning |
| - | - |
| `workflow_files` | The files to convert, each with its repository `path` and its `content`, at most 20 files and 256 KB per call. For GitHub Actions pass every workflow that takes part in the build - they become **one** pipeline, because a repository's workflows share one checkout and one run. For GitLab, Bitbucket and CircleCI pass exactly one file. |
| `format` | Optional: `github_actions`, `gitlab_ci`, `bitbucket_pipelines` or `circleci`. Omitted, it is inferred from the paths - a file named `.gitlab-ci*` selects `gitlab_ci`, `bitbucket-pipelines*` selects `bitbucket_pipelines`, a path under `.circleci/` selects `circleci`, anything else `github_actions`. |
| `application_id` | Optional: when it resolves, the pipeline's `metadata.name` is the application's name; otherwise one is derived from the workflow and the notes say so. |

It answers with the rendered `pipeline_yaml`, the `notes` naming every construct it could not carry across faithfully, and a validation verdict. It writes nothing to the repository: open the pull request yourself, or ask the AI to open one. Its twin, `cicd_validate_pipeline_yaml`, validates a definition you wrote or edited and lists every violation with its key and severity - see [Validation](https://docs.ankra.ai/guides/pipeline-reference.md#validation).

## What maps to what

Jobs become stages, `needs` becomes `needs`, conditions keep their `${{ }}` delimiters, and the `steps`, `matrix`, `inputs`, `env` and `needs` references map one to one. A reference to the `github` context does not: the validator reports it with the Ankra equivalent (`github.sha` is `ankra.sha`, `github.event_name` is `event.kind`, and so on) for you to rewrite. See [Expressions](https://docs.ankra.ai/guides/pipeline-reference.md#expressions) for the two deliberate departures.

Third-party steps go through one mapping table, so a GitHub action, a Bitbucket pipe and a CircleCI orb command that do the same work become the same stage. A row matches every version of its identifier (`actions/checkout@v4`, `atlassian/slack-notify:2.1.0`).

### Steps that become a stage

| Source | Becomes |
| - | - |
| `actions/checkout` | A `checkout` stage; `ref`, `fetch-depth`, `submodules` and `lfs` are carried as inputs |
| `docker/build-push-action`, `depot/build-push-action`, `circleci/docker/build` | A `build` stage: `file`/`dockerfile`, `context`, `platforms`, `build-args`, `provenance`, `sbom`, `cache-to`. `push: true` is understood and needs nothing - `build` always pushes its digest to the organisation's own CI staging repository, and `publish` is what tags it into your real one, once a `gate` step approves it. Build argument **names** are kept and their values dropped, because a value in a workflow is routinely a secret expression that must not be committed. The `tags` input is reported and left behind: an Ankra build produces a digest, and `publish` is what tags it, always as `sha-<7>` - a workflow's own tag scheme (`latest`, semver, a branch name) is not carried across. |
| `aquasecurity/trivy-action`, `aquasecurity/trivy-pipe` | A `scan` stage running Trivy alone (`scanners: ["trivy"]`) that blocks per the organisation's image gate; a `severity` input is reported, because the scan blocks per the gate and reports every finding either way. `scan` also runs Semgrep and Checkov when you add them to `scanners:` by hand - the converter does not add them for you, because nothing in a Trivy-only action says a repository wants either. |
| `slackapi/slack-github-action`, `8398a7/action-slack`, `atlassian/slack-notify`, `atlassian/microsoft-teams-notify`, `circleci/slack/notify` | A `run` stage carrying a **placeholder that fails until you finish it**: every input the source step declared, carried across as comments so nothing is lost, a commented `curl` to fill in, and `exit 1` at the end. A CircleCI `event` of `pass`, `fail` or `always` still becomes the stage's `if:` (`success()`, `failure()`, `always()`). Failing is deliberate - a script of nothing but comments would conclude the step green, and a green `notify-slack` step that delivered no notification is indistinguishable from one that did |
| A script that only packages and pushes a Helm chart (`helm package`, `helm push`, `helm lint`, `helm dependency`, `helm registry`) | A `run` stage on `alpine/helm:3.16.3` running the same `helm package` and `helm push` the script did - the image is named rather than inherited, because a pipeline's `defaults.image` is its build toolchain and ships no `helm`. The `helm registry login` the script performed is dropped, and the login is declared instead: add `secrets: [{name: registry_login, from: registry}]`, list it on the stage, and pass `--registry-config /run/agent-secrets/registry_login` to `helm push` - see [A registry login in a step](https://docs.ankra.ai/guides/pipeline-reference.md#a-registry-login-in-a-step). The workflow's `ANKRA_REGISTRY_USERNAME` and `ANKRA_REGISTRY_PASSWORD` do not travel: they are GitHub Actions repository secrets, and a pipeline step reads none. A script that does anything else stays a script. |
| `circleci/node/install-packages`, `circleci/python/install-packages` | The shell command the selector stands for: `npm ci`, `yarn install --frozen-lockfile`, `yarn install --immutable`, `pnpm install --frozen-lockfile`; `pip install -r requirements.txt`, `pipenv install`, `poetry install`. An unknown package manager becomes a failing stage. |

No converter emits a `uses:` reference any more. Until the built-in step library ships, any `uses:` is refused outright at validation - "Reusable steps are not available yet; write the step as `run:`" - so a converted file carrying one could not be planned at all. Each construct that used to map onto an `ankra/*` step is therefore emitted as the `run:` stage above, with a note naming the library step it stands in for.

### Steps that become the stage image

`actions/setup-node`, `actions/setup-go`, `actions/setup-python`, `actions/setup-java`, `circleci/node/install` and `circleci/go/install` do not become stages. Each becomes the image the job's later stages run in - `node:<version>`, `golang:<version>`, `python:<version>`, `eclipse-temurin:<version>` - because a stage that runs in that image has the toolchain the step installed. `20.x` and `20` both become `node:20`; a matrix expression such as `${{ matrix.node }}` is carried into the image. `lts/*`, `latest`, a range or a version file names a resolution rule rather than a version, so no image is derived and the note tells you to pin one on the job's stages. A setup step with no version at all leaves the job's stages on the runner image below, which ships no language toolchain, so pin the toolchain image on those stages yourself.

### Jobs with no container

A `run` stage that names no image, with no `defaults.image` to inherit, cannot run: `ankra pipeline validate`, the commit gate and the planner refuse the whole definition under `pipeline_image:`, naming the stage. So the converter never leaves one. A GitHub-hosted job with no `container:` runs its stages in an image chosen from its `runs-on`:

| `runs-on` | Image |
| - | - |
| `ubuntu-latest`, `ubuntu-24.04`, `ubuntu-24.04-arm` | `buildpack-deps:noble@sha256:27bdb6ab1ca0df6acfe341752ee4d9c08a341d80e3ee75ccc3b34b79af0dfaf1` (Ubuntu 24.04) |
| `ubuntu-22.04`, `ubuntu-22.04-arm` | `buildpack-deps:jammy@sha256:b28e7724fdddd120588c097871f740efb9ca340cfc4dbc1ef9be5f0065474198` (Ubuntu 22.04) |
| Anything else (an older, Windows or macOS runner, a self-hosted or larger-runner label, an expression) | the Ubuntu 24.04 image above, with a note that the runner was not recognised |

The converter writes the tag together with its digest, so a registry-side retag never changes what the converted file runs. The first such job's image becomes the converted file's `defaults.image`, so every stage that names none inherits it; a job on a different runner names its own image on its stages. The same image is the default for a GitLab job or a Bitbucket step that names no `image:` (GitLab and Bitbucket ran those in a runner default this file cannot see), a CircleCI `machine` or `macos` executor, and the stages the converter emits for a step it could not carry across, which then fail with their own explanation rather than for want of an image. Each placement is a note naming the stages and the image.

`buildpack-deps` is Docker's official Ubuntu image with `git`, `curl`, `wget`, `ssh`, `make`, `gcc` and the common development headers. It is a starting point, not a copy of GitHub's runner, which also ships language toolchains, `docker`, `jq` and cloud CLIs. A step that relied on one of those fails until a setup step (above) or the stage's own `image:` provides it. Installing it at run time is not the way out: a stage runs as a non-root user on a read-only root filesystem. Change `defaults.image`, or set `image:` on a stage, to run it in the image your job needs.

### Steps that are dropped, with a note

| Source | Why |
| - | - |
| `docker/login-action`, `docker/setup-buildx-action`, `docker/setup-qemu-action`, `depot/setup-action`, `circleci/docker/check` | An Ankra run authenticates to the registry with its own credential and the build stage brings its own builder |
| `aws-actions/configure-aws-credentials`, `google-github-actions/auth`, `azure/login`, `circleci/aws-cli/setup` | A cloud credential is materialised through the pipeline's `credentials` block, not configured by a step |
| `azure/setup-helm`, `circleci/helm/install_helm_client` | An Ankra run authenticates to the registry with its own credential, and a chart-push script becomes a `run` stage on the `alpine/helm:3.16.3` image, which already ships helm |

### Everything else

A third-party step the table does not know becomes a `run` stage that stops the run and says what has to be replaced:

```bash theme={null}
echo "This step used <reference>, which the converter could not map to an Ankra stage."
echo "Replace it with an image that already provides what the action did, a script, or an ankra/* step."
exit 1
```

Failing is the point: a dropped step would produce a pipeline that runs green having skipped whatever the step did, and you would have no way to tell a converted step from a discarded one.

## What each source loses

Every item below arrives as a note with the file, job or step it came from. Read the notes before the YAML.

**GitHub Actions**

* The `permissions` block is not converted: a run's authority is the intersection of the pipeline's own `permissions`, the organisation's CI policy and the target environment, not the workflow token's scopes.
* A trigger with no Ankra equivalent is not converted, and a trigger that narrows on something an Ankra trigger cannot (for example pull request `types`) loses the narrowing: the pipeline runs on **more** of those events than the workflow did.
* `defaults.run.shell` is not converted; a stage runs its script with the shell its image provides.
* Several workflows fold into one pipeline whose `on:` is their union - a path filter that one workflow declares and another does not leaves the union unfiltered, so every run the workflows started still starts. Each workflow's own `push` and `pull_request` branches and paths stay on its stages as [event-scoped `when` blocks](https://docs.ankra.ai/guides/pipeline-reference#event-scoped-filters) wherever they are narrower than the union, and a workflow that does not listen to an event the pipeline does keeps its stages out of it with `when.events`. A note names the workflows this applied to.
* A second `concurrency` block is not converted: a pipeline carries one concurrency group, and the first workflow claimed it.
* A job that calls a reusable workflow (`uses: ./.github/workflows/…`) cannot be followed; its stage fails until it is replaced.
* A workflow that is not valid YAML contributes nothing, and the note says which.

**GitLab CI**

* `workflow:rules` is not converted, so runs it filtered out are not filtered here.
* A top-level `services:` block, which gives every job a sidecar, is not converted: declare the services the stages need in the pipeline's `services` block and list them on each stage, or the jobs that relied on them fail at runtime.
* A job that triggers a downstream pipeline has no equivalent.
* `except:` is not converted: an Ankra stage filters on the branches, paths and events it runs *for*, never on the ones it skips. A `rules:` block carrying `when: never` on a `changes:` list is not converted, and its paths go with it: that rule skips the paths it names, and an Ankra `when.paths` says which paths the stage runs *for*, so carrying them across would invert the meaning. Otherwise its `changes:` paths are carried, onto the stage's flat `when.paths` unless the rule names an event. A rule whose `if:` tests `$CI_PIPELINE_SOURCE == "merge_request_event"` becomes [`when.pull_request.paths`](https://docs.ankra.ai/guides/pipeline-reference#event-scoped-filters), one testing `== "push"` becomes `when.push.paths` (a matching rule with no `changes:` leaves that event unfiltered), and the rest go to `when.paths`. Any other `if:` condition, and the rules' order, are not carried, and the note says so.

**Bitbucket Pipelines**

* A step `size` of `1x`, `2x`, `4x` or `8x` becomes a memory request; any other value is reported and you declare `resources.memory` yourself.
* `bookmarks:` pipelines belong to Mercurial and are not converted.
* A parallel group's fail-fast is not converted: an Ankra run lets the other stages of a group finish when one fails.
* A pipeline that imports from another repository cannot be followed and contributes nothing.
* `runs-on` labels are not converted: an Ankra stage is placed by the organisation's CI cluster and the stage's own `runs_on` block.
* A manual trigger becomes an `approval` stage approvable by the `admin` and `operator` roles, because anyone with write access could press the Bitbucket button and no Ankra role maps onto that; narrow the roles to whoever actually held the button.
* A `pull-requests:` pattern is not carried as a filter. Bitbucket matches it against the pull request's **source** branch, while an Ankra `pull_request` trigger and a stage's `when.branches` match the **target**, and nothing in a pipeline filters on the source branch. Each section therefore runs for every pull request (`on.pull_request.branches: ['**']`), and the note suggests a push trigger on the pattern if that is the filter you need. A `'**'` section gets no note.
* A step that a branch section shares with a pull request, tag or custom section loses its branch filter, because a stage carries one branch list for all its events; the note names the filter that was dropped.
* `clone: lfs: true` and `clone: enabled: false` are not converted: the checkout stage does not fetch Git LFS objects, and every stage shares the workspace it fills. A step's own `clone:` settings are not converted either, because a run checks the repository out once.

Two Bitbucket settings carry across without a note: `clone: depth` becomes the checkout stage's `with.fetch_depth` (`full` becomes `"0"`, the whole history), and a step's `condition.changesets` becomes its `when.paths` - the `includePaths` entries followed by each `excludePaths` entry negated with `!`. The planner lets the last matching entry decide, so a changed file counts exactly when Bitbucket would count it. As in Bitbucket, a step whose changesets match nothing is skipped on its own: the converted step after it lists it under `skippable_needs`, so it still runs, and waits on the filtered step only when that step runs. A manual step's approval carries the same path filter, so a push that skips the step leaves no approval waiting.

**CircleCI**

* A version 2.0 configuration converts the way a 2.1 one does; any orb, command or parameter it cannot declare is simply absent. A setup configuration's continuation pipeline cannot be followed; only the jobs declared in the file are converted.
* An inline orb is not converted; every step that calls it becomes a stage that fails until it is replaced. An orb job the converter cannot follow is reported the same way.
* A parameter whose name carries a dash is renamed (an input name may not carry one), and every reference to it is renamed the same way.
* An approval job becomes an `approval` stage approvable by the `admin` and `operator` roles, with the same advice to narrow them.
* Anchors and aliases are resolved before conversion, so an anchored executor or step list behaves like an inline one.

## What to review after conversion

1. **The notes, first.** Each is a decision the converter took on your behalf or could not take. Stages that fail by design (`exit 1` with the reference) are in the YAML so you cannot miss them.
2. **Images.** Every stage needs an image the organisation's [image policy](https://docs.ankra.ai/guides/ankra-pipelines.md#organisation-ci-settings) allows - and so does every sidecar you declare in the pipeline's `services` block, whose image and `env` are held to the same boundary a step's own are, refusing the whole step rather than running beside it. A policy prefix is matched at a path boundary, and one ending in `/`, `:` or `@` admits only that boundary (`ghcr.io/my-org/` admits `ghcr.io/my-org/api` and not `ghcr.io/my-org:v1`) - the settings API refuses a trailing separator at write time anyway, so write the bare prefix. Pin the toolchain images the notes could not derive, and replace the runner image on any stage that needs more than it ships ([Jobs with no container](#jobs-with-no-container)).
3. **Network.** A `run` stage's tier defaults to `none`, which denies all egress; give `defaults.network: egress-https`, or a `network` on the stage itself, to the ones that install packages or reach a registry. The converted `checkout`, `build` and `scan` stages need nothing said about them: those three kinds default to `egress-https` ([the tier a stage gets by default](https://docs.ankra.ai/guides/pipeline-reference.md#the-tier-a-stage-gets-by-default)). That tier reaches public addresses only, so a job that pulled from a VPC-internal mirror, an in-cluster registry or an internal Git host needs its range in [`ci_egress_allowed_cidrs`](https://docs.ankra.ai/guides/ankra-pipelines#organisation-ci-settings) *before* the converted stage runs, and without it fails on a connection timeout rather than a permission error. Remember that anything above `egress-https` is a protected section - as are a stage's `timeout`, `cache`, `resources` and `with:` block, and the whole `services` block. A converted pipeline that declares any of them runs its pull requests under the last approved authority until an administrator approves the definition on your default branch; see [Logic is open, authority is closed](https://docs.ankra.ai/guides/ankra-pipelines.md#logic-is-open-authority-is-closed).
4. **Secrets and credentials.** The converter declares what the workflow referenced; the `secrets` block names a source (`app_env_secret`, `org_variable`, `registry`, `credential`) that has to exist in the organisation. Delivery is live for `secrets` sourced `app_env_secret`, `org_variable` or `registry`, and for `credentials` sourced `git` or `registry` - each revealed once, at step start, to a file the step reads. `secrets: [{from: credential}]` still validates but is not deliverable yet, and a `credentials` binding sourced `cloud` or `object_storage` is refused by name until the run has its own scoped identity - reconsider any converted cloud-credential step against that gap.
5. **Filters.** Where a `rules:`, `except:` or trigger `types` narrowing was lost, add `when:` or `if:` so the pipeline does not run more often than the workflow did.
6. **Resources and timeouts.** A stage takes 500m CPU, 1Gi memory and 30 minutes unless it says otherwise; a Go build or a Playwright suite wants more of the first two. A pull request from a fork gets less than a converted workflow may expect, whatever the file asks for: 2 CPU, 4Gi, a 60-minute step timeout, 8 matrix legs per stage, no sidecar services and no caches, with 256 MiB of artifacts and logs for the whole run. The clamps are applied to the plan, so the pull request's own status comment shows the smaller run rather than the one the file declared - see [Forks](https://docs.ankra.ai/guides/ankra-pipelines.md#what-a-step-runs-as).
7. **Caches, if you add one by hand.** A `cache:` block does not behave the way `actions/cache` does across branches. An Ankra cache claim is scoped to the trust of the run that resolved it, so every run that is not on your default branch - a feature branch, a tag, any pull request - shares one non-default scope, and none of them can read or write the claim your default branch's builds use. GitHub restores a default-branch cache into any branch; Ankra deliberately does not, because that is the path a less-trusted ref uses to poison what production builds from. Budget one cold build per cache key on each side of that line, and none at all on a fork's pull request, which resolves no shared cache.
8. **What executes today, and what the converter never adds.** `checkout`, `run`, `build`, `scan`, `gate`, `publish` and `deploy` all execute end to end; `verify` and the kinds with no executor yet (`preview`, `agent`, `approval`, `webhook`, `automation`, `ankra`, `external`) validate with a warning and are **skipped**, so a converted `approval` stage blocks nothing and a converted `webhook` notifies nobody. A `deploy` stage releases only to [host targets](https://docs.ankra.ai/reference/cli/targets.md) outside Kubernetes, so a converted job that ran `kubectl` or `helm` does not become one; deploy to a cluster through the application's push to deploy instead - see [What is in place today](https://docs.ankra.ai/guides/ankra-pipelines.md#what-is-in-place-today). The converter itself never writes a `gate` or a `publish` stage - no source format has one to map from - so a workflow whose `docker/build-push-action` pushed straight to your registry converts into a pipeline whose `build` stage pushes a digest to Ankra's own **CI staging repository only**, and stops there: without a `gate` stage after `scan` and a `publish` stage after `gate`, nothing ever reaches your real repository. Add both by hand (see the [worked example](https://docs.ankra.ai/guides/ankra-pipelines.md#connect-a-repository-push-see-the-run)) rather than assuming the converted file publishes what the workflow did.

Then validate it:

```bash theme={null}
ankra pipeline validate .ankra/pipeline.yaml --application my-service
```

## Run both side by side

Keep the workflow. A pipeline run and a workflow run on the same commit are independent: the workflow's checks keep their names, the pipeline's is `Ankra pipeline`, and both appear on the pull request. But know what actually decides which one a pull request's **preview** deploys from, because it is not a setting you flip: `.ankra/pipeline.yaml` outranks a workflow the moment it is committed on the default branch *and* the organisation has a CI cluster to run it on - the application reads as `ankra_pipeline`-sourced from that point on, whether or not a workflow still runs alongside it, and its previews deploy the pipeline's own digest from then on (one that has not built the head yet answers *build missing* rather than quietly falling back to the workflow's image). A merge to your default branch is more forgiving: your own CI's build observation always outranks the pipeline's, so a workflow that keeps publishing keeps being what gets deployed, and the pipeline's digest is only used once nothing higher-ranked has been observed for that commit. Retire the workflow deliberately, once you have watched the pipeline publish correctly on its own for a while.

Two rules for the overlap:

* Do not make `Ankra pipeline` a required check in branch protection until it has been green for as long as you would trust any new CI system, and never while its stages include a kind that does not execute yet. Such a stage is skipped, not failed, so the check goes **green** having done none of what the stage names - read the run's skipped stages, not only its conclusion.
* Retire the workflow deliberately. Ankra does not delete a workflow for you; when it proposes retiring a generated one, it will be a reviewable pull request that you merge.

> **[Ankra Pipelines](https://docs.ankra.ai/guides/ankra-pipelines.md)**
>
> Prerequisites, the first green run, the security model and limits.

> **[pipeline.yaml reference](https://docs.ankra.ai/guides/pipeline-reference.md)**
>
> Every key, the expression language and the diagnostics the validator reports.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.