.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.
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:- Reads what builds the application today and converts it into
.ankra/pipeline.yamlthrough the same converter Ankra AI uses, then stores that as the application’s pipeline definition of record. - Records the application as building on Ankra Pipelines (
pipeline_sourcebecomesankra_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-migrationthat 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.
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. Example prompts:- “Convert
.github/workflows/ci.ymland.github/workflows/build-deploy.ymlinto an Ankra pipeline and show me every note.” - “Convert this
.gitlab-ci.ymlinto.ankra/pipeline.yamlfor theorders-apiapplication.”
cicd_convert_workflow_to_pipeline:
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.
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 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
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 default image, which then has to ship the toolchain.
Steps that are dropped, with a note
Everything else
A third-party step the table does not know becomes arun stage that stops the run and says what has to be replaced:
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
permissionsblock is not converted: a run’s authority is the intersection of the pipeline’s ownpermissions, 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.shellis 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 ownpushandpull_requestbranches and paths stay on its stages as event-scopedwhenblocks 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 withwhen.events. A note names the workflows this applied to. - A second
concurrencyblock 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.
workflow:rulesis 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’sservicesblock 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. Arules:block carryingwhen: neveron achanges:list is not converted, and its paths go with it: that rule skips the paths it names, and an Ankrawhen.pathssays which paths the stage runs for, so carrying them across would invert the meaning. Otherwise itschanges:paths are carried, onto the stage’s flatwhen.pathsunless the rule names an event. A rule whoseif:tests$CI_PIPELINE_SOURCE == "merge_request_event"becomeswhen.pull_request.paths, one testing== "push"becomeswhen.push.paths(a matching rule with nochanges:leaves that event unfiltered), and the rest go towhen.paths. Any otherif:condition, and the rules’ order, are not carried, and the note says so.
- A step
sizeof1x,2x,4xor8xbecomes a memory request; any other value is reported and you declareresources.memoryyourself. 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-onlabels are not converted: an Ankra stage is placed by the organisation’s CI cluster and the stage’s ownruns_onblock.- A manual trigger becomes an
approvalstage approvable by theadminandoperatorroles, 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 Ankrapull_requesttrigger and a stage’swhen.branchesmatch 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: trueandclone: enabled: falseare not converted: the checkout stage does not fetch Git LFS objects, and every stage shares the workspace it fills. A step’s ownclone:settings are not converted either, because a run checks the repository out once.
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
approvalstage approvable by theadminandoperatorroles, 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
- The notes, first. Each is a decision the converter took on your behalf or could not take. Stages that fail by design (
exit 1with the reference) are in the YAML so you cannot miss them. - Images. Every stage needs an image the organisation’s image policy allows - and so does every sidecar you declare in the pipeline’s
servicesblock, whose image andenvare 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/admitsghcr.io/my-org/apiand notghcr.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. - Network. A
runstage’s tier defaults tonone, which denies all egress; givedefaults.network: egress-https, or anetworkon the stage itself, to the ones that install packages or reach a registry. The convertedcheckout,buildandscanstages need nothing said about them: those three kinds default toegress-https(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 inci_egress_allowed_cidrsbefore the converted stage runs, and without it fails on a connection timeout rather than a permission error. Remember that anything aboveegress-httpsis a protected section - as are a stage’stimeout,cache,resourcesandwith:block, and the wholeservicesblock. 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. - Secrets and credentials. The converter declares what the workflow referenced; the
secretsblock names a source (app_env_secret,org_variable,registry,credential) that has to exist in the organisation. Delivery is live forsecretssourcedapp_env_secret,org_variableorregistry, and forcredentialssourcedgitorregistry- each revealed once, at step start, to a file the step reads.secrets: [{from: credential}]still validates but is not deliverable yet, and acredentialsbinding sourcedcloudorobject_storageis refused by name until the run has its own scoped identity - reconsider any converted cloud-credential step against that gap. - Filters. Where a
rules:,except:or triggertypesnarrowing was lost, addwhen:orif:so the pipeline does not run more often than the workflow did. - 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.
- Caches, if you add one by hand. A
cache:block does not behave the wayactions/cachedoes 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. - What executes today, and what the converter never adds.
checkout,run,build,scan,gate,publishanddeployall execute end to end;verifyand the kinds with no executor yet (preview,agent,approval,webhook,automation,ankra,external) validate with a warning and are skipped, so a convertedapprovalstage blocks nothing and a convertedwebhooknotifies nobody. Adeploystage releases only to host targets outside Kubernetes, so a converted job that rankubectlorhelmdoes not become one; deploy to a cluster through the application’s push to deploy instead - see What is in place today. The converter itself never writes agateor apublishstage - no source format has one to map from - so a workflow whosedocker/build-push-actionpushed straight to your registry converts into a pipeline whosebuildstage pushes a digest to Ankra’s own CI staging repository only, and stops there: without agatestage afterscanand apublishstage aftergate, nothing ever reaches your real repository. Add both by hand (see the worked example) rather than assuming the converted file publishes what the workflow did.
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 isAnkra 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 pipelinea 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
Prerequisites, the first green run, the security model and limits.
pipeline.yaml reference
Every key, the expression language and the diagnostics the validator reports.