How GitLab and Harbor container registry integration works for your pipelines
A platform team that wants CI/CD images stored under its own controls, rather than in the GitLab built-in registry, can point GitLab projects at Harbor. The GitLab Harbor integration lets a project use Harbor as its container registry, and when it is active GitLab creates the HARBOR_* CI/CD variables that pipelines use to push and pull images. The integration is configured with the Harbor URL, the Harbor project name, a username and a password, and it is available in GitLab Free, Premium and Ultimate. Harbor itself is an open-source registry licensed under Apache-2.0, and the CNCF records it at the Graduated maturity level, which is the most mature stage in the CNCF project lifecycle. The practical decision is where the registry lives and who operates it, not whether a given GitLab tier unlocks the integration.
The six variables are HARBOR_USERNAME, HARBOR_HOST, HARBOR_OCI, HARBOR_PASSWORD, HARBOR_URL and HARBOR_PROJECT. Because the credentials are delivered to jobs as CI/CD variables, the account behind HARBOR_USERNAME and HARBOR_PASSWORD should be a dedicated Harbor robot or service account with only the permissions the pipelines need, and the password should be masked and protected. Confirm with your Harbor administrator which role that account holds on the target Harbor project, and whether pipelines on unprotected branches should be able to push at all. Also confirm that the Harbor URL and project name in the GitLab settings match exactly what your jobs reference, since a mismatch is the most likely cause of failed pushes on first setup. Project-level settings override group-level settings, so a team that inherits a group default should check the effective values in each project before assuming a pipeline uses the shared registry.
Before you rely on this in production, confirm the Harbor version you run and the GitLab version your instance uses, since both products ship on their own release schedules. Harbor's own documentation covers how the registry stores images and Helm charts as OCI artifacts, and GitLab's Harbor integration page describes the settings screen and the variable behavior in detail. Plan a test project that pushes and pulls a throwaway image through the integration, then check that access is denied for a project that should not reach the registry. Those two checks confirm both the wiring and the isolation before any team migrates its build pipelines.
What you need in place before connecting GitLab to Harbor
The Harbor integration is simple to configure but depends on four things being ready first: a Harbor instance you can reach, a project with the right account, a supported GitLab version and tier, and a network path from runners to Harbor. Confirm each one before you touch the GitLab settings, because most failed pushes trace back to one of these.
A Harbor instance and its prerequisites
Harbor is a separate system you install and run, so GitLab only connects to it. Check the Harbor installation prerequisites for the host, container runtime and storage before choosing a version. Pick a version from a supported release line and confirm the current release at install time, then read the release notes for that line. Breaking changes in recent releases, such as changes to webhooks and content trust, can affect how pipelines behave after an upgrade, so review them before you go live.
A dedicated Harbor project
Create a Harbor project for the GitLab workloads rather than reusing a shared project. The project name becomes the value of HARBOR_PROJECT in your pipelines, and its access settings decide which images can be pushed and pulled. Decide up front whether production and non-production images share a project, since that choice shapes access control, retention and vulnerability scanning policy later.
A robot account with push and pull rights
Use a Harbor robot account scoped to the single project that pipelines need, with push and pull permissions only. Its username and secret are stored as CI/CD variables, so avoid a personal administrator account. Record who owns the robot, when its secret expires and how it is rotated, because a rotated secret that is not updated in GitLab will break every job that pushes images.
A supported self-managed GitLab version and tier
The Harbor integration is available in the Free, Premium and Ultimate tiers, so the tier is not a blocker. Confirm that your self-managed GitLab instance is on a release line that GitLab still maintains, using the maintenance policy. If the instance is behind a required upgrade stop, plan that upgrade first, because integration settings and CI/CD behavior should be tested on the version you will run in production.
Network reachability from runners to Harbor
The CI/CD jobs that push and pull images run on your GitLab Runners, not on the GitLab server, so the runners must reach the Harbor URL over the network and resolve its hostname. Test this from the runner hosts or executor network, including any proxy, firewall rule or private DNS entry. Also confirm the TLS certificate chain that Harbor presents is trusted by the runner image or host, since a missing internal CA is a common cause of failed pushes.
Agreed ownership and change control
Name who owns the Harbor instance, the GitLab integration settings and the robot credentials, and agree how changes are approved. The group-level defaults apply to every project beneath them, so a single change can affect many pipelines. Ask your team to document the Harbor URL, project name and credential owner in one place before rollout, so that later audits and incident reviews can trace which system pushes and pulls which images.
Configure the Harbor integration in GitLab step by step
This sequence connects a GitLab group or project to a Harbor project so that pipelines receive the registry address and a scoped account through CI/CD variables. It ends with a test pipeline that logs in, pushes and pulls an image, which confirms the integration works before other teams depend on it.
Create the Harbor project for GitLab workloads
A Harbor administrator creates a Harbor project to hold the images built by GitLab pipelines, and sets its access and visibility to match the teams that use it. Harbor applies role-based access control per project, so one project per workload or trust boundary keeps permissions easier to review. Confirm your naming convention and whether the project should be private with your security team first. Done when the project appears in Harbor and an administrator can see its settings.
Create a dedicated Harbor account with least privilege
Create a Harbor user account used only by GitLab pipelines, not by a person, and grant it the narrowest role on the Harbor project that still allows the pushes and pulls your pipelines need. Take the role names from the documentation for your Harbor version, and confirm whether the account needs push rights in every project or only some. Store the password in your secrets manager, because GitLab will hold it as a CI/CD variable. Done when a login with this account succeeds and Harbor denies it any action outside that project.
Open the Harbor integration at group or project level
A GitLab group owner or project maintainer opens the Harbor integration settings. Choose the group when every project should share the same registry, and the individual project when one repository needs its own. Confirm that your GitLab role allows changing integrations at the level you picked. Done when the Harbor integration form opens at that level and shows the fields described in the GitLab Harbor integration documentation.
Enter the Harbor URL, project name and credentials
Enter the Harbor URL, the Harbor project name, and the username and password of the dedicated account. Use the HTTPS address of your Harbor instance, and paste the password from your secrets manager so it stays out of shared notes. Confirm that the URL is reachable from your GitLab Runner hosts and that the project name matches the Harbor project exactly. Done when GitLab saves the form without errors and the integration shows as active.
Verify the six HARBOR_* variables in CI/CD settings
Once the integration is active, GitLab creates six CI/CD variables: HARBOR_USERNAME, HARBOR_HOST, HARBOR_OCI, HARBOR_PASSWORD, HARBOR_URL and HARBOR_PROJECT. Open the project's CI/CD variable settings and confirm that all six are present and contain the values you expect. Check that the password variable is masked or protected according to your policy. Done when the list is complete and every value matches the Harbor project and account you created.
Run a test pipeline that logs in, pushes and pulls
Add a short test job that logs in with HARBOR_USERNAME and HARBOR_PASSWORD, builds or tags a small test image, pushes it to the Harbor project, and pulls it back. Use a runner whose container tooling your pipeline image supports, and confirm that the runner can reach the Harbor host. Run the job in a test project or on a protected branch so no release artifact is created by accident. Done when the job log shows a successful login, push and pull, and the test image is visible in Harbor.
Confirm inherited values in each project
Project settings override group settings, so a project with its own Harbor configuration may not reflect later changes made at group level. Open each project that should inherit the group defaults and confirm that its Harbor values and HARBOR_* variables match the group configuration. Repeat this check after any change to the group integration, and after projects are created or moved. Done when every project resolves to the intended Harbor project and account, and each override is documented.
How project settings override group defaults for Harbor
Configure Harbor once at the group level when most projects in the group push to the same registry, then override the settings only in projects that need something different. GitLab's Harbor integration takes the Harbor URL, Harbor project name, username and password, and project-level settings override group-level settings. When the integration is active, GitLab exposes the values to pipelines as the HARBOR_URL, HARBOR_HOST, HARBOR_OCI, HARBOR_PROJECT, HARBOR_USERNAME and HARBOR_PASSWORD CI/CD variables. A project that must push to a different Harbor project, for example a team that publishes to its own registry namespace, only needs its own project-level values, and everything it leaves unset continues to come from the group.
The main risk is silent inheritance. A project that never touches the integration will use the group credential, so if that credential belongs to a robot or user account with write access across many Harbor projects, every pipeline in the group can push to registries it has no business touching. Before rollout, confirm with Harbor which account the group credential maps to, what permissions it holds, and whether Harbor's own project-level role-based access control can narrow it. Prefer a dedicated account per Harbor project or per group, scoped to the minimum push and pull rights, and confirm in your environment that the credential's permissions match what the group documents.
Record which level owns each value in a simple register: the group, the project, or the Harbor side. Review it whenever the group credential is rotated, a Harbor project is renamed, or a project is moved between groups, because each of those changes can alter which value a pipeline actually receives. Audit overrides on a schedule, checking for projects that override one variable but inherit the rest, and for group credentials that still point at retired Harbor projects. Check current behavior against the GitLab Harbor integration documentation before you write the audit procedure, since override handling is what the register depends on.
Mapping the six HARBOR_* CI/CD variables to Harbor values
This table pairs each HARBOR_* variable GitLab creates with the Harbor value it carries and the job step that uses it. Read it as a checklist: confirm each value format in your own environment against the GitLab Harbor integration docs before wiring pipelines to push or pull. Masking and protection matter most for the password and the account that holds it.
| Variable | Harbor value it carries | Used in a push or pull job for | Mask or protect? |
|---|---|---|---|
| HARBOR_URL | Full Harbor base URL configured in the integration, including the scheme | Reference point for the Harbor endpoint; confirm whether your jobs use it directly or only through HARBOR_HOST | Not secret; protect if your URL is internal and you restrict variable scope |
| HARBOR_HOST | Host portion of the Harbor URL, without the scheme or path | Used as the registry host in docker login and in full image names such as host/project/image:tag | Not secret; keep unmasked for readable logs |
| HARBOR_OCI | OCI reference form for the registry, used for OCI artifacts such as Helm charts stored in Harbor | Used when pushing or pulling OCI artifacts; confirm the exact prefix format in the GitLab docs | Not secret; confirm the value format before relying on it in scripts |
| HARBOR_PROJECT | Harbor project name that holds the repositories for this GitLab project or group | Forms the path segment between the host and the image name, so a wrong value pushes to the wrong Harbor project | Not secret; check it first when pushes are denied |
| HARBOR_USERNAME | Harbor account name the integration authenticates as | Passed to docker login for both push and pull; use a dedicated robot or service account with the narrowest project role | Mask where the value appears in job output; protect if restricted to protected branches |
| HARBOR_PASSWORD | Password or secret for the Harbor account | Passed to docker login with the username; never echo it in scripts or artifacts | Mask and protect; rotate it when the account or the pipeline owner changes |
Using the variables in push and pull jobs
A build-and-push job logs in to Harbor with the integration credentials, builds the image and pushes it into the Harbor project that the integration names. Treat this job as the only writer to the registry, because push rights are what allow an image to enter a project. The integration is described in the GitLab Harbor integration docs. The example below is illustrative only: check your build tool's login and tag syntax against its own documentation.
- Login variables: HARBOR_USERNAME and HARBOR_PASSWORD supply the credential for the login step. Keep the password masked so it does not appear in job logs.
- Destination variables: HARBOR_OCI or HARBOR_HOST, combined with HARBOR_PROJECT, form the image path. Confirm which of the two your build tool expects, since the two forms may be handled differently by different tools.
- Write permission: the Harbor account behind the credential needs push rights on the target project, granted through Harbor's role-based access control. A read-only account fails at the push step.
- Traceable tags: tag each image with the commit or pipeline identifier so that deploy jobs can reference one exact artifact.
- Scanning: Trivy scanning is an install-time option in Harbor. Confirm with your Harbor administrators whether scans run on push in the target project before relying on them.
Check that the job fails cleanly when the password is wrong, and that the pushed tag appears in the expected Harbor project.
A deploy job usually needs only to pull. It should use a credential that can read the project and nothing more, so that a compromised deployment step cannot publish a new image. The same variables are available to this job, but it should reference the exact tag the build job produced, so that each deployment runs the artifact that was tested. The example below is illustrative only and omits exact commands.
- Read-only credential: provide a Harbor account with pull rights only, scoped to the project the deployment needs. Ask your Harbor administrators to confirm which role maps to that access.
- Variable visibility: the integration variables are set at group or project level. Confirm that the environment the deploy job runs in receives them, and that no other project can read them.
- Runtime credentials: CI variables are visible only inside the job. The cluster or host that actually pulls the image needs its own registry credential or trust configuration. Confirm how that is provisioned.
- Replicated copies: if deployment targets pull from a replica, Harbor replication rules determine which copy is current. Confirm the replication schedule before pointing a deployment at a replica.
- Tag consistency: the deploy job should fail if the requested tag is missing, rather than falling back to a different image.
Verify that the deploy job pulls the exact tag from the build job and fails once the read credential is removed.
Where a job runs decides who else can observe its environment. Shared runners serve many projects, so a job on one may execute alongside code written by another team. Protected runners only pick up jobs from protected branches or tags, which makes them the usual place for jobs that hold registry write credentials. Runner behavior is described in the GitLab Runner documentation. Confirm the runner setup with your platform team before any write credential reaches a project.
- Shared runner exposure: a push job on a shared runner executes on infrastructure used by other projects. Avoid giving that job write credentials.
- Protected runners for push: restrict build-and-push jobs to protected runners and protected branches or tags, so that only reviewed code can use the credential.
- Protected variables: mark the write-capable HARBOR_* variables as protected, and confirm how your GitLab version applies that setting to unprotected branches.
- Pull job placement: a pull-only job can run on a shared runner, but only if the images it pulls are trusted by your policy. Confirm that policy in writing.
- Version alignment: GitLab advises keeping Runner major and minor versions aligned with GitLab, so check the runner fleet during every GitLab upgrade.
Check the runner tags in the job definition and confirm which runner actually executed the push.
Securing the Harbor credentials and image flow
The Harbor integration puts a registry credential into every pipeline that pushes or pulls images, so the account, the variable and the registry policy need to be set together. The cards below cover what to lock down on the GitLab side, what to configure in Harbor, and what to re-check when Harbor changes behavior between releases.
Dedicated least-privilege Harbor account
Create a Harbor user used only by GitLab pipelines, not a personal administrator account. Grant it push or pull rights only on the Harbor project the integration targets, and avoid system-wide administrator rights. Because GitLab stores this identity as the HARBOR_USERNAME and HARBOR_PASSWORD variables, a compromised pipeline can only reach what that account can reach. Confirm the exact role names in your Harbor version before you assign them.
Masking and protecting the password variable
The HARBOR_PASSWORD variable should be masked so it does not print in job logs, and marked protected so it is only exposed to pipelines on protected branches and tags. Check that the masking rules accept your password format, because a password that does not meet them cannot be masked. Treat the value as a secret: rotate it on a schedule and after any runner or project role change.
Limiting which branches and environments read it
Protected variables only solve part of the problem. Scope variables to specific environments where possible, and make sure only the branches that build release images can reach the push credentials. Feature branches usually need pull access at most. Review the project and group variable scopes together, since a project-level override can change what a pipeline receives from the group default.
Enabling Harbor role-based access control
Harbor projects support role-based access control, so set membership on each Harbor project rather than relying on a shared admin login. Separate who can push release images from who can only read them, and keep the pipeline account in the narrowest role that still lets it work. Review project membership on the same cadence as your GitLab group membership, because the two lists drift apart over time.
Vulnerability scanning with Trivy at install time
Harbor runs static vulnerability analysis through the open-source Trivy scanner, but only when Trivy is enabled at install time with the --with-trivy option. Decide before installation whether Trivy runs inside Harbor or connects to an external instance, and confirm which scan policies block pulls or pushes in your environment. Check the Trivy version bundled with your Harbor release, since the scanner is updated independently of Harbor.
Webhook and content trust changes in v2.15.3
Harbor v2.15.3 blocks webhooks to internal network addresses by default, so a GitLab or internal CI notification endpoint may silently stop receiving events after upgrade. It also makes signing unsigned images fail in projects that enforce content trust. Test both behaviors in a staging Harbor before upgrading production, and confirm which of your pipelines sign images.
Runner isolation and CI/CD hardening
A registry credential is only as safe as the runner that receives it. Use GitLab's hardening guidance for CI/CD variables, application settings and runner isolation, and apply it gradually, as that guidance recommends. Prefer runners dedicated to trusted projects for jobs that push release images, and confirm that shared runners cannot read the variables used for registry pushes.
Why pushes and pulls fail after connecting Harbor
This table lists the failures teams most often see after pointing a GitLab project at Harbor, with the symptom to look for and the fix for each. Work down the rows in order, since a wrong project name or stale override can look like a network or permission fault. Most failures trace back to one of the six HARBOR_* variables or to a project setting that overrides the group default.
| Cause | What you see | Fix |
|---|---|---|
| Wrong Harbor project name | Push or pull returns not found or denied for the repository path, even though the robot or user can log in | Compare the HARBOR_PROJECT value in the job log with the exact project name in Harbor, then correct the project-level or group-level integration setting |
| Expired or rotated password | Login to Harbor fails in the first step of the job, often after a credential rotation or account lock | Generate a new password or robot secret in Harbor, update the integration password, and confirm the value matches the HARBOR_PASSWORD variable the job receives |
| Account lacks push rights | Login succeeds but the push step returns unauthorized or denied, while pulls from the same project may still work | Grant the account the push role on the Harbor project through its role-based access control settings, then rerun the pipeline |
| Runner cannot reach the Harbor host | Connection timeouts or DNS errors appear at login, and the same host opens from a workstation | Test name resolution and TCP reachability from the runner's network, then fix routes, DNS or egress rules between the runner and HARBOR_HOST |
| Certificate trust gap | x509 or certificate authority errors appear at login, often when Harbor uses an internal certificate authority | Install the internal root certificate on runner hosts or in the job image trust store, then confirm the HARBOR_URL uses the name on the certificate |
| Project override points to an old Harbor | Jobs in one project use a different host or project than the group default, and changes to the group setting have no effect | Open the project's integration settings, check whether it overrides the group, and update or clear the project-level values |
| Harbor upgrade changed behavior | Pushes that worked before fail after an upgrade, for example signing steps in projects that enforce content trust | Read the Harbor release notes for the version you run, confirm the signing or webhook setting, and align projects before rerunning pipelines |
| Variables missing from the job | Scripts reference empty HARBOR_* values, or the integration appears active but jobs do not receive them | Confirm the integration is enabled at the level that applies to the project, check the variable scope and protected or masked flags, and rerun the job |
Questions teams ask before connecting GitLab to Harbor
What drives the cost of connecting GitLab to Harbor?
The cost comes from the people doing the work, not from the integration itself, since GitLab documents Harbor as a project-level integration available in Free, Premium and Ultimate. The main drivers are the number of GitLab groups and projects that will push or pull images, how many credential sets you need, whether Harbor is already running and hardened, and whether you add Harbor features such as scanning, replication or proxy cache. Ongoing costs include Harbor hosting, storage for images, and upkeep of Harbor and GitLab versions. Ask any quote to separate one-time setup from recurring operations, and to state which GitLab tier and Harbor components it assumes.
Does the GitLab tier change whether Harbor works?
No. GitLab's documentation lists the Harbor integration as available in Free, Premium and Ultimate, so the tier does not decide whether the connection works. The tier matters for other decisions around the same pipelines, such as advanced CI/CD and priority support in Premium, or the security and compliance features listed for Ultimate. Confirm the features your pipelines depend on against the current GitLab pricing page before you commit, because tier pricing is set by GitLab and changes over time.
How long does a Harbor and GitLab setup take?
Duration depends on how many projects and environments need registry access, not on a fixed figure. A single project pushing to one Harbor project with one robot or service account is a small change. Each additional group, environment (for example, staging and production with separate Harbor projects), and access model adds design, testing and review work. Harbor installation and network preparation can take longer than the GitLab side if no Harbor instance exists yet. Ask for a timeline that lists the projects, environments and Harbor components in scope.
What must our team provide before the work starts?
Plan for four things. First, Harbor administrator access or a named Harbor owner who can create projects and accounts. Second, network paths from GitLab Runners to Harbor over the ports and hostnames your design uses, plus the certificate chain Runners must trust. Third, a decision on who owns the Harbor and GitLab service accounts, since credentials should not belong to one engineer. Fourth, a list of GitLab groups and projects in scope. Confirm the Harbor version and installation method with your own environment before you start, because prerequisites differ by release.
Can our own team connect GitLab to Harbor without a partner?
Yes, if your team has administrators for both systems and experience with container registries, CI/CD variables and certificates. The integration is configured with the Harbor URL, Harbor project name, username and password, and it creates the six HARBOR_* CI/CD variables that pipelines use. The harder parts are credential design, scoping access by project, testing push and pull from every runner type, and documenting how the setup is maintained. A partner is most useful when the scope spans many groups, air-gapped networks or regulated evidence requirements, and a written scope will show which parts you can handle internally.
How do Harbor and GitLab upgrades affect the integration?
Upgrades can change behavior, so test the integration after each one. Harbor's v2.15.2 release upgraded its bundled database to PostgreSQL 18 on container startup and recommended a backup before upgrading. Harbor v2.15.3 introduced two breaking changes: webhooks to internal network addresses are blocked by default, and signing unsigned images fails in projects that enforce content trust. GitLab requires upgrades to pass through required upgrade stops, so check the upgrade path before moving GitLab. Confirm the Harbor version you run against the latest release notes, and keep a test pipeline that pushes and pulls after every change.
When is Harbor proxy cache or replication worth adding?
Add them when a specific need appears, not by default. Proxy cache projects can cache upstream registries such as Docker Hub and serve the cached copy if the upstream is unreachable, but they are read-only for pushes and get a default 7-day retention policy. Replication rules can copy images to other registries, filtered by name, tag or label, and run on a schedule or on push events. Consider proxy cache when pipelines pull base images from public registries and reliability or rate limits matter, and replication when images must exist in a second registry or site. Each adds storage, retention and access decisions to plan.
How should credentials be designed for the six HARBOR_* variables?
Use dedicated Harbor accounts with the least access each pipeline needs, rather than a personal administrator login. GitLab stores the username and password as CI/CD variables, and project-level settings override group-level defaults, so a clear structure of which level holds which credentials prevents surprises. Mark variables as masked and protected where your policy requires it, and rotate passwords on a schedule with an owner named for each account. Confirm with your security team how these secrets fit your secrets manager and audit requirements before you choose the design.
How DeployOpen scopes and delivers the GitLab and Harbor integration
DeployOpen plans, configures and documents the link between self-managed GitLab and a Harbor registry inside infrastructure you control. Engagements are fixed-scope, with the registry endpoints, group and project settings, CI/CD variables and access model agreed in writing before any configuration begins.
How we scope
- Confirm which GitLab groups and projects will push and pull images, and which Harbor projects they map to.
- Record the Harbor version, install method and whether Trivy scanning is enabled before any settings change.
- Agree which robot or service account credentials each group uses, and who owns rotation.
- Document the group default and project override rules so every team knows where a setting applies.
How we build
- Configure the Harbor integration at group level first, then add project overrides only where a team needs them.
- Wire the HARBOR_* CI/CD variables into a sample pipeline that pushes and pulls a test image.
- Test failure paths, including expired credentials, wrong project names and a Harbor instance that is unreachable.
- Check runner network access to the Harbor endpoint and review how credentials are masked in job logs.
How we hand over
- Deliver a written configuration record covering group settings, project overrides and the variables each pipeline relies on.
- Provide a runbook for credential rotation, adding a project and recovering from a failed push or pull.
- Walk your platform and security teams through the setup and the upgrade notes for the Harbor and GitLab versions in use.
- Hand over the test pipeline and its results so your team can repeat the checks after changes.
How we support
- Support is available as a retainer for Harbor and GitLab version changes that affect the integration.
- Retainers can cover re-testing after GitLab upgrades and Harbor patch releases, with scope agreed in writing.
- Ask any partner to confirm response expectations and what the retainer excludes before you sign.
- Continuous security testing of the registry path can be scoped separately if your risk model requires it.

