Artifact Registry
One place every package and image comes from · roadmap item 32 · owner: SRE
Every build resolves dependencies, every deployment pulls an image, and every host installs OS packages. Today the first two go through a single Nexus OSS node on-premises — developer machines cannot reach the internet and get every package through it — while images built in the cloud sit in registries spread over several projects, and hosts fetch Ubuntu packages through the forward proxy. This page is the target: Google Artifact Registry as the one registry for Go, Python, npm, Maven, Docker and Ubuntu Apt, reached privately and by identity rather than by keys, with a retention rule that cannot delete what is running. It is also the registry the paved road of item 25 publishes to — "published to the registry" in 25.1 and 25.3 means here.
The work is planned as milestones 32.1–32.7 on the CI/CD & GitOps milestones. Their dates are a first draft and will be adjusted.
Why
| Problem | What it means today |
|---|---|
| Single point of failure | One Nexus OSS node, no high availability. When it is down, every build and deploy in the company stops. |
| Patching depends on the community | No vendor support for the OSS edition; how long a CVE stays open cannot be planned. |
| Cloud cannot reach it | Cloud workloads have no path to the on-prem cache, so they download every dependency from the internet again, and teams stood up their own registries per project. |
| Two identity systems | Nexus local accounts on-prem, IAM in the cloud. Accounts are managed by hand, permissions are coarse, changes are hard to trace, and leavers' accounts linger. |
| Clean-up cannot keep "the newest N" | The OSS clean-up is time-based only. It is fenced with regular expressions, which cannot stop it deleting an old version that is still deployed but rarely pulled. |
| Hosts fetch OS packages through the proxy | What a host installs, and from where, is not under any registry's control. |
The scale of the clean-up problem: a dry run in March 2026 marked 370 of 502 image versions for deletion — every one of which had to be checked by hand against what was running.
Target
Every format gets two repositories — one for what we publish, one that caches its upstream — and, where Artifact Registry supports it, a virtual repository in front of both, so a client is configured with one URL per format.
| Format | We publish to (standard) | Upstream cache (remote) | Client configuration |
|---|---|---|---|
| Docker | internal-docker | proxy-dockerhub — Docker Hub | image references, registries.conf |
| npm | internal-npm | proxy-npmjs — npmjs.org | .npmrc |
| Maven | internal-maven | proxy-maven-central — Maven Central | settings.xml |
| Python | internal-pypi | proxy-pypi-org — pypi.org | pip.conf / uv index |
| Go | internal-go | proxy-golang — proxy.golang.org | GOPROXY, GOPRIVATE for private modules |
| Apt | internal-apt | proxy-ubuntu and proxy-ubuntu-security — Ubuntu archive and security, 24.04 and 26.04 LTS | hosts: sources.list pointing at the on-prem Apt cache; builds: Artifact Registry Apt transport |
| NuGet | — | — | Exception: stays on a minimal Nexus, which serves NuGet only. Artifact Registry does not support NuGet; one non-core unit uses it. |
Repositories are created and permissioned by IaC (item 04), not by hand. Image repositories are set to immutable tags: a tag, once pushed, cannot be moved to different content. Only LTS releases are mirrored for Apt; a new LTS is added when hosts move to it.
Access path
Developer machines have no internet access today, and that does not change. Opening the forward
proxy to Artifact Registry's public hostnames is ruled out: *.pkg.dev serves every Artifact
Registry in the world, including anyone's public repositories, and the proxy cannot see past the
hostname to tell ours apart.
Instead, on-prem reaches Artifact Registry privately:
- A VPN or Interconnect link attached to the Shared VPC host project.
- Private Google Access through
restricted.googleapis.com, its address range advertised to on-prem by Cloud Router. - On-prem DNS answering
*.pkg.devand*.googleapis.comwith the restricted address (item 10). - A VPC Service Controls perimeter around the host project and the registry project, so only our repositories can be reached on that path.
CI runners are self-hosted: those on-prem take the same path, those in GCP sit inside the perimeter. Cloud workloads reach the registry directly, inside the same perimeter.
Hosts install Apt packages through an on-prem Apt cache, whose only upstream is Artifact Registry. Patching the whole fleet then crosses the WAN once per package rather than once per host, a WAN outage does not stop installing what is already cached, and hosts need no Google credentials of their own.
Upstream control
Artifact Registry decides which upstream a package comes from; the network blocks every other path; CI checks what got in.
- The remote repositories are the upstream allowlist. Each has exactly one upstream, and they are defined only in IaC — creating or changing a repository is granted to the Terraform identity alone, so a new upstream is a reviewed merge request.
- Virtual repositories resolve internal first, so a public package that takes the name of one of ours is never chosen over it (dependency confusion).
- Clients use one URL per format — no
--extra-index-urlfor Python (pip would pick the highest version across indexes), noGOPROXY=…,directfor Go, Maven's mirror set to*. - The forward proxy no longer reaches public registries or Ubuntu archives once everything has cut over.
- CI rejects lockfiles with git or URL dependencies, which no registry setting can see.
- GitLab's package registry and dependency proxy stay off. Artifact Registry is the only package store.
What Artifact Registry cannot do is allow or block individual packages: a remote repository mirrors its whole upstream. That gap is covered by scanning.
Security scanning
Findings from every layer land in DefectDojo (item 21), where the release gate reads them.
| Layer | How | Catches |
|---|---|---|
| Build | Trivy on the image and the source dependencies, an SBOM, govulncheck for Go — from the shared CI template | Vulnerable dependencies and OS packages before anything is pushed |
| Registry | Artifact Analysis on internal-docker only | Images pushed outside CI; CVEs published shortly after the build |
| Running | Daily rescan of the digests running in the clusters — the same list retention uses | Long-running versions no one pulls any more |
| Deploy | Signature and attestation verified before admission (item 15.4) | Anything not built and scanned by CI |
| Upstream | Fetching only through the remote repositories; lockfiles | Malicious or typo-squatted packages entering unseen |
Retention
The rule comes from the release contract, which already fixes how images are tagged and released:
- Version tags are never cleaned. A release is the release-candidate image given a version alias by retag (crane / skopeo), not a rebuild, so the version tag is what production runs.
- Commit tags are kept newest N per image, and never while deployed — a version referenced by a running workload is not a clean-up candidate whatever its age.
- Packages in the other formats keep every published version; only the remote caches expire.
Clean-up policies run dry first and are checked against the running digests. The March 2026 Docker dry run (502 versions, 370 candidates) is the first baseline.
Migration
- Proof of concept (32.1) — every format end to end, the access path measured, the open questions below settled.
- Repositories, identity and private access under IaC (32.2).
- Historical artifacts copied format by format (32.4), with a delta copy that can be repeated until the last pipeline moves. Nexus keeps serving in the meantime.
- Pilot pipelines cut over (32.5), Nexus and Artifact Registry side by side. Rollback is pointing the registry URL back.
- Everything cut over (32.6) — every pipeline, developer machines and hosts' Apt sources. This
includes retiring the
mirror-nexusstage some pipelines carry today, which copies images from Artifact Registry into the on-prem Nexus so on-prem clusters can pull them over an insecure-registry configuration. Only after the last pipeline has moved and the final delta is copied does Nexus turn read-only. - Nexus retired (32.7) after at least four weeks read-only — except the minimal NuGet instance.
Open questions for the proof of concept
- Go remote repositories — how fully Artifact Registry caches proxy.golang.org and handles private modules, before the Go row is committed to.
- Bandwidth on the private path — image pulls and fleet patching through the hybrid link,
compared with today's
mirror-nexuspath. - Artifact Analysis — coverage beyond images, cost, and import into DefectDojo.
- Virtual repositories and org policy — support per format, and whether org policy can restrict remote upstreams in addition to IAM.
- Region — asia-east1 is the default for proximity.
- What the historical copy covers — which Nexus repositories, and how far back.
Where it connects
| Item | Relationship |
|---|---|
| 25 · Application Containerization | 25.1's base images and 25.3's per-service images are published here; 25.1 waits for 32.2. |
| 15 · CI/CD Pipeline Optimization | 15.4 signs images and verifies them before deploy; it waits for 32.6, and the signatures live beside the images here. |
| 20 · End-to-End Build Auditing | The artifact a build event points at is a digest in this registry. |
| 21 · Vulnerability Management | Every scanning layer above reports into DefectDojo. |
| 12 · Dynamic Secrets Engine | The no-static-credential rule this design follows. |
| 10 · DNS / 07 · Network IaC | On-prem DNS for the restricted address; firewall and proxy rules. |
| 04 · Declarative Infrastructure | Repositories, identity and the private path are Terraform. |
Back to Architecture · the direction is roadmap item 32.