On March 18, 2025, Google shut down Container Registry (gcr.io) for new pushes. If you're still using gcr.io/my-project/my-image in your CI/CD pipelines, you need to migrate to Artifact Registry — now. At Commsult Indonesia, I managed the migration for three GCP projects and two CI/CD systems in February 2025 before the deadline. Beyond the migration, Artifact Registry offers features that gcr.io never had: fine-grained IAM per repository, built-in vulnerability scanning, remote repository caching for Docker Hub images, and SBOM generation. This guide covers the migration and the new workflow.
Container Registry (gcr.io) was a single global namespace per project. Every image you pushed to gcr.io/my-project/ was accessible to anyone with project-level read access. Artifact Registry introduces repository-level IAM — you can grant a CI/CD service account push access to one repository without it having read access to others. This is significantly more secure for projects with multiple teams and multiple image repositories. Artifact Registry also supports multiple artifact formats beyond Docker: npm packages, Maven artifacts, Python packages, and Helm charts — all in one service with unified IAM.
Each Artifact Registry repository has a format (Docker, npm, Maven, etc.), a location (region), and an IAM policy. For Docker images, create one repository per team or per application cluster rather than one global repository. Use regional repositories (asia-southeast1 for Singapore region) to minimize pull latency from GKE clusters and Cloud Run services in the same region. Enable vulnerability scanning at the repository level — Artifact Registry integrates with Container Analysis to scan images for known CVEs on push and continuously as new vulnerabilities are published.
Google provides an automatic migration tool that creates Artifact Registry repositories in the gcr.io compatibility mode — images pushed to gcr.io/my-project/my-image are transparently redirected to the Artifact Registry backend. For a clean migration (recommended), create new pkg.dev repositories, update all CI/CD pipelines to push to the new registry URLs (asia-southeast1-docker.pkg.dev/my-project/my-repo/my-image:tag), and update all deployment configs (Kubernetes manifests, Cloud Run service definitions, docker-compose files) to pull from the new URLs. The migration window is well-documented — test with one low-stakes image first.
From my experience: use Artifact Registry's remote repository feature to cache Docker Hub images locally on GCP. Remote repositories act as a caching proxy — when your Cloud Build pulls node:20-alpine, it pulls from Artifact Registry's cache instead of Docker Hub, avoiding Docker Hub's rate limits (100 anonymous pulls per 6 hours). For a CI/CD pipeline that runs 50 builds per day all pulling node:20-alpine, the rate limit hit is guaranteed without a registry cache. One remote repository configuration in Artifact Registry solves this permanently.
A chaotic image tagging strategy causes deployment confusion — which version is running in production? The strategy I use: semantic version tags for release images (v1.2.3), git SHA tags for every build (sha-abc1234), and branch tags for branch-specific images (main-latest, feature-login-latest). Never use latest as your only tag — it's a floating tag that makes rollback impossible without knowing which SHA 'latest' pointed to at the time. In your CD pipeline, tag production images with both the semver version and the git SHA. This lets you correlate a running container to an exact code commit.
# Create Artifact Registry repository
gcloud artifacts repositories create my-app-images --repository-format=docker --location=asia-southeast1 --description="Application Docker images" --project=my-project
# Enable vulnerability scanning
gcloud artifacts repositories update my-app-images --location=asia-southeast1 --enable-vulnerability-scanning
# Grant CI/CD service account push access (repo-level, not project-level)
gcloud artifacts repositories add-iam-policy-binding my-app-images --location=asia-southeast1 --member="serviceAccount:cicd-sa@my-project.iam.gserviceaccount.com" --role="roles/artifactregistry.writer"
# Build and push with new registry URL
docker build -t asia-southeast1-docker.pkg.dev/my-project/my-app-images/api:v1.2.3 .
docker push asia-southeast1-docker.pkg.dev/my-project/my-app-images/api:v1.2.3
# Check vulnerability scan results
gcloud artifacts docker images describe asia-southeast1-docker.pkg.dev/my-project/my-app-images/api:v1.2.3 --show-package-vulnerabilityArtifact Registry integrates with Container Analysis for on-push vulnerability scanning. When you push an image, Container Analysis automatically scans it against the CVE database and surfaces findings in the Artifact Registry console. Enable this at the project level. In your CI/CD pipeline, add a step after push that queries Container Analysis for critical and high severity findings using gcloud artifacts docker images describe. Block deployment if critical vulnerabilities are found. This creates an automated security gate that prevents known-vulnerable images from reaching production.
┌─────────────────────────────────────────────────────┐
│ GCR → Artifact Registry Migration │
├─────────────────────────────────────────────────────┤
│ │
│ Before (gcr.io): │
│ gcr.io/my-project/my-image:tag │
│ → Project-level IAM only │
│ → No vulnerability scanning │
│ → Docker images only │
│ │
│ After (Artifact Registry): │
│ asia-southeast1-docker.pkg.dev/ │
│ my-project/my-repo/my-image:sha-abc1234 │
│ → Repository-level IAM │
│ → Built-in CVE scanning │
│ → Docker + npm + Maven + Helm │
└─────────────────────────────────────────────────────┘Artifact Registry charges egress for images pulled from outside GCP's network — $0.085/GB from Asia region to the internet. If your CI/CD runs on GitHub Actions (outside GCP) and your images are large (1-2GB), every build incurs egress charges. A 2GB image pulled 20 times per day costs approximately $102/month in egress alone. Solutions: run CI/CD inside GCP (Cloud Build has free pull from Artifact Registry in the same region), use multi-stage builds to keep images small, or use self-hosted GitHub Actions runners on GCP to eliminate egress charges. I moved our image-heavy builds to Cloud Build for exactly this reason.
Configure service accounts with minimal permissions for each role in your image workflow. CI/CD pipelines get roles/artifactregistry.writer on the specific repository they push to. Deployment systems (GKE, Cloud Run) get roles/artifactregistry.reader on repositories they pull from. Never grant project-level artifactregistry.admin to a CI/CD service account — this grants access to all repositories in the project. Repository-level IAM bindings, added via gcloud artifacts repositories add-iam-policy-binding, enforce principle of least privilege and limit the blast radius if a CI/CD credential is compromised.
Without cleanup policies, an Artifact Registry repository accumulates thousands of image tags over months. Storage is cheap ($0.10/GB/month) but unmanaged growth creates operational noise — which version is production? Which can be deleted safely? Artifact Registry supports cleanup policies that automatically delete untagged images older than N days and versions beyond the N most recent for a given tag prefix. Set a policy to keep the last 10 tagged versions and delete untagged images older than 7 days. Run this as a scheduled Cloud Scheduler + Cloud Run job for repositories that don't support native cleanup policies.
I was initially frustrated by the forced migration from gcr.io — our pipelines worked fine and the migration added work. After completing the migration and using Artifact Registry for several months, I genuinely prefer it. The repository-level IAM is more secure. The vulnerability scanning integration catches issues without extra tooling. The remote repository caching for Docker Hub has eliminated all our rate limit issues. The multi-format support means we now also use Artifact Registry for our npm package registry (private packages for internal tools), consolidating two separate systems into one. If you haven't migrated yet, do it now — the benefits are real.
Sources & Further Reading