> ## 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.

# Harbor as your application registry

> Publish application images to a Harbor you operate, with Ankra minting, rotating and revoking a push robot per application and wiring the pull secret into every cluster.

An application publishes to your organisation's private Ankra registry unless you say otherwise. If you already run Harbor, declare it on the application and Ankra publishes there, reads the published tags back from there, and pulls from there - including minting the robot account the build logs in with, if you let it.

This guide is the operator's side of [Using your own registry](https://docs.ankra.ai/concepts/applications.md#using-your-own-registry): what Harbor needs to look like before you point an application at it, what Ankra creates once you do, and how to check it from the Harbor end.

***

## The two credentials

Ankra talks to your Harbor with [registry credentials](https://docs.ankra.ai/platform/credentials/registry.md) of your organisation. A declaration names up to two of them, and they do different jobs.

| Credential | Declared as | What Ankra does with it | Rights it needs |
| - | - | - | - |
| **Read / pull** | `credential_name` (`--credential`) | Reads published tags for publish readiness, the deploy gate, image listings and demos, and builds the `dockerconfigjson` pull secret every cluster uses | Pull and read on the project: `repository` pull/list/read, `artifact` list/read, `tag` list, `project` read |
| **Project administrator** | `admin_credential_name` (`--admin-credential`) | Checks that the declared project exists and creates it when it does not; mints, rotates and revokes the application's own push robot | A **system** robot with the permission set below, or a Harbor **user** with Project Admin on the project (which can create projects only when Harbor's *Project creation* setting is *Everyone*) |

> [!WARNING]
>
> The read credential is what makes a declaration usable rather than merely descriptive. Without one, Ankra records where the images live but cannot read their tags or pull them - so publish readiness, the demo build check and the deploy gate all keep reporting the application as never built, while it publishes healthily to a registry Ankra simply cannot see. It is also what the cluster pull secret is built from: declare a registry with no read credential and no pull secret is created at all.

**Ankra does not mint robots for a registry you operate unless you hand it the keys.** With no `admin_credential_name`, setup names the two Actions secrets the workflow reads and leaves them to you; your robots stay yours and untouched.

***

## What Harbor needs first

**1. Have the project, or let Ankra create it**

Ankra publishes into the project the declaration's `oci://<host>/<project>` names. With an `admin_credential_name` it checks the project exists when you declare it, and creates it if it does not - private, or public with `--project-public` - when that credential may create projects. A credential that may not is refused with the project and host named, so a declaration no build could publish to is never accepted. Without an admin credential nothing is checked: create the project yourself, and decide whether it is public or private - Ankra works with either, and a private project is why the pull secret exists.

**2. Mint the project-administrator credential**

The account behind `admin_credential_name` is what Ankra checks and creates projects with, and mints robots with. A **system-level robot** is the tidiest; this is the minimal set that does everything Ankra asks of it, by level:

| Level | Resource | Actions | Used for |
| - | - | - | - |
| System (`/`) | `project` | `list`, `create` | Checking whether the declared project exists (the listing filtered by name) and creating it when it does not |
| System (`/`) | `robot` | `create`, `read`, `list`, `delete` | Minting, probing and revoking the application's push robot |
| Project (`*`) | `project` | `read` | Reading a project Ankra can already see |
| Project (`*`) | `repository` | `push`, `pull`, `list`, `read` | Granting them to the push robot Ankra mints: Harbor lets a robot grant another robot only the rights it holds itself |
| Project (`*`) | `artifact` | `create`, `read`, `list` | Likewise, for the robot's re-tag of the published digest |
| Project (`*`) | `tag` | `create`, `list` | Likewise, for the `sha-<7>` tag and any `publish.tags` |
| Project (`*`) | `robot` | `create`, `read`, `list`, `delete` | Robot accounts on the project |

The admin credential itself never pushes an image; the `repository`, `artifact` and `tag` rows exist because Harbor caps a robot a robot creates at its creator's own permissions, so the admin robot must hold everything it will grant the application's push robot. Harbor refuses `project:read` on a system robot (`bad request permission: project:read`), and project-level read on `*` covers only projects that exist. Ankra therefore checks a project it cannot read by listing projects by name, which system `project:list` covers; a name the listing lacks is created with `project:create`. Store the robot as a registry credential in Ankra.

> [!WARNING]
>
> A **project-scoped** robot cannot administer robots: Harbor's project-level robot permission set has no `robot` create/update/delete, so a declaration whose `admin_credential_name` points at an ordinary project robot fails to mint the application's robot - with a permission error from Harbor, not from Ankra. A Harbor **user** with Project Admin works for an existing project, and can create a missing one only when Harbor's *Project creation* setting is *Everyone*.

**3. Store a read credential**

Create the pull account - a system robot with pull/list/read across the projects Ankra reads is the tidiest - and store it as a second registry credential. This is the one that ends up inside every cluster's pull secret, so scope it to pull, not push.

***

## Declaring it on an application

Declare the registry when you create the application where you can: the setup job generates the build workflow from the declaration the application is created with, so a registry added afterwards leaves a workflow that logs in to the wrong one until the application is reconciled and its setup pull request merged.

```bash theme={null}
# At creation
ankra application add . \
  --registry-url oci://artifact.example.com/commerce-images \
  --registry-credential example-harbor-pull \
  --registry-admin-credential example-harbor-admin

# On an existing application
ankra application registry set <application-id> \
  --url oci://artifact.example.com/commerce-images \
  --credential example-harbor-pull \
  --admin-credential example-harbor-admin \
  --pull-secret harbor-registry

# What the declaration resolved to, and where each component is expected to publish
ankra application registry get <application-id>
```

Where your Harbor's repository layout is not Ankra's `<project>/<app>/<component>` nesting, say so rather than reorganising Harbor: `--flat-repositories` addresses components as `<project>/<component>`, and `--component-repository backend=commerce-backend` names one outright. Both are used by publish readiness, the deploy gate and the generated workflow, so they describe what your pipeline already pushes.

***

## The robot Ankra mints

With a project-administrator credential in the declaration, every application gets a push robot of its own.

| | |
| - | - |
| **Name** | `robot$<project>+app-<application-id>` |
| **Rights** | `push` and `pull` on that one project - nothing else |
| **Expiry** | Non-expiring, so a build never fails at 2am because a token aged out |
| **Stored as** | The platform-managed registry credential `ankra-harbor-app-<application-id>` |
| **Delivered to** | The repository's Actions secrets, `ANKRA_REGISTRY_USERNAME` / `ANKRA_REGISTRY_PASSWORD` by default |

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.

```bash theme={null}
# Mint it (or confirm the existing one) and write its login into the repository
ankra application registry robot ensure <application-id>

# Show it: name, registry, created and last-rotated timestamps
ankra application registry robot get <application-id>

# Rotate after a leak - the old secret stops working the moment Harbor answers
ankra application registry robot rotate <application-id>

# Revoke: deletes the robot from Harbor and drops its credential
ankra application registry robot revoke <application-id> --yes
```

The same lives under **Settings** → **Image registry** in the portal, and on the API at `/api/v1/org/applications/{application_id}/registry-robot` (`GET`, `POST` with `{"rotate": true|false}`, `DELETE`).

### Checking it from the Harbor end

Project robots are not returned by Harbor's system robot listing; query them with the project id.

```bash theme={null}
# Find the project id
curl -u '<admin>' 'https://artifact.example.com/api/v2.0/projects/commerce-images' \
  -H 'X-Is-Resource-Name: true'

# List the project's robots (id from the previous call)
curl -u '<admin>' 'https://artifact.example.com/api/v2.0/robots?q=Level%3Dproject%2CProjectID%3D<id>'
```

A healthy application robot comes back enabled, with `expires_at: -1` and exactly `push repository` plus `pull repository` on its own project.

***

## Robot accounts you create yourself

The robots above are Ankra's: one push robot per application, and the organisation's `ankra-harbor-ci` and `ankra-harbor-pull` logins. For anything that logs in to your project without Ankra in the loop - a CI system you run yourself, a laptop pushing an image by hand, a cluster Ankra does not manage pulling from the project - create a robot of your own.

A robot is a named login bound to **one project**: your organisation's own, unless you [pick another](#scoping-a-robot-to-one-project). It is a project-level robot on the registry, so whatever it is allowed to do, it does it on that project and reaches nothing else on the registry. Its secret is shown exactly once, when it is created or rotated. The login is also stored as the managed registry credential `ankra-harbor-robot-<name>`, so clusters and applications can reference it like any other registry credential; it cannot be edited or deleted through the generic credential lanes, only revoked here.

```bash theme={null}
# Mint one. The secret is printed once, with the docker login that uses it.
ankra registry robots create jenkins --description "Jenkins on the office server"

# Pull-only, for a cluster outside Ankra
ankra registry robots create edge-cluster --scope pull

# Script it: the secret rides -o json as `secret`
ankra registry robots create jenkins -o json | jq -r .secret

# What exists (secrets are never listed)
ankra registry robots list
ankra registry robots get jenkins

# Lost or leaked: mint a new secret, the old one stops working
ankra registry robots rotate jenkins

# Done with it: deletes the robot from the registry and drops its credential
ankra registry robots delete jenkins --yes
```

| Field | Value |
| - | - |
| **Login** | `robot$org-<organisation-id>+user-<name>` |
| **Project** | One project: your organisation's own, or an [extra project](#scoping-a-robot-to-one-project) you pick |
| **Rights** | `push` = push and pull; `pull` = pull only; or your own list of permissions |
| **Expiry** | Never, unless you set one when creating it |
| **Credential** | `ankra-harbor-robot-<name>`, managed, revoked with the robot |

On the API the same lives at `/api/v1/org/registry-robots`; creating and rotating need `credentials.write`, revoking `credentials.delete`. Names are 2 to 32 lower-case letters, digits and hyphens.

### Exactly the permissions a job needs

Push-and-pull fits a build and pull fits a cluster. A cleanup job that deletes old artifacts, a release job that moves a tag, or a scanner that reads reports needs something else, and should not be handed a login that can also push. State the permissions instead of a scope:

```bash theme={null}
# What can be granted
ankra registry robots permissions

# A cleanup job: read what is there, delete what is old, nothing else
ankra registry robots create cleanup \
  --permission repository:pull,artifact:list,artifact:delete

# A contractor's pull login that stops working after 30 days
ankra registry robots create contractor --scope pull --expires-in-days 30
```

| Permission | Allows |
| - | - |
| `repository:pull` | Pulling images and charts |
| `repository:push` | Pushing images and charts. Pull comes with it, because a client pushing asks the registry for both |
| `repository:list`, `repository:read` | Listing the project's repositories and reading one through the registry API |
| `repository:delete` | Deleting a whole repository and every artifact in it |
| `artifact:list`, `artifact:read` | Listing a repository's artifacts and reading one: digest, size, labels, scan summary |
| `artifact:delete` | Deleting artifacts |
| `tag:list`, `tag:create`, `tag:delete` | Listing tags, adding a tag to an artifact that is already pushed, and removing one |
| `scan:create`, `scan:read` | Starting a vulnerability scan and reading its report |

Every permission is a right on the **content** of your project. None of them administers the project - its members, robots, quota or retention stay with Ankra - and none of them reaches another project. `--scope` and `--permission` are alternatives; stating both is refused.

Because some of these permissions delete images, creating a robot with any permission other than pull and push needs the `credentials.reveal` permission as well as `credentials.write`. Administrators and owners hold both; a preset robot, or one that lists only pull and push, needs only `credentials.write`.

`--expires-in-days` is enforced by the registry: after that many days the login stops working. Rotating a robot gives it a new secret and never more time, so an expired robot is revoked and created again rather than rotated.

### Scoping a robot to one project

Everything your organisation publishes lands in one registry project, so a robot bound to it can reach all of it. To give a login less, keep some images apart: create an extra project, push those images there, and bind the robot to that project. A robot reaches the one project it is bound to and nothing else on the registry.

```bash theme={null}
# Your projects: your organisation's own is called default
ankra registry projects list

# An extra project for what the staging pipeline builds
ankra registry projects create staging

# A robot that can push and pull there, and nowhere else
ankra registry robots create staging-ci --project staging
```

`ankra registry projects list` prints where to push for each project. An extra project named `staging` is `org-<organisation-id>-staging` on the registry, so its images are `<registry host>/org-<organisation-id>-staging/<repository>`.

| | |
| - | - |
| **Names** | 2 to 30 lower-case letters, digits and single hyphens, starting and ending on a letter or digit. `default` is your organisation's own project |
| **How many** | Up to 5 extra projects per organisation |
| **Settings** | Private, with the same scan policy, retention policy and storage quota as your own project |
| **Robots** | One project per robot. A job that needs two projects uses two robots |
| **Permissions** | Listing needs `credentials.read`, creating `credentials.write`, deleting `credentials.delete` |

Deleting a project never deletes anything else on the way. `ankra registry projects delete staging` is refused while the project still holds repositories, and while robot accounts are still bound to it: delete the repositories from the registry and revoke the robots first. Your organisation's own project cannot be deleted.

Ankra's own lanes keep using your organisation's own project: applications build into it, and clusters pull from it with the `pull` robot. An extra project is for what you push yourself, and a cluster pulls from it with a robot you bind to it, referenced through its credential `ankra-harbor-robot-<name>`.

### Robots on a Harbor you run yourself

If your organisation runs its own Harbor and has connected it to Ankra as an OCI registry, the same commands create robots there too. Name the registry entry, a credential of yours that may manage robot accounts on it, and the **one project** of that registry the robot is bound to. Ankra mints, rotates and revokes the robot with that credential, and never uses its own registry administrator on a registry it does not run. On the **Registry robots** page, pick the registry under **Registry** in the create dialog: the projects it offers are the ones your Harbor holds.

```bash theme={null}
# The OCI registry entries your organisation has connected (a connected
# Harbor is an OCI Helm registry to Ankra, so it is listed with them)
ankra helm registries list

# The projects that registry holds, as the admin credential sees them
ankra registry projects list \
  --registry smartoptics-harbor \
  --admin-credential smartoptics-harbor-robot-admin

# A push robot that reaches only the collectorcharts project
ankra registry robots create collector-ci \
  --registry smartoptics-harbor \
  --admin-credential smartoptics-harbor-robot-admin \
  --project collectorcharts

# Rotate, inspect and revoke it like any other robot. A robot's name is
# unique across your organisation's registries, so the name alone says
# which robot, and which registry, these act on.
ankra registry robots rotate collector-ci
ankra registry robots get collector-ci
ankra registry robots delete collector-ci --yes
```

| Setting | What it is |
| - | - |
| **Registry** | An OCI registry entry of your organisation, other than the Ankra registry |
| **Admin credential** | A registry credential you stored, with project administrator rights (robot accounts) on the project. A robot login Ankra manages cannot be used |
| **Project** | Required: one project of that registry, by its name there (such as `collectorcharts`). The robot reaches that project and no other |
| **Login** | `robot$<project>+user-<name>`, with your registry's robot prefix |
| **Permissions** | The same scopes, permissions and expiry as on the Ankra registry. Creating one, and listing that registry's projects, also needs `credentials.reveal`, because Ankra acts as your admin credential |

A robot of the same name that already exists on your registry is left alone, and the create is refused: pick another name or delete it in Harbor first. If the registry entry is deleted, or now points at another host, rotating the robot is refused and revoking it removes Ankra's copy of the login only; delete the robot in Harbor yourself if it still exists. A registry that refuses the admin credential is reported as such, so check the credential's rights on the project.

### Seeing every robot, including Ankra's

`ankra registry robots list` and the **Registry robots** page list every login your project honours, not only the ones you created: yours first, then the ones Ankra manages.

| Kind | Robots | Who rotates it |
| - | - | - |
| `user` | The ones you created | You, with `robots rotate` |
| `organisation` | `ci` (push and pull, behind `ankra-harbor-ci`) and `pull` (behind `ankra-harbor-pull`) | Ankra |
| `application` | One push robot per application, on the registry that application publishes to | Ankra, or you with `ankra application registry robot rotate <application>` |

`--kind` narrows the listing to one kind, or to `managed`, which is shorthand for everything Ankra minted: the `organisation` and `application` robots together.

```bash theme={null}
ankra registry robots list                      # everything
ankra registry robots list --kind user          # only yours
ankra registry robots list --kind managed       # organisation and application robots
ankra registry robots list --kind application   # one push robot per application
ankra registry robots get ci
```

Managed robots are read-only here. Ankra hands each one's secret to the builds and clusters that use it, so rotating or revoking one from this lane would strand them; `rotate` and `delete` refuse a managed robot and say so. An application robot minted on a registry you run yourself shows that registry's host and project.

## Pull secrets in your clusters

The generated manifests reference the pull secret through `imagePullSecrets`, and Ankra keeps that Secret in place for you - **per cluster and per namespace**, without you registering anything.

* On **every deploy**, the target namespace is added to that cluster's registry pull-secret resource for the Secret the declaration names (`harbor-registry` above, `ankra-registry-pull` by default), and the resource is re-applied so the Secret exists in the new namespace.
* A cluster carries **one resource per Secret name**, so an application pulling from your Harbor coexists with every other application pulling from Ankra's own registry - registering a namespace on one never rewrites the other's.
* **Rotation fans out**: rotating the robot flips the deployed resources so the new login replaces the old one in each cluster.
* Clusters imported before the registry existed get the resource created on the spot.

```bash theme={null}
# What the cluster actually holds
ankra cluster select <cluster>
ankra cluster get secrets --all-namespaces
```

```
harbor-registry        commerce    kubernetes.io/dockerconfigjson
harbor-registry        insight     kubernetes.io/dockerconfigjson
ankra-registry-pull    default     kubernetes.io/dockerconfigjson
```

> [!NOTE]
>
> The Secret is built from the declaration's **read** credential, at the declared host. A declaration with no read credential registers no namespace and creates no Secret, which surfaces in the cluster as `ImagePullBackOff` on a private project rather than as an error in Ankra.

***

## Verifying the whole path

`ankra application publish-readiness <application-id>` answers the four questions that matter, and distinguishes "nothing published" from "cannot see", so an unreadable registry never reads as an empty one.

| Check | `ok` means | Common warning |
| - | - | - |
| Image registry | The declaration resolved and Ankra can read the project with the named credential | No credential named, or the project is not visible to it |
| Registry login secrets | The repository carries the two Actions secrets the workflow logs in with | Missing - mint the robot, or write them yourself |
| Publish workflow | The workflow on the default branch publishes to the declared repository | Publishes somewhere else: the declaration changed after setup. Reconcile the application and merge its setup pull request |
| Image artifact | At least one image exists in the repository | `No image has been pushed yet` - the first successful build publishes it, and deploy stays blocked until it exists |

***

## Troubleshooting

| Symptom | Cause | Fix |
| - | - | - |
| `robot ensure` fails with a Harbor permission error | `admin_credential_name` points at a project-scoped robot, which cannot administer robots | Use a Harbor user with Project Admin, or a system robot with robot permissions |
| `registry set` is refused: `The project 'x' does not exist on 'host', and the registry credential ... may not create it` | The project is missing and the admin credential has no project-creation right | Create the project in Harbor and make the credential its Project Admin, or allow the credential to create projects (a user under *Project creation: Everyone*, or a system robot with `project` create) |
| `registry set` is refused: `... may not read or list projects on 'host', so the project 'x' could not be checked` | The admin credential has neither project read on `x` nor system-level `project:list` | Grant the robot system `project` list (and create), as in the permission table above |
| `registry set` is refused: `The project 'x' exists on 'host' but the registry credential ... cannot see it` | The project is private and the admin credential is not a member of it | Make the credential a Project Admin of that project |
| A build fails at the push with `project x not found`, and the declaration was set without `--admin-credential` | The project was never checked, because nothing could check it | Create the project, or redeclare with `--admin-credential` so Ankra checks or creates it |
| Publish readiness says the application never published, but images are in Harbor | No read credential on the declaration, or the credential cannot see the project | Name a read credential with project read + repository list/read |
| Publish readiness says the workflow publishes elsewhere | The registry was declared after the application was created | `ankra application reconcile <application-id>`, then merge the setup pull request it raises |
| Pods report `ImagePullBackOff` on a private project | No read credential, so no pull secret was created | Add `--credential` to the declaration and redeploy |
| Images land at unexpected repository paths | Your Harbor's layout is not Ankra's nesting | `--flat-repositories`, or `--component-repository <component>=<repository>` |

***

## Related

> **[Using your own registry](https://docs.ankra.ai/concepts/applications.md#using-your-own-registry)**
>
> The declaration's every field, and how components inherit it.

> **[Robot accounts](https://docs.ankra.ai/concepts/applications.md#robot-accounts)**
>
> The per-application robot on Ankra's own registry.

> **[Registry credentials](https://docs.ankra.ai/platform/credentials/registry.md)**
>
> Storing the credentials this guide names.

> **[CI/CD pipeline](https://docs.ankra.ai/guides/cicd-pipeline.md)**
>
> The build workflow that logs in with the robot.


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