Applications connect to GitHub repositories and use a GitHub credential. By default the generated CI/CD pipeline pushes the container image to your organisation’s private Ankra registry; if you already run your own registry, declare it and Ankra publishes to and reads from it instead.
Watch a repository go from source to a live URL with one command.
How it works
1
Connect a repository
Provide a name and a GitHub credential, then pick the repository and a branch from the repository’s own branch list - the default branch is preselected, and Enter a different branch lets you type one that does not exist yet.
2
Analyze and generate
Ankra inspects the repository, detects the language and framework, and generates the artifacts it needs: a Dockerfile, Kubernetes manifests, and a CI/CD workflow.
3
Merge the pull request
Review and merge the PR. Merging activates the CI/CD pipeline in your repository.
4
Build and publish
CI builds and pushes the container image to the organisation’s private Ankra registry. Ankra surfaces the image URL and scans the published image.
5
Deploy
Deploy the application onto a cluster. Ankra verifies that the image was published and tracks the rollout.
Building on Ankra’s builders
The flow above puts the first image behind a merged pull request: until the generated workflow is on your default branch, your CI has nothing to run, and nothing to publish. Ankra can also build the image itself, on its own builders, with no workflow in the repository at all. Ankra clones the commit, resolves a recipe for it, builds it, and pushes the image to the same registry the application publishes to. The recipe is the first of these that fits:- The repository’s own
Dockerfile— used as-is. - A generated Dockerfile — the one Ankra wrote for this application.
- Buildpacks — when there is no Dockerfile to use.
--commit takes a full 40- or 64-character commit sha. The queue deduplicates on the string it is given, so an abbreviation would be a second key for the same commit and would build it twice. Asking twice for one commit joins a single build rather than racing two, and the answer’s already_requested says when that happened.Following a build
A request is not yet a build.start answers with a request id immediately; the build itself is created when a builder claims the request. So there are two things to read:
ankra application build start --wait walks both steps for you and exits non-zero if the build failed, which makes it usable as a pipeline gate. Watching the build list instead is a trap: a previous failed build of the same commit is still the newest one for that commit, so it would report an old failure as your build’s result.
Waiting for a builder
Ankra’s builders are one pool that every organisation shares. Your organisation runs at most three builds on it at a time, so a burst of builds from one organisation - onboarding a repository with many components, say - cannot hold every builder while others wait. Requests beyond three staypending, in the order they were made, and the next one starts as soon as one of your running builds finishes. A pending request is waiting its turn, not failing: it keeps its request id, and --wait keeps following it.
What a failure means
A failed build carries anerror_class that says whose failure it was:
The last three are platform-side and are already visible to Ankra without anyone reporting them.
This replaces the image build, not the whole generated pipeline. That workflow also runs a Semgrep scan, and packages a Helm chart for applications that publish one. If you plan to retire the workflow entirely, decide where those two go first.
The application workspace
An application opens as a workspace: a context sidebar beside the main one, in the order the work happens, with one page per section at/organisation/applications/<application-id>/<section>. A link to an older ?tab= address redirects to its section.
The sidebar carries two badges: Security shows the CISA-listed finding count once the KEV catalog has synced, and never before; Environment shows the declared keys still without a value.
The Applications list has two ways in: All applications, or Needs attention - a failed setup, an unmerged setup pull request, a failed deploy or a degraded one. Setup in progress, deploying, parked, resting, and a runtime Ankra could not read are not attention.
What Ankra tracks
For each application you see:- State and analysis status - where the application is in the connect/analyze/generate/build lifecycle, with an error message if something needs attention.
- Repository - owner, name, branch, and URL.
- Components - the repository’s deployable apps. An ordinary repository has one; a monorepo has one per app.
- Artifacts - the private container image URL and the latest published build, per component.
- Pull request - a link to the generated PR.
- Jobs - the underlying platform jobs for the application, so you can follow analysis and generation as it runs.
Monorepos
A repository that builds more than one deployable app is onboarded as a monorepo: Ankra records one component per app, and each component gets its own packaging and its own image.
Ankra decides the components from, in order: the per-component workflows the repository already carries (so re-running analysis never renames a component and orphans the images it has published), the apps the analysis proposed, then the repository’s structure - two or more Dockerfiles in subdirectories and no Dockerfile at the root, or a workspace marker (
nx.json, turbo.json, lerna.json, pnpm-workspace.yaml, go.work) plus two or more subdirectories with their own dependency manifest. A Dockerfile at the repository root always means a single app, whatever else the repository contains.
Build state, published images, deploys, demos, and container scanning are all reported per component, so an application whose API component built and whose frontend did not says exactly that.
Using your own registry
An application publishes to your organisation’s private Ankra registry unless you say otherwise. If you already operate a registry - the same Harbor whose OCI charts Ankra indexes, for instance - declare it on the application and Ankra publishes there, reads the published tags back from there, and pulls from there. Declare it on an existing application from Settings → Image registry, from the CLI, or withimage_registry when you create the application. The settings panel also reports the host and project the declaration resolved to and the image repository each component is expected to publish to, so you can compare them against where your builds actually push.
Ankra does not mint robots for a registry you operate unless you hand it the keys. With a declared registry and no
admin_credential_name, setup names the two Actions secrets the workflow reads and leaves them to you, rather than writing a credential Ankra happens to hold over a push robot you administer. Publish readiness reports whether those secrets exist and names them. Name an admin_credential_name to have Ankra mint a push robot for the application there (Robot accounts), or set manage_actions_secrets: true to have it write the declared credential into them instead.
An application is bound to the GitHub credential it was created with; its App installation must reach the repository for builds, deploys and Actions secrets. Move an application onto another credential of the organisation with
ankra application credential set <application-id> --credential <name> (GET/PUT /api/v1/org/applications/{application_id}/repository-credential); the answer reports whether the new binding resolves to an installation.On a monorepo, a component may declare its own
image_registry to publish into a different project of the same registry - useful when each app has its own robot accounts and retention rules. A component’s declaration wins over the application’s; the application’s applies to every component that declares none.Robot accounts
Every application gets a registry robot account of its own: a push+pull robot namedrobot$<project>+app-<application-id> on the project it publishes to, stored as the platform-managed registry credential ankra-harbor-app-<application-id> and written into the repository’s Actions secrets as the login the build workflow uses. Nothing else shares that login, so a leaked repository secret is rotated or revoked for that one application, and a deleted application takes its robot with it.
- On your organisation’s Ankra registry the robot is minted automatically when the application is set up, and again whenever the application is reconciled. Applications onboarded before robots existed get one the first time Fix automatically runs in the Pipelines section, or when you create it from Settings → Image registry.
- On a registry you operate, Ankra mints one only with a credential that may: set
admin_credential_nameto a registry credential with project administrator rights on the declared project (in Harbor, a project admin user, or a robot granted the robot-account permissions). Without it Ankra never touches your robots; publish readiness names the field.
/api/v1/org/applications/{application_id}/registry-robot (GET, POST with {"rotate": true|false}, DELETE). The robot’s credential appears in your organisation’s registry credentials as managed by Ankra; it cannot be edited or deleted there, only through the application.
Security scanning
Applications include code and container security insights, so vulnerabilities surface alongside the build rather than in a separate tool. The Security section reads the way the Security Center reads a fleet finding - risk first, ranked by exploitability - in eight views:
Every finding row opens a sheet with severity, CVSS, EPSS (as
EPSS 94% · top 1%), the CISA KEV entry - date added, due date, ransomware use and required action - the fix, and a link to the platform’s own advisory page for the CVE. Exploitability reads Unknown until the KEV catalog has synced; absent is never read as not exploited.
On a monorepo, the image tags and the scanned image reference follow the component you select; code and IaC findings cover the whole repository, because that is where those scanners run. Pair this with AI Insights for proactive analysis.
Preview demos
Before you merge, you can spin up a throwaway demo of a pull request or branch build to see it running. In the portal these live in the application’s Previews section, which calls each one a preview; the CLI and API keep thedemo name. Each demo is deployed into its own isolated namespace (ankra-demo-pr-<n> or ankra-demo-br-<branch>) on the organisation’s staging cluster, PodSecurity-hardened and quota-bounded, and is automatically torn down when its TTL expires - so it can never affect existing workloads.
1
Configure a staging cluster
An admin sets the organisation’s staging cluster under AI → Settings → Workspaces. Optionally set a demo base domain (with an ingress class and TLS secret) there too - this is what gives demos a public URL.
2
Deploy a demo
Deploy a branch or PR demo from the application’s Previews section, from the CLI, or by asking the AI assistant. The demo pulls the image tag the PR/branch build pushed.
3
Open the preview
When a public host is available, Ankra returns a preview URL you can open directly. Otherwise the demo stays reachable in-cluster (service DNS + a
kubectl port-forward command).The preview URL
Ankra resolves the demo’s public hostname automatically, and every surface (portal, CLI, MCP, and automatic PR previews) uses the same rule:- An organisation demo base domain, if configured, wins outright - the host is
<namespace>.<demo-base-domain>, served with your configured ingress class and TLS secret. - Otherwise, the staging cluster’s own delegated DNS zone, but only when that zone is active - giving
<namespace>.<cluster-id>.<org-id>.<ankra-domain>, a hostname theexternal-dnsrunning on that cluster can resolve. When the cluster also carries the Ankra networking stack, the demo’s ingress requests a Let’s Encrypt certificate from itsletsencrypt-prodissuer automatically, so the preview URL is served over https. - Otherwise the demo stays in-cluster-only - no ingress is created, and you reach it with the returned service DNS name and
kubectl port-forward.
A demo only gets a resolvable public URL when the organisation has a demo base domain configured or the staging cluster has an active Ankra DNS zone. Without either, the demo still deploys - it just stays in-cluster-only.
ankra.cc by default. An organisation can register its own custom domain - dedicated to that organisation alone - in the Custom Ankra domain field under AI → Settings → Workspaces, or from the CLI with ankra org domain set <domain>. The domain must be a bare domain the organisation owns, its NS records must already point at Ankra’s nameservers (the ones serving ankra.cc), and no other organisation may hold it.
With a custom domain registered, the external-dns Ankra runs on each of the organisation’s clusters is scoped to the whole domain, not just that cluster’s delegated <cluster-id>.<org-id>.<domain> zone. An Ingress on any hostname under the domain - a top-level name like app.example.com included - is published automatically by the cluster serving it. On the shared ankra.cc root the scope stays the cluster’s own delegated zone: names directly under ankra.cc are allocated by the platform, and one organisation’s cluster must never be able to write another’s.
Zones already provisioned keep the domain they were minted under, so the switch is refused while any cluster DNS zone or DNS record still lives under the old domain. The refusal names exactly what is blocking it - the portal lists the cluster domains and records beside the field, and ankra org domain set prints the same list - so the migration is a checklist rather than a guess:
1
Inventory what is under the current root
ankra org dns zones lists every cluster domain in the organisation with its state; ankra org dns list lists the DNS records. Both are reads. In the portal the same two lists live under Organisation → Settings → DNS records.2
Destroy any playground environment
A playground publishes a wildcard DNS record in the organisation’s zone, and that record is reconciled - the platform writes it again on its next pass over a ready environment, so deleting it in the next step will not clear it. Destroy the environment instead:
ankra cluster playground destroy <cluster_id>. The refusal names any playground that is still holding a record.3
Remove each cluster domain
ankra cluster domain <cluster> --remove (or DELETE /api/v1/clusters/{cluster_id}/dns-zone, or Remove on the cluster domain row in the portal) hands the zone to Ankra for teardown.The removal is held: nothing re-creates the zone until you ask for it back in the last step - not the external-dns Ankra runs on that cluster, and not the discovery that mints zones for clusters that have none. ankra cluster domain <cluster> reports Opted out: yes for as long as the hold stands, so a domain that is gone is distinguishable from one that is between reconciler passes.4
Delete the DNS records
ankra org dns delete <record>, or delete them from the DNS records table. These are the records you created; anything the platform publishes is listed separately in the refusal, with what to remove instead.5
Register the new domain
ankra org domain set example.com, or save the Custom Ankra domain field. Ankra tears the organisation zone down and re-creates it under the new domain automatically.6
Re-enable the cluster domains
ankra cluster domain <cluster> --enable withdraws the hold and re-creates each cluster’s zone under the new root, and the cluster’s external-dns picks up the new zone on its next cloud-provider stack pass. Every cluster you removed in step 3 needs this, the staging cluster included.ankra cluster domain <cluster> on its own is a read: it reports the cluster’s domain, or state none when it has none, and never creates a zone. Creating one is the explicit --enable. (Before CLI v0.13.0 the bare command created a zone, which meant checking a cluster’s domain could add a blocker to this very migration.)What a root-domain switch does and does not touch
Two questions decide whether the switch is safe to attempt on a domain that is already in use. Both have short answers. Ankra touches only the records it creates in the domain you register. Registeringexample.com adopts it as the organisation’s zone apex, with each cluster’s own <cluster-id>.example.com subzone under it, and the external-dns Ankra installs on every cluster is scoped to the whole domain so it can publish top-level names like app.example.com. Each controller runs with its own record ownership (--txt-owner-id), so it creates, updates and deletes only the records that exist because of an Ingress on its cluster: records you already publish at the apex or under any other name in example.com are never read, written or deleted - not by the switch, and not by anything Ankra runs afterwards.
A switch never re-labels a zone. Zone labels are derived from the organisation and cluster ids, so they are the same under any root: a cluster that was axmndle4sl.<org>.ankra.cc becomes axmndle4sl.<org>.example.com. The label is also what Ankra passes as external-dns’s --txt-owner-id, and what any GitOps path built from a cluster domain embeds - so the TXT ownership registry stays matched to its records across the switch, and the paths do not move. --enable re-creating a zone preserves the label for the same reason.
Serving a zone in your own DNS account from every cluster
A custom Ankra domain has to be hosted in Ankra’s own DNS account, because Ankra mints the per-cluster credentials for it. A zone you keep in your own DNS provider account is different: Ankra cannot mint anything there, so theexternal-dns it installs drops Ingress hostnames on that zone silently (the sync logs All records are already up to date, because from its point of view there is nothing in scope), and cert-manager’s HTTP-01 challenge for such a hostname waits forever for a name that never resolves.
Declare the zone with a credential of your own instead, once, for the whole organisation:
external-dns for example.dev, rendered and reconciled by Ankra in a dedicated custom-dns stack, within about two minutes of the declaration or of the cluster coming up. An Ingress on chat.example.dev publishes from whichever cluster serves it, and the certificate follows once the name resolves. Each cluster’s controller is pinned to exactly that zone (--domain-filter) with its own record ownership, so it can never fight the controller Ankra runs for the cluster’s Ankra domain, another cluster’s controller, or records you publish in the zone yourself.
- The credential is the external-dns webhook provider URL including its token, for any provider that speaks the external-dns webhook protocol. It is written to the platform’s secret store on create and no read surface returns it; re-creating it under the same name re-points every cluster at once, which is how a rotated token rolls out. Its name is what
listshows. ankra cluster custom-dns-zones add <cluster> --zone <zone> --credential <name>declares the same thing for one cluster. A cluster’s own declaration of a zone takes precedence over the organisation’s on that cluster - the way to serve a zone with a different credential on one cluster - andankra cluster custom-dns-zones list <cluster>shows where each zone a cluster serves was declared (SOURCE=clusterororganisation).- A zone that overlaps the domain Ankra already serves for the organisation (its
<org-id>.<ankra-domain>zone, or the whole custom Ankra domain when one is registered) is refused: Ankra’s own controllers publish there already and cannot be told to stay off those names. A zone above the Ankra domain is accepted, and each cluster’s controller is told to leave the Ankra-served part alone. - A domain in Avura needs no credential of your own: once the Avura workspace is connected to the organisation and the domain is shared with Ankra, declare it with
--credential avura. See use Avura domains with Ankra. ankra org custom-dns-zones remove --zone example.devwithdraws the declaration. Ankra tears down the controllers it rendered on every cluster that inherited the zone; clusters that declared it themselves keep theirs, and the zone’s records are yours and are left untouched.
GET/POST /api/v1/org/custom-dns-zones, DELETE /api/v1/org/custom-dns-zones/{zone}, and the per-cluster /api/v1/clusters/{cluster_id}/custom-dns-zones twins.
Deploying a demo
A demo deploys every component of the application by default. A single-app application runs its one image; a monorepo runs one pod per component — the frontend and the API of a two-app repository both come up, wired together, instead of half an application answering where the other half belonged. You can still deselect components or demo a single one.- Portal
- CLI
- AI / MCP
Open the application’s Previews section, choose Launch preview, pick a branch or enter a pull request number, and deploy. The dialog groups what to preview, its lifetime and environment, and the staging cluster, and lists the branch, image, lifetime, template, resources, cleanup and access in a summary before the launch button; a missing image or an offline staging agent is an explicit blocker. The section shows one card per active preview with its branch or commit, component, image, expiry, access and cluster. Each preview links to its own detail page - live provisioning progress read from the staging cluster, the namespace’s bill of materials with manifests (grouped per component), Kubernetes events, and pod logs, plus the preview URL or port-forward command to reach it. On a monorepo the launch dialog lists every component with its own build status and tag, lets you include or exclude each, and marks the web entry — the component that owns the demo URL.
Monorepo demos: every component, one URL
A monorepo demo deploys each component as its own Deployment and Service inside the demo namespace, and the demo only reports ready once every component accepts connections:-
The web entry owns the demo URL. Ankra picks the frontend-shaped component (a name or directory like
frontend,web,ui,portal) as the entry serving/on the demo host; you can move the entry in the launch dialog or withentry_componenton the API. -
API components share the host under a path. The single API-shaped component (
api,backend,server) is published under/apion the same demo host, routed straight to its Service — so a browser calling/api/...reaches the API even when the frontend’s own proxy target was baked for another environment. Override per component withingress_path. -
Save the routing when the guess is wrong. The entry and the
/apipath above are heuristics, and the lanes with no human in the loop — the automatic PR previews and the MCP demo tools — have no launch dialog to correct them in. Declare the routing once in the demo configuration and every lane follows it:A declaration is authoritative: the/apiguess stops running, so a component you leave out ofcomponentsstays reachable in-cluster only rather than picking up a path it never asked for. Per-launchingress_pathoverrides still win over the declaration. Clear it with"routing": nullto return every lane to the heuristics. -
Components reach each other by name. Every component’s Service is named after it, so in-namespace URLs like
http://crm-api:8090resolve. Env values can also use placeholders that resolve at deploy time:
The saved migration command runs once per deploy, inside the image of the component that owns the schema (the primary component - usually the backend). Demo environment defaults and the throwaway database are shared by every component of the demo.
Existing demos and single-app applications are unaffected: a demo recorded before multi-component support (or of an application with one component) keeps exactly the previous single-pod shape.
Environment variables and a throwaway database
Most real applications need configuration to boot — a database name, an API key, an SMTP host. Demos support both per-application defaults and per-launch overrides, so any codebase can run as a demo without changes:- Defaults are the application’s environment template, under Previews (Preview settings), which separates the disposable dependencies each preview gets from the shared services it points at and labels every generated value with the dependency it comes from. Every demo of the application inherits them — manual launches, CLI/MCP deploys, and the automatic PR previews.
- Overrides are set per launch in the Environment & database section of the launch dialog (or via the
envargument ofdeploy_pr_demo). Overrides win by name.
The throwaway database is ephemeral by design: it starts empty on every deploy and is wiped with the namespace. The database runs a pgvector-capable Postgres, so migrations using
CREATE EXTENSION vector work. .url and .password references are always delivered through a Kubernetes Secret, never as plain env text.migrate_command) — for example pnpm run db:migrate or alembic upgrade head. It runs inside your application image (sh -c) as an init container, after the database accepts connections and with the same environment the app sees, so a fresh demo database provisions its schema before the first request. Because every deploy starts the database empty, the command re-runs on every deploy — write migrations to be idempotent (every standard migration tool is).
The command runs with your image’s
WORKDIR as the working directory and on PYTHONPATH. Python puts the script’s directory on sys.path, not the working directory, so a migration entrypoint that is a script rather than a console script — python scripts/bootstrap_database.py — could not import the application package sitting beside it and died at import with ModuleNotFoundError. Setting PYTHONPATH yourself in the command still wins.database_extensions (for example ["vector"]) and the demo database creates them at initdb, before your migrations run.
Ankra detects both automatically when it analyses a repository: a Postgres client dependency, an ORM configuration, or a composed database marks the application as database-needing, its referenced connection variables are wired to the ${{ ankra.demo_database.* }} placeholders, and a recognised migration script becomes the migration command — so the first demo of a database-backed application works without any manual configuration. Detection only ever seeds an application that has no demo configuration yet; it never overwrites what you or the pre-setup agent saved.
The demo container port
The demo’s readiness probe, Service, and preview route all target one container port, resolved in this order: the component’s recorded port, the generated runtime Dockerfile’sEXPOSE, the generated Deployment manifest’s containerPort, the analysed target_port parameter, and only then the platform default. The launch dialog shows the resolved port and where it came from; a port you did not edit is left to the platform to resolve.
When the resolved port is still wrong — stale analysis, a hand-edited Dockerfile — the platform corrects itself at runtime: a demo whose container runs cleanly but never accepts connections has its logs read for the port the server actually announces (Accepting connections at…, Listening on…, and the other common startup banners). If the evidence is unambiguous, the demo is repointed at the announced port on the fly, the correction is recorded on the application so the next launch resolves it statically, and the demo detail page shows the correction. Ambiguous evidence never auto-corrects; it flows into the failure message (“Nothing accepted connections on port 3000. The container’s logs say it listens on port 8001.”) and dispatches the pre-setup agent instead.
Automatic pre-setup when a demo crashes
An image that validates its environment on boot — refusing to start without a database URL, an auth secret, an API key — crash-loops when demoed with no configuration. Ankra now detects this and fixes it with an agent:- When a demo fails with a startup crash (CrashLoopBackOff or a container configuration error), a failed migration command, or a port-evidence timeout (the container announced a different port than the demo probed), Ankra dispatches a one-shot pre-setup agent for it automatically. The run appears on the AI agents page like any other mission.
- The agent reads the crashed container’s logs, works out which environment variables the application demands, and generates a pre-setup: missing database URLs become
${{ ankra.demo_database.* }}references with the throwaway Postgres enabled, secrets get fresh random values, and mode flags get the value that avoids external side effects. - It saves the pre-setup as the application’s demo defaults — so every future demo inherits it — and redeploys the failed demo to prove it boots.
CREATE EXTENSION failures. It never weakens the application’s own validation, never reuses values found in logs, and merges with existing demo settings rather than replacing what you configured. Dispatch is bounded to one run per demo per day; image-pull failures and provisioning timeouts never dispatch (no environment can fix those). You can also trigger it on demand with POST /org/applications/{application_id}/demos/{workspace_id}/fix, or just ask the AI assistant to fix the failed demo — the chat has the same get_demo_diagnostics, update_application_demo_config, and redeploy_demo tools the mission uses.
Managing applications
Which Ankra AI lanes run on the application’s repository - the pull request review, the organisation skills review, the automatic preview URL - is set per application under Settings → Ankra AI. See Application AI settings.
Built the app with Claude? Watch it shipped to Kubernetes on Ankra.
Prerequisites
1
Connect GitHub
Add a GitHub credential with access to the application’s repository. The Ankra GitHub App needs permission to open pull requests, commit workflow files, and manage Actions secrets. Ankra installs the Ankra registry credentials on the repository automatically; for your own registry you set the login secrets yourself.
API
Applications are available over the API for CLI and scripted use, under/api/v1/org/applications (bearer-token authenticated) - create, list, inspect, retry, reconcile, and delete. See the API Reference for endpoints and schemas.
The same lifecycle is available through Ankra’s AI and MCP clients - connect, deploy, retry, reconcile, and delete applications, and follow their CI workflow runs - see the MCP Tool Reference.
See the CI/CD Pipeline guide for how the generated pipeline fits into GitOps.