docs: Add Geo setup path for a populated metadata database primary
What does this MR do?
Adds the Geo setup path for the one state this page does not cover: a primary whose container registry is already on the metadata database and already holds data.
Each existing path carries a prerequisite that a completed migration invalidates. The prerequisites differ, so it is worth listing them rather than generalising:
| Path | Gating prerequisite | Why a migrated deployment fails it |
|---|---|---|
| Create a GitLab instance with the container registry and Geo | "A new instance of GitLab", "A configured container registry for the instance with no data" | The registry holds data |
| Add container registries to existing Geo sites | "Two new instances of GitLab, set up as primary and secondary sites", "A configured container registry for the primary site with no data" | Both: the sites exist and the registry holds data |
| Add Geo support and container registry to an existing instance of GitLab | "An existing instance of GitLab with no container registry configured", "No existing Geo site" | Geo already exists |
| Add Geo support to an instance with a configured container registry (both variants) | "No existing Geo site" | Geo already exists |
| Migrate the container registry from legacy metadata | "Container registries on both sites using legacy metadata", "Both registries must have existing data (images pushed)" | The primary is already on the database |
So the case with no published procedure is the ordinary steady state after a completed migration, and following the migration path to its end lands a deployment precisely there. An operator who then needs to add a secondary, or rebuild one, reaches for "Add container registries to existing Geo sites" because it is the nearest match by title, and hits "no data" with a populated primary.
The prerequisite is not incidental, and an operator cannot simply disregard it. Each of the four setup paths delegates its metadata-database step to Container registry metadata database for new installations, whose own prerequisite is "You must have a new container registry with no images pushed to the registry," so the "no data" gate is restated one level down. The migrate path delegates instead to the enable-for-existing-registries section and is gated by its own requirement of legacy metadata on both sites. There is no path through the existing material that a migrated, populated primary satisfies.
The ordering is the substance
The secondary's registry database must be provisioned and migrated before the secondary's registry starts against populated storage. That is stated as a warning rather than buried in a step, because getting it wrong does not produce a clean error:
- A registry that starts first has no schema to write to.
- In prefer mode, a registry starting against storage with no lockfile does not fall back to legacy metadata. It requires a reachable database, and if it reaches an empty one it adopts it and serves an empty catalog. Prefer mode was introduced in 18.7 and became the default for new Linux package and self-compiled installations in 19.0, so the behavior is not new in 19.0; what changed in 19.0 is how many sites have it switched on without choosing it.
The procedure's own step 3 reconfigures with the database still disabled, which does start the registry, and that is deliberate rather than a contradiction of the warning: at that point the secondary's storage is empty on both paths, because step 2 empties it when rebuilding and confirms it empty when adding a new site. The hazard the warning names is a registry meeting populated storage before the schema exists. The step says so, so a reader following it does not have to work that out.
Verification, including the trap
The verification step names the correct comparisons: the catalog, the Docker-Content-Digest for a tag, and registry database row counts.
It also warns explicitly against the wrong one. Object counts are not comparable between the two sites, and the difference is large enough to look like a failure:
| Primary (migrated from legacy) | Secondary (built on the database) | |
|---|---|---|
| Objects in storage | 9 | 3 |
| Manifest digest for the tag | sha256:26ba97be… |
sha256:26ba97be… |
| Own registry database | repos=1 manifests=1 tags=1 |
repos=1 manifests=1 tags=1 |
The digests are identical, so the secondary serves the same image. The primary holds three times the objects because it was migrated from legacy metadata and the import does not delete the former metadata files, so it retains five link files under _layers/ and _manifests/ plus the manifest stored as a blob. The secondary was built directly on the database, so its storage holds only the two content blobs and the lockfile.
An operator comparing object counts concludes the backfill is broken. Worth a note in the docs rather than a support ticket.
A real bug sits next to this note, and the note is scoped so it does not paper over it. Issue 590744 reports a secondary that never receives one _manifests/revisions/sha256/<digest>/link file per image update, so the deficit accumulates. Those link files exist only on a secondary using legacy metadata, and a site built by this procedure is on the metadata database from its first start, so the note scopes the pointer accordingly rather than sending a reader after a cause that cannot apply to them. A note that simply said "the difference is expected" would tell an operator to ignore that. So the note explains why a standing difference is structural, and says explicitly that a difference which grows with each push is not, with a link to that issue.
Evidence
Two-site Geo lab, GitLab 19.2.1, registry v4.40.2-gitlab, each site with its own object storage and its own registry PostgreSQL. The procedure ran twice end to end against a primary carrying database-in-use with repositories in its own registry database.
Measured at each step: secondary storage and database emptied to 0 objects and 0 tables; the registry's pre- and post-deployment migrations applied (192 and 20 on the lab's registry version), taking the database from 0 to 20 tables, before the registry started; registry then starting on using the metadata database and writing database-in-use; and after the resync, matching catalog, matching tag list, matching manifest digest, and matching database row counts.
Related issues
- The
Prefer modebehavior this MR links to is clarified by !249940, which documents when the lockfile is written and what happens when it is absent. Reviewing them together is useful; this MR reads correctly without it, since it links to the existing section. - !246581 is an open draft that rewrites this page for clarity. It does not add a path for a migrated, populated primary, so the two changes do not overlap, but it renames every heading on the page. If it merges first, this section needs a rebase and its link to "Migrate the container registry from legacy metadata" needs to point at the renamed anchor.
Author's checklist
- Follows the documentation style guide
-
markdownlint-cli2clean (0 errors, CI's pinned version) -
vale --minAlertLevel warningclean: 0 errors, 0 warnings across the whole file - Editing an existing page, so no new product availability details block is required
- No
{{< history >}}block: the section documents existing behavior that was undocumented rather than a new or changed feature, so there is no version claim to make. - Request review from the Container Registry technical writer
AI-Generated Content Disclosure: This MR was prepared with assistance from Claude Code. The output has been reviewed for correctness, verified on a lab against GitLab 19.2.1 with registry v4.40.2-gitlab, and validated against the documentation style guide.