Every container image, npm package and Python wheel your Google Cloud workloads run passes through Artifact Registry, so whoever can write to a repository can change what runs in production. Securing it is mostly a customer-side job: choosing repository-level IAM roles, deciding which repositories are public, routing public dependencies through remote and virtual repositories, turning on scanning, and logging the reads and writes that matter. Many teams arrived here through the Container Registry migration, carrying over broad Cloud Storage grants that no longer fit. This guide covers the Artifact Registry controls in practical depth, for platform engineers, supply chain security leads and cloud reviewers. For registry principles that apply to any product, read the Container Registry Security Guide; for the gcr.io timeline and cutover, see Google Container Registry Shutdown.
This is an independent guide from NHI Mgmt Group, based on Google Cloud's public documentation as of November 2026. It is not sponsored or reviewed by Google. Product features change, so confirm settings against Google Cloud's current documentation.
Key takeaways
- Grant at the repository, not the project. Artifact Registry roles can be bound on a single repository, so a build pipeline can write to one repository and read from three, instead of holding Writer across the project.
- Public means allUsers on Reader. A repository becomes anonymously readable when
roles/artifactregistry.readeris granted toallUsers; govern that with organisation policy and review it like any public bucket. - Virtual repositories decide the source. Put private packages in a standard repository, proxy public ones through a remote repository, and point clients only at a virtual repository that ranks private first.
- Scanning is a per-repository choice. Enabling the Artifact Analysis API starts automatic scanning on push, and each repository can allow or disable it, so confirm production repositories are scanned.
- Machine identities do most of the pushing. Cloud Build, GKE nodes and Cloud Run service agents read and write artifacts; give each a dedicated service account with the narrowest role, and federate external CI instead of using keys.
How identity and access work in Artifact Registry
Artifact Registry uses Google Cloud IAM directly. There are no registry-local users or robot accounts: every push and pull is made by a Google principal (user, group, service account or federated workload identity) and checked against IAM allow policies on the organisation, folder, project or individual repository. Docker clients authenticate with short-lived OAuth access tokens through the gcloud credential helper, and language clients (npm, pip, Maven) use equivalent credential helpers or keyrings.
Google documents two grant scopes. Project-level grants suit cases where the same access applies to every repository in the project; repository-level grants suit cases where principals need different access to different repositories. Repository-level grants are the safer default for anything that holds production artifacts. One rule needs care: a role granted on a virtual repository applies to every upstream repository it exposes, whatever the permissions on those upstreams, so treat access to a virtual repository as access to all of its upstreams.
| Role | Role ID | What it allows | Typical holder |
|---|---|---|---|
| Artifact Registry Reader | roles/artifactregistry.reader | Fetch artifacts and view repository metadata | Runtime service accounts, GKE node service accounts, developers |
| Artifact Registry Writer | roles/artifactregistry.writer | Read and upload artifacts | The build service account for that repository only |
| Artifact Registry Repository Administrator | roles/artifactregistry.repoAdmin | Read, upload and delete artifacts | Release engineering, cleanup automation |
| Artifact Registry Administrator | roles/artifactregistry.admin | Create and manage repositories and artifacts, including IAM and cleanup policies | Platform team, small named group |
| Artifact Registry Create-on-push Writer | roles/artifactregistry.createOnPushWriter | Read and write artifacts, and create gcr.io repositories on push to gcr.io URLs | Transition period only |
| Container Registry to Artifact Registry Migration Admin | roles/artifactregistry.containerRegistryMigrationAdmin | All permissions needed to run the migration tooling | Migration operator, removed afterwards |
Google advises against basic roles (Owner, Editor, Viewer) in production; use the predefined roles above so principals get only what they need. IAM Conditions and repository tags can further narrow a binding, for example to repositories tagged as production. For the wider model of roles and service accounts, see the Google Cloud IAM and Service Accounts Guide.
Public repositories
Anonymous read access is granted by binding the Reader role to allUsers, and removed by deleting that binding. Google recommends capping per-user request quotas on public repositories so one caller cannot exhaust the project quota. Keep public repositories in a dedicated project, publish only artifacts built for distribution, and use the iam.allowedPolicyMemberDomains (domain restricted sharing) organisation policy elsewhere so a public binding cannot be added to an internal repository by accident. See the Google Cloud Organization Policy Guide.
Admin roles and privileged access
The privileged actions in Artifact Registry are changing IAM on a repository, deleting artifacts or repositories, editing cleanup policies, and moving tags. Each can change what production pulls or remove evidence.
- Keep Administrator small:
roles/artifactregistry.admincan rewrite repository IAM and cleanup policies. Hold it in a small group, ideally granted just in time, and keep it off CI service accounts. - Separate write from delete: builds need Writer; only release or cleanup automation needs Repository Administrator. Splitting the two means a compromised build token can add an artifact but cannot remove history.
- Turn on immutable tags for release repositories: Docker repositories offer an Immutable image tags setting (
--immutable-tagsat creation, off by default, changeable later by an administrator). With it on, a tag such asv1.4.2cannot be moved to a different digest, so deployments by tag stay reproducible. - Retire transition roles: the create-on-push and migration roles exist for the gcr.io move. Once gcr.io repositories are created and traffic is cut over, replace them with standard Writer or Reader grants.
- Watch the Editor role on default service accounts: default accounts may receive Editor automatically, which includes artifact write. Google recommends enforcing
iam.automaticIamGrantsForDefaultServiceAccountsand replacing Editor with narrower roles.
Integrations, API tokens and service accounts
Most traffic to Artifact Registry is machine to machine, so the non-human identities around it carry most of the risk and most of the opportunity. Treat each integration as its own identity with its own scope.
| Integration | Identity used | What Google documents | Hardened setting |
|---|---|---|---|
| Cloud Build | Compute Engine default service account in newer projects, or the legacy Cloud Build service account, depending on organisation settings | Default permissions in the same project include upload and download | Run builds as a user-managed service account with Writer on the target repository only. See Google Cloud Build Security. |
| GKE nodes | Node service account | Pulls work without extra setup when GKE is in the same project and nodes use the default account with storage read access | Use a dedicated node service account with Reader on the needed repositories; grant cross-project Reader explicitly. |
| Cloud Run | Cloud Run service agent service-PROJECT-NUMBER@serverless-robot-prod.iam.gserviceaccount.com | Needs a role on the repository when the image is in another project | Grant Reader on the single repository. See the Cloud Run and Cloud Functions Security Guide. |
| Compute Engine VMs | Attached service account with access scopes | Docker pulls need storage-ro, storage-rw or cloud-platform; other formats need cloud-platform | Use cloud-platform with a narrowly granted service account, so IAM rather than scopes limits access. |
| External CI (GitHub Actions, GitLab, on-premises) | Federated principal | Google recommends Workload Identity Federation over service account keys | Federate with attribute conditions on repository and branch. See the Cloud Workload Identity Guide. |
| Remote repository upstreams | Username and password for the upstream | Remote repositories support HTTP basic authentication to upstreams | Store the upstream credential in Secret Manager, use a read-only upstream token and rotate it. |
Avoid downloaded service account keys and the _json_key Docker login pattern for automation: a key file in a CI variable is a long-lived credential that works from anywhere. The guide to replacing Google Cloud service account keys covers the migration path.
Remote and virtual repositories
A remote repository proxies an upstream such as Docker Hub, Maven Central, npmjs, PyPI or the Go proxy, caching each package version on first request. Cached copies stay available if the upstream goes offline, and mutable metadata refreshes on a schedule (for example five minutes for npm manifests and one hour for Python indexes and Docker tag lists). Custom upstream URLs must use HTTPS.
A virtual repository presents several upstream repositories of one format through a single endpoint, each with an integer priority where a higher value wins. Google's documented pattern against dependency confusion is:
- Create a remote repository as the proxy for the public index.
- Create a standard repository for internal packages.
- Create a virtual repository that gives the standard repository the higher priority, so an internal package name always resolves internally.
- Configure package managers to read only from the virtual repository, and remove direct public index settings such as extra index URLs in pip configuration, because a client that also searches a public index can still pull from it.
- Pin versions in requests and lock files; without a version a client may receive the latest version regardless of priority. See the Malicious Package and Dependency Confusion Guide.
Logging and detection
Artifact Registry writes Cloud Audit Logs under protoPayload.serviceName="artifactregistry.googleapis.com". Admin Activity logs are always written; push, pull and delete events are Data Access logs, which you turn on for the service. Turn on at least DATA_WRITE for all repositories and DATA_READ for production and public ones, and route them to a central sink. See the Google Cloud Audit Logs and Log Sinks Guide.
| Event | Method name in the log | Log type | Why it matters |
|---|---|---|---|
| IAM change on a repository | google.devtools.artifactregistry.v1.ArtifactRegistry.SetIamPolicy | Admin Activity | Alert on any binding to allUsers or allAuthenticatedUsers |
| Repository created, updated or deleted | CreateRepository, UpdateRepository, DeleteRepository | Admin Activity | Catches disabled scanning, changed cleanup policies, removed repositories |
| gcr.io repository created on push | Docker-CreateOnPush | Admin Activity | Unexpected new repositories during or after migration |
| Image push | Docker-StartUpload, Docker-FinishUpload, Docker-PutManifest | Data Access | Pushes by any identity other than the expected build account |
| Package publish | Npm-PublishPackage, Python-UploadPackage, Maven-Artifact-Upload | Data Access | Publishing from a laptop or an unknown principal |
| Image pull | Docker-GetManifest, Docker-ServeBlob | Data Access | Bulk pulls from unusual networks or identities |
| Deletion | Docker-DeleteManifest, Docker-DeleteTag, DeleteVersion, BatchDeleteVersions | Data Access | Deletes outside cleanup automation |
Map detections to MITRE ATT&CK T1525 Implant Internal Image (an unexpected push to a production repository), T1195.002 Compromise Software Supply Chain (a new package version from an unknown publisher) and T1078.004 Cloud Accounts (a service account used from an unexpected location).
Vulnerability findings
Enabling the Artifact Analysis API starts automatic scanning of new images pushed to Docker repositories, subject to each repository's scanning setting (--allow-vulnerability-scanning or --disable-vulnerability-scanning). Google documents scanning for Linux-based images, covering OS packages from major distributions and language packages including Go, Java, Node.js, Python, Ruby, Rust, PHP and .NET. Each digest is scanned on push and analysed continuously while it has been pulled within the last 30 days; after that its metadata goes stale, and stale metadata older than 90 days is archived. Pulling an image again triggers a rescan. Malicious packages from the OpenSSF Malicious Packages repository are reported as CRITICAL findings. On-demand scanning covers images before push, with results kept for 48 hours. Route findings into your vulnerability management process; see the Risk-Based Vulnerability Management Guide for triage practice.
Metrics that show it is working
- Share of repositories with repository-level grants only (target: all production repositories).
- Number of repositories bound to
allUsers, each with a named owner and justification. - Share of production Docker repositories with scanning allowed and immutable tags on.
- Pushes to production repositories by any identity other than the approved build service accounts (target: zero).
- Service account keys in use for Artifact Registry access (target: zero, replaced by federation).
- Median age of critical findings in images pulled in the last 30 days.
Hardening baseline
A baseline that platform teams can apply repository by repository, in roughly this order. Test each step in a non-production project first, and keep the previous IAM policy (gcloud artifacts repositories get-iam-policy) so a change can be reverted quickly.
- Inventory: list repositories, formats, locations and IAM bindings per project, and flag project-level Writer, basic roles and public bindings.
- Move grants to repositories: give each build service account Writer on its own repository and each runtime account Reader on what it pulls; then remove project-level Writer.
- Protect release tags: turn on immutable tags for release Docker repositories and deploy by digest where the platform supports it, verified with Binary Authorization for GKE and Cloud Run.
- Route dependencies: create remote repositories for each public index you use and a virtual repository per format that ranks internal packages first; switch client configuration to the virtual endpoint.
- Scan: enable the Artifact Analysis API, confirm scanning is allowed on each production repository, and send findings to your tracker.
- Encrypt to policy: where you need customer-managed keys, create the repository with
--kms-key, grantingroles/cloudkms.cryptoKeyEncrypterDecryptertoservice-PROJECT-NUMBER@gcp-sa-artifactregistry.iam.gserviceaccount.com. CMEK is chosen at creation and stays fixed for that repository, so plan it up front;constraints/gcp.restrictNonCmekServicescan require it. See the Cloud KMS and Cloud HSM Guide. - Add a perimeter: include
artifactregistry.googleapis.comin your VPC Service Controls perimeter with the services that consume it (GKE, Cloud Run, Artifact Analysis, Binary Authorization). Inside a perimeter, remote repositories reach external upstreams only once you allow them, and cachedmirror.gcr.ioimages need an egress rule. For gcr.io repositories use theANY_IDENTITYidentity type in ingress and egress rules. See the VPC Service Controls Guide. - Clean up safely: add cleanup policies with Delete and Keep actions (for example keep
mostRecentVersionsand tagged release versions), run them with--dry-runfirst, then apply with--no-dry-run. - Log and alert: turn on Data Access logs, route to a sink, and alert on the events in the table above.
Decision guidance
| Decision | Smaller organisation | Larger organisation |
|---|---|---|
| Repository layout | One project with a repository per format and environment | Central artifact project per environment, consumed cross-project with repository-level Reader |
| Public dependencies | One remote repository per index plus a virtual repository | Remote repositories per index, virtual repositories per team or format, and client configuration enforced through build images |
| Encryption | Google-owned keys unless policy requires otherwise | CMEK by organisation policy for regulated data, keys in a dedicated KMS project |
| Perimeter | Organisation policy and IAM first | VPC Service Controls around artifact and runtime projects, with documented egress for upstreams |
Framework mapping
| Control area | Artifact Registry setting | NIST SP 800-53 Rev 5 |
|---|---|---|
| Least privilege | Repository-level predefined roles, no basic roles | AC-6, AC-3 |
| Supply chain integrity | Virtual repositories, immutable tags, Binary Authorization | SR-3, SR-11, SI-7, CM-14 |
| Vulnerability scanning | Artifact Analysis automatic and on-demand scanning | RA-5 |
| Encryption at rest | CMEK repositories | SC-28, SC-12 |
| Boundary protection | VPC Service Controls perimeter | SC-7 |
| Audit logging | Admin Activity and Data Access logs | AU-2, AU-6 |
| Machine credentials | Workload Identity Federation instead of keys | IA-5 |
Common misconfigurations
- Project-level Writer for every pipeline: one compromised build credential can then overwrite images for every service. Bind Writer on the specific repository instead.
- Carrying over Cloud Storage grants after the gcr.io move: Container Registry access came from Cloud Storage roles on buckets; Artifact Registry uses its own roles, so review and replace the old grants rather than recreating them broadly.
- A public binding added for convenience:
allUserson a repository meant for a partner. Use a federated or partner service account with Reader, and keep public repositories in their own project. - Clients that still search the public index: a virtual repository protects only clients configured to use it alone. Remove extra index settings and enforce configuration in base build images.
- Scanning assumed, not confirmed: a repository created with scanning disabled, or images not pulled for over 30 days, produce stale findings. Check the setting and the metadata age for production images.
- Mutable release tags: deploying
:latestor a movable version tag lets a later push change what runs. Turn on immutable tags and deploy by digest.
Practitioner checklist
- Replace project-level and basic role grants with repository-level Reader, Writer or Repository Administrator.
- Review every
allUsersandallAuthenticatedUsersbinding and enforce domain restricted sharing outside public projects. - Run builds as dedicated service accounts with Writer on one repository, and federate external CI instead of using keys.
- Create remote and virtual repositories per format, rank internal packages first, and point clients only at the virtual endpoint.
- Turn on immutable tags for release Docker repositories and deploy by digest.
- Enable the Artifact Analysis API and confirm scanning is allowed on every production repository.
- Decide CMEK before creating regulated repositories and enforce it with organisation policy where required.
- Turn on Data Access logs and alert on SetIamPolicy, unexpected pushes and deletions.
- Remove create-on-push and migration roles once the gcr.io transition is complete.
Standards and references
- Google Cloud: Configure roles and permissions for Artifact Registry
- Google Cloud: Virtual repositories overview
- Google Cloud: Remote repositories overview
- Google Cloud: Artifact Analysis container scanning overview
- Google Cloud: Artifact Registry audit logging
- Google Cloud: Securing Artifact Registry with VPC Service Controls
- Google Cloud: Enabling customer-managed encryption keys
- NIST: SP 800-53 Rev 5 Security and Privacy Controls
Related NHI Mgmt Group resources: Container Registry Security Guide · Google Container Registry Shutdown · Google Cloud Build Security · Google Cloud Security Guide