Skip to main content
Most CI time goes on work a change did not need: backend tests for a frontend fix, or a second build of a commit its pull request already built and tested. Ankra Pipelines skips that work with a few lines of .ankra/pipeline.yaml, and never skips anything it cannot prove the change left alone.
Four steps. A pull request runs only the stages its files touch. The image is built once, scanned and gated by digest. The merge train tests the pull request as it will merge. Publish re-tags the tested image instead of building it again. Below them: if Ankra cannot see every changed file, nothing is skipped.

What it saves

Timings from Ankra’s own pipelines.

15 seconds from merge to published

A TypeScript frontend with 20-minute pull request runs. Its last 40 merges re-tagged the image the pull request had already built and tested.

About 3 minutes instead of 19

A Go and Next.js repository. A pull request that does not touch the backend skips its 17 minutes of tests.

About 60 CPU minutes per merge

A Go, TypeScript and Rust monorepo. Three rarely touched stages, one a 10-minute suite, now run only when their files change. Estimated from stage timings.

Which technique to use

They combine. A typical setup is pull-request-only filters on the expensive tests, promote_from on publish, and the merge train on the default branch.

Skip documentation-only changes

A trigger paths list that starts with ! means “everything except”. A change that only touches the excluded files runs no stage, and the run is recorded as skipped. Name the list once in path_sets: and both triggers use it.
A pull request that changes only docs/intro.md and README.md is skipped with the reason “Every file this event changed falls outside the paths this pipeline’s trigger declares.” One that also changes api/README.md runs, because *.md matches only files at the repository root.
  • Patterns match file paths from the repository root: api/**, never ./api/** or /api/**.
  • * matches any characters except /, ** matches any characters including /, and ? matches one character.
  • A pattern matches whole file names, so docs/ matches nothing. Write docs/**.
  • ! removes what a pattern matches, and the last pattern that matches a file decides: ["web/**", "!web/**/*.md"] is the web directory without its Markdown files.
  • Matching is case-sensitive.
  • Exclude a file type everywhere (**/*.md) only when nothing your build or tests read has that type. Code that embeds Markdown prompts or templates needs its tests to run when they change.
  • ankra pipeline validate warns about a pattern that can match no file (a leading ./ or /, a trailing /, a backslash) and names the pattern to write instead.

Run only the tests a change touches

Filter each expensive test stage on the files it covers. Choose whether the filter applies to pull requests only or to every event.
Pull requests skip the tests of components they do not touch, and every push to main still runs everything. This is the safest place to start, and the technique behind the “About 3 minutes instead of 19” example.
A pull request that changes only frontend/src/app.tsx skips backend-test with reason path_filter. A stage that needs a filtered stage is skipped with it, unless it lists it in skippable_needs (see Later stages and conditions).

Build once and promote on merge

Publish the image the pull request run already built, scanned and tested, instead of building the merged commit again.
A promoted push runs publish alone: it re-tags the gate-approved digest as the push’s own sha-<7> tag, and push-to-deploy rolls it out. The other stages show as skipped with reason promoted, naming the run that passed them. This is the “15 seconds from merge to published” example. A push is promoted when its commit has the same git tree as a pull request run that passed every stage publish needs, under the same approved pipeline authority, within the last 72 hours, from a branch in the repository itself. Anything else builds and tests in full, and the run records why. A stage that a pull-request-only filter skipped did not pass, so that merge builds in full too.
promote_from is a protected section: an administrator approves it with the default branch’s definition, and a pull request cannot turn it on for itself. Pipelines Ankra generates for an application already declare it when their shape allows. A publish that meets a registry timeout or an outage status is retried for up to five minutes before the step fails.

Test each pull request as it will merge

The merge train tests each pull request squashed onto the latest main, then merges it through GitHub’s own merge. When that run passed every stage publish needs, promote_from publishes its image on the merge.
  • A train run uses the pull request’s own list of changed files, so the same filters apply. A train run with nothing left to run passes, and is never a source for promotion.
  • --max-cars 3 on enable tests three pull requests at once. One that fails leaves the train, and the ones behind it are tested again without it.
  • Make the Ankra merge train check required on main, and every merge goes through the train.
The merge train runs on GitHub repositories. See the merge train for what happens when main moves or a car fails.

Run slow suites nightly

Run a suite that is too slow for every pull request once a night, and cancel a branch’s older run when a newer commit arrives.
The schedule’s stages list restricts the nightly run to those stages, so name every stage they need, checkout included. when.events: [schedule] on the suite keeps it out of push and pull request runs. A cancelled older run shows as Superseded, with a neutral commit status and no failure notification.

Write each path list once

A monorepo filters many stages on the same directories, and most of those lists also name the pipeline file. path_sets: names each list once. Any path list includes a set with a {set: <name>} entry, and a set can include another set.
Ankra replaces each reference with the set’s patterns where it stands, when it reads the file, so the plan is exactly the plan of the lists written out by hand. Changing pipeline changes every list that includes it. ankra pipeline validate refuses a reference to a set that does not exist and sets that include each other, warns about a set nothing uses, and lists every set with its patterns and the stages that use it. See Path sets.

Run everything when you need to

Skipping is the default only where you asked for it, and you can always set it aside.
  • One run in full. ankra pipeline run --full or ankra pipeline rerun <run> --full runs every stage, whatever the path filters say.
  • A pull request in full. Add the ankra:full-run label on GitHub. A full run starts at once, every later push to the pull request runs in full while the label stays, and so does its merge train car.
  • Main in full. Add a full_run block, or have an organisation administrator turn on ci_full_run_on_default_branch:
  • A stage that is never skipped. List it in the organisation’s ci_always_run_stages. It runs with every stage it needs, whatever its path filters or the trigger’s say, and a pull request cannot change that, because the list is not in the repository.
  • Nothing at all. A pushed commit with [skip ci] in its message records a skipped run. By default that works on every branch but the default branch; ci_skip_markers changes where.
Each full run says why on the run page, for example “Path filters were set aside: a full run was requested by user:…”. See Setting path filters aside for every source and setting.

Why a skip never hides a failure

A path filter only skips a stage when Ankra has the complete list of files the change touched. When it cannot be sure, the stage runs, and the run’s paths_unverified says why.
Pushes and pull requests are filtered the same way on GitHub, GitLab and Bitbucket Cloud, up to 300 changed files.

Preview before you commit

1

Plan the pipeline for a change

ankra pipeline validate plans the pipeline for a push and a pull request without running anything. Each --changed-file makes the path filters decide for real.
2

Read the plan

The output ends with every stage against every event. For the Every event pipeline above:
3

Try a few typical changes

Run it for a documentation edit, one component and two components. The command also reports every refusal and warning above, and exits non-zero only when the pipeline cannot be planned. For stages with affected:, run it inside your checkout: it reads the same dependency graph from your working tree and marks each one run (graph: affected) or skip: not affected (graph).

See what you saved

Ankra records the CI time each run did not spend because of a path filter or a promoted publish, and shows it in three places.
  • The run page says what the run skipped, for example “Skipped 3 stages, saved about 14 min”.
  • The Pipelines page shows the total for the last 30 days.
  • ankra pipeline get <run-id> prints the same figure under the run’s steps, with one line per saving.
Each figure is an estimate from the stage’s own recent successful runs in the same repository. A stage that has no successful runs yet shows as unknown and is not counted. A stage skipped by a branch filter or a condition is not counted either, because it would not have run anyway. The figure is runner time, so two stages that would have run side by side count twice. Only runs planned after this was released are counted. The pipeline.yaml reference describes the method and the API routes.

Next steps

Ankra Pipelines

How a run happens, the merge train and affected-only stages.

pipeline.yaml reference

Every key, event-scoped filters, expressions and validator diagnostics.

Promote a tested image

Every condition a push must meet to publish a pull request run’s image.

Get started with Ankra CI

Connect a repository and get its first commit through a run.