Create GCP Artifact Registry Repositories

The gcp:artifactregistry/repository:Repository resource, part of the Pulumi GCP provider, defines an Artifact Registry repository: its format, location, mode (standard/virtual/remote), and configuration for encryption, cleanup, and upstream sources. This guide focuses on four capabilities: standard and multi-region repositories, virtual repositories for aggregating sources, remote repositories for caching public registries, and cleanup policies and encryption.

Repositories may reference Cloud KMS keys, Secret Manager secrets, or upstream repositories that must exist separately. The examples are intentionally small. Combine them with your own IAM policies, encryption keys, and access controls.

Create a standard Docker repository in a region

Most deployments start with a standard repository that stores artifacts in a single region.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const my_repo = new gcp.artifactregistry.Repository("my-repo", {
    location: "us-central1",
    repositoryId: "my-repository",
    description: "example docker repository",
    format: "DOCKER",
});
import pulumi
import pulumi_gcp as gcp

my_repo = gcp.artifactregistry.Repository("my-repo",
    location="us-central1",
    repository_id="my-repository",
    description="example docker repository",
    format="DOCKER")
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/artifactregistry"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := artifactregistry.NewRepository(ctx, "my-repo", &artifactregistry.RepositoryArgs{
			Location:     pulumi.String("us-central1"),
			RepositoryId: pulumi.String("my-repository"),
			Description:  pulumi.String("example docker repository"),
			Format:       pulumi.String("DOCKER"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var my_repo = new Gcp.ArtifactRegistry.Repository("my-repo", new()
    {
        Location = "us-central1",
        RepositoryId = "my-repository",
        Description = "example docker repository",
        Format = "DOCKER",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.artifactregistry.Repository;
import com.pulumi.gcp.artifactregistry.RepositoryArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var my_repo = new Repository("my-repo", RepositoryArgs.builder()
            .location("us-central1")
            .repositoryId("my-repository")
            .description("example docker repository")
            .format("DOCKER")
            .build());

    }
}
resources:
  my-repo:
    type: gcp:artifactregistry:Repository
    properties:
      location: us-central1
      repositoryId: my-repository
      description: example docker repository
      format: DOCKER

The repositoryId sets the repository name, while location specifies the region. The format property determines what artifact types the repository accepts (Docker, Maven, NPM, Python, etc.). Without additional configuration, the repository uses Google-managed encryption and has no cleanup policies.

Store artifacts across multiple regions

Applications deployed globally benefit from multi-region repositories that replicate artifacts across geographic areas.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const my_repo = new gcp.artifactregistry.Repository("my-repo", {
    repositoryId: "my-repository",
    description: "example docker repository",
    location: "us",
    format: "DOCKER",
});
import pulumi
import pulumi_gcp as gcp

my_repo = gcp.artifactregistry.Repository("my-repo",
    repository_id="my-repository",
    description="example docker repository",
    location="us",
    format="DOCKER")
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/artifactregistry"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := artifactregistry.NewRepository(ctx, "my-repo", &artifactregistry.RepositoryArgs{
			RepositoryId: pulumi.String("my-repository"),
			Description:  pulumi.String("example docker repository"),
			Location:     pulumi.String("us"),
			Format:       pulumi.String("DOCKER"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var my_repo = new Gcp.ArtifactRegistry.Repository("my-repo", new()
    {
        RepositoryId = "my-repository",
        Description = "example docker repository",
        Location = "us",
        Format = "DOCKER",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.artifactregistry.Repository;
import com.pulumi.gcp.artifactregistry.RepositoryArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var my_repo = new Repository("my-repo", RepositoryArgs.builder()
            .repositoryId("my-repository")
            .description("example docker repository")
            .location("us")
            .format("DOCKER")
            .build());

    }
}
resources:
  my-repo:
    type: gcp:artifactregistry:Repository
    properties:
      repositoryId: my-repository
      description: example docker repository
      location: us
      format: DOCKER

Setting location to a multi-region value like “us”, “europe”, or “asia” replicates artifacts across multiple regions within that geography. This reduces latency for distributed teams and provides redundancy, but increases storage costs compared to single-region repositories.

Enforce immutable tags for Docker images

Production environments often require that image tags remain stable once pushed.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const my_repo = new gcp.artifactregistry.Repository("my-repo", {
    location: "us-central1",
    repositoryId: "my-repository",
    description: "example docker repository",
    format: "DOCKER",
    dockerConfig: {
        immutableTags: true,
    },
});
import pulumi
import pulumi_gcp as gcp

my_repo = gcp.artifactregistry.Repository("my-repo",
    location="us-central1",
    repository_id="my-repository",
    description="example docker repository",
    format="DOCKER",
    docker_config={
        "immutable_tags": True,
    })
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/artifactregistry"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := artifactregistry.NewRepository(ctx, "my-repo", &artifactregistry.RepositoryArgs{
			Location:     pulumi.String("us-central1"),
			RepositoryId: pulumi.String("my-repository"),
			Description:  pulumi.String("example docker repository"),
			Format:       pulumi.String("DOCKER"),
			DockerConfig: &artifactregistry.RepositoryDockerConfigArgs{
				ImmutableTags: pulumi.Bool(true),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var my_repo = new Gcp.ArtifactRegistry.Repository("my-repo", new()
    {
        Location = "us-central1",
        RepositoryId = "my-repository",
        Description = "example docker repository",
        Format = "DOCKER",
        DockerConfig = new Gcp.ArtifactRegistry.Inputs.RepositoryDockerConfigArgs
        {
            ImmutableTags = true,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.artifactregistry.Repository;
import com.pulumi.gcp.artifactregistry.RepositoryArgs;
import com.pulumi.gcp.artifactregistry.inputs.RepositoryDockerConfigArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var my_repo = new Repository("my-repo", RepositoryArgs.builder()
            .location("us-central1")
            .repositoryId("my-repository")
            .description("example docker repository")
            .format("DOCKER")
            .dockerConfig(RepositoryDockerConfigArgs.builder()
                .immutableTags(true)
                .build())
            .build());

    }
}
resources:
  my-repo:
    type: gcp:artifactregistry:Repository
    properties:
      location: us-central1
      repositoryId: my-repository
      description: example docker repository
      format: DOCKER
      dockerConfig:
        immutableTags: true

The dockerConfig block enables Docker-specific features. Setting immutableTags to true prevents tag overwrites, ensuring that a tag like “v1.0” always points to the same image digest. This prevents accidental overwrites that could break production deployments.

Encrypt repository contents with customer-managed keys

Organizations with strict compliance requirements use customer-managed encryption keys to control how artifact data is encrypted.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const project = gcp.organizations.getProject({});
const cryptoKey = new gcp.kms.CryptoKeyIAMMember("crypto_key", {
    cryptoKeyId: "kms-key",
    role: "roles/cloudkms.cryptoKeyEncrypterDecrypter",
    member: project.then(project => `serviceAccount:service-${project.number}@gcp-sa-artifactregistry.iam.gserviceaccount.com`),
});
const my_repo = new gcp.artifactregistry.Repository("my-repo", {
    location: "us-central1",
    repositoryId: "my-repository",
    description: "example docker repository with cmek",
    format: "DOCKER",
    kmsKeyName: "kms-key",
}, {
    dependsOn: [cryptoKey],
});
import pulumi
import pulumi_gcp as gcp

project = gcp.organizations.get_project()
crypto_key = gcp.kms.CryptoKeyIAMMember("crypto_key",
    crypto_key_id="kms-key",
    role="roles/cloudkms.cryptoKeyEncrypterDecrypter",
    member=f"serviceAccount:service-{project.number}@gcp-sa-artifactregistry.iam.gserviceaccount.com")
my_repo = gcp.artifactregistry.Repository("my-repo",
    location="us-central1",
    repository_id="my-repository",
    description="example docker repository with cmek",
    format="DOCKER",
    kms_key_name="kms-key",
    opts = pulumi.ResourceOptions(depends_on=[crypto_key]))
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/artifactregistry"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/kms"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/organizations"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		project, err := organizations.LookupProject(ctx, &organizations.LookupProjectArgs{}, nil)
		if err != nil {
			return err
		}
		cryptoKey, err := kms.NewCryptoKeyIAMMember(ctx, "crypto_key", &kms.CryptoKeyIAMMemberArgs{
			CryptoKeyId: pulumi.String("kms-key"),
			Role:        pulumi.String("roles/cloudkms.cryptoKeyEncrypterDecrypter"),
			Member:      pulumi.Sprintf("serviceAccount:service-%v@gcp-sa-artifactregistry.iam.gserviceaccount.com", project.Number),
		})
		if err != nil {
			return err
		}
		_, err = artifactregistry.NewRepository(ctx, "my-repo", &artifactregistry.RepositoryArgs{
			Location:     pulumi.String("us-central1"),
			RepositoryId: pulumi.String("my-repository"),
			Description:  pulumi.String("example docker repository with cmek"),
			Format:       pulumi.String("DOCKER"),
			KmsKeyName:   pulumi.String("kms-key"),
		}, pulumi.DependsOn([]pulumi.Resource{
			cryptoKey,
		}))
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var project = Gcp.Organizations.GetProject.Invoke();

    var cryptoKey = new Gcp.Kms.CryptoKeyIAMMember("crypto_key", new()
    {
        CryptoKeyId = "kms-key",
        Role = "roles/cloudkms.cryptoKeyEncrypterDecrypter",
        Member = $"serviceAccount:service-{project.Apply(getProjectResult => getProjectResult.Number)}@gcp-sa-artifactregistry.iam.gserviceaccount.com",
    });

    var my_repo = new Gcp.ArtifactRegistry.Repository("my-repo", new()
    {
        Location = "us-central1",
        RepositoryId = "my-repository",
        Description = "example docker repository with cmek",
        Format = "DOCKER",
        KmsKeyName = "kms-key",
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            cryptoKey,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.organizations.OrganizationsFunctions;
import com.pulumi.gcp.organizations.inputs.GetProjectArgs;
import com.pulumi.gcp.kms.CryptoKeyIAMMember;
import com.pulumi.gcp.kms.CryptoKeyIAMMemberArgs;
import com.pulumi.gcp.artifactregistry.Repository;
import com.pulumi.gcp.artifactregistry.RepositoryArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        final var project = OrganizationsFunctions.getProject(GetProjectArgs.builder()
            .build());

        var cryptoKey = new CryptoKeyIAMMember("cryptoKey", CryptoKeyIAMMemberArgs.builder()
            .cryptoKeyId("kms-key")
            .role("roles/cloudkms.cryptoKeyEncrypterDecrypter")
            .member(String.format("serviceAccount:service-%s@gcp-sa-artifactregistry.iam.gserviceaccount.com", project.number()))
            .build());

        var my_repo = new Repository("my-repo", RepositoryArgs.builder()
            .location("us-central1")
            .repositoryId("my-repository")
            .description("example docker repository with cmek")
            .format("DOCKER")
            .kmsKeyName("kms-key")
            .build(), CustomResourceOptions.builder()
                .dependsOn(cryptoKey)
                .build());

    }
}
resources:
  my-repo:
    type: gcp:artifactregistry:Repository
    properties:
      location: us-central1
      repositoryId: my-repository
      description: example docker repository with cmek
      format: DOCKER
      kmsKeyName: kms-key
    options:
      dependsOn:
        - ${cryptoKey}
  cryptoKey:
    type: gcp:kms:CryptoKeyIAMMember
    name: crypto_key
    properties:
      cryptoKeyId: kms-key
      role: roles/cloudkms.cryptoKeyEncrypterDecrypter
      member: serviceAccount:service-${project.number}@gcp-sa-artifactregistry.iam.gserviceaccount.com
variables:
  project:
    fn::invoke:
      function: gcp:organizations:getProject
      arguments: {}

The kmsKeyName property references a Cloud KMS key for encryption. Before creating the repository, you must grant the Artifact Registry service account the cryptoKeyEncrypterDecrypter role on the key. The dependsOn ensures the IAM binding completes before repository creation.

Aggregate multiple repositories into a virtual view

Teams managing multiple artifact sources can create virtual repositories that present a unified view.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const my_repo_upstream_1 = new gcp.artifactregistry.Repository("my-repo-upstream-1", {
    location: "us-central1",
    repositoryId: "my-repository-upstream-1",
    description: "example docker repository (upstream source) 1",
    format: "DOCKER",
});
const my_repo_upstream_2 = new gcp.artifactregistry.Repository("my-repo-upstream-2", {
    location: "us-central1",
    repositoryId: "my-repository-upstream-2",
    description: "example docker repository (upstream source) 2",
    format: "DOCKER",
});
const my_repo = new gcp.artifactregistry.Repository("my-repo", {
    location: "us-central1",
    repositoryId: "my-repository",
    description: "example virtual docker repository",
    format: "DOCKER",
    mode: "VIRTUAL_REPOSITORY",
    virtualRepositoryConfig: {
        upstreamPolicies: [
            {
                id: "my-repository-upstream-1",
                repository: my_repo_upstream_1.id,
                priority: 20,
            },
            {
                id: "my-repository-upstream-2",
                repository: my_repo_upstream_2.id,
                priority: 10,
            },
        ],
    },
});
import pulumi
import pulumi_gcp as gcp

my_repo_upstream_1 = gcp.artifactregistry.Repository("my-repo-upstream-1",
    location="us-central1",
    repository_id="my-repository-upstream-1",
    description="example docker repository (upstream source) 1",
    format="DOCKER")
my_repo_upstream_2 = gcp.artifactregistry.Repository("my-repo-upstream-2",
    location="us-central1",
    repository_id="my-repository-upstream-2",
    description="example docker repository (upstream source) 2",
    format="DOCKER")
my_repo = gcp.artifactregistry.Repository("my-repo",
    location="us-central1",
    repository_id="my-repository",
    description="example virtual docker repository",
    format="DOCKER",
    mode="VIRTUAL_REPOSITORY",
    virtual_repository_config={
        "upstream_policies": [
            {
                "id": "my-repository-upstream-1",
                "repository": my_repo_upstream_1.id,
                "priority": 20,
            },
            {
                "id": "my-repository-upstream-2",
                "repository": my_repo_upstream_2.id,
                "priority": 10,
            },
        ],
    })
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/artifactregistry"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		my_repo_upstream_1, err := artifactregistry.NewRepository(ctx, "my-repo-upstream-1", &artifactregistry.RepositoryArgs{
			Location:     pulumi.String("us-central1"),
			RepositoryId: pulumi.String("my-repository-upstream-1"),
			Description:  pulumi.String("example docker repository (upstream source) 1"),
			Format:       pulumi.String("DOCKER"),
		})
		if err != nil {
			return err
		}
		my_repo_upstream_2, err := artifactregistry.NewRepository(ctx, "my-repo-upstream-2", &artifactregistry.RepositoryArgs{
			Location:     pulumi.String("us-central1"),
			RepositoryId: pulumi.String("my-repository-upstream-2"),
			Description:  pulumi.String("example docker repository (upstream source) 2"),
			Format:       pulumi.String("DOCKER"),
		})
		if err != nil {
			return err
		}
		_, err = artifactregistry.NewRepository(ctx, "my-repo", &artifactregistry.RepositoryArgs{
			Location:     pulumi.String("us-central1"),
			RepositoryId: pulumi.String("my-repository"),
			Description:  pulumi.String("example virtual docker repository"),
			Format:       pulumi.String("DOCKER"),
			Mode:         pulumi.String("VIRTUAL_REPOSITORY"),
			VirtualRepositoryConfig: &artifactregistry.RepositoryVirtualRepositoryConfigArgs{
				UpstreamPolicies: artifactregistry.RepositoryVirtualRepositoryConfigUpstreamPolicyArray{
					&artifactregistry.RepositoryVirtualRepositoryConfigUpstreamPolicyArgs{
						Id:         pulumi.String("my-repository-upstream-1"),
						Repository: my_repo_upstream_1.ID(),
						Priority:   pulumi.Int(20),
					},
					&artifactregistry.RepositoryVirtualRepositoryConfigUpstreamPolicyArgs{
						Id:         pulumi.String("my-repository-upstream-2"),
						Repository: my_repo_upstream_2.ID(),
						Priority:   pulumi.Int(10),
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var my_repo_upstream_1 = new Gcp.ArtifactRegistry.Repository("my-repo-upstream-1", new()
    {
        Location = "us-central1",
        RepositoryId = "my-repository-upstream-1",
        Description = "example docker repository (upstream source) 1",
        Format = "DOCKER",
    });

    var my_repo_upstream_2 = new Gcp.ArtifactRegistry.Repository("my-repo-upstream-2", new()
    {
        Location = "us-central1",
        RepositoryId = "my-repository-upstream-2",
        Description = "example docker repository (upstream source) 2",
        Format = "DOCKER",
    });

    var my_repo = new Gcp.ArtifactRegistry.Repository("my-repo", new()
    {
        Location = "us-central1",
        RepositoryId = "my-repository",
        Description = "example virtual docker repository",
        Format = "DOCKER",
        Mode = "VIRTUAL_REPOSITORY",
        VirtualRepositoryConfig = new Gcp.ArtifactRegistry.Inputs.RepositoryVirtualRepositoryConfigArgs
        {
            UpstreamPolicies = new[]
            {
                new Gcp.ArtifactRegistry.Inputs.RepositoryVirtualRepositoryConfigUpstreamPolicyArgs
                {
                    Id = "my-repository-upstream-1",
                    Repository = my_repo_upstream_1.Id,
                    Priority = 20,
                },
                new Gcp.ArtifactRegistry.Inputs.RepositoryVirtualRepositoryConfigUpstreamPolicyArgs
                {
                    Id = "my-repository-upstream-2",
                    Repository = my_repo_upstream_2.Id,
                    Priority = 10,
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.artifactregistry.Repository;
import com.pulumi.gcp.artifactregistry.RepositoryArgs;
import com.pulumi.gcp.artifactregistry.inputs.RepositoryVirtualRepositoryConfigArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var my_repo_upstream_1 = new Repository("my-repo-upstream-1", RepositoryArgs.builder()
            .location("us-central1")
            .repositoryId("my-repository-upstream-1")
            .description("example docker repository (upstream source) 1")
            .format("DOCKER")
            .build());

        var my_repo_upstream_2 = new Repository("my-repo-upstream-2", RepositoryArgs.builder()
            .location("us-central1")
            .repositoryId("my-repository-upstream-2")
            .description("example docker repository (upstream source) 2")
            .format("DOCKER")
            .build());

        var my_repo = new Repository("my-repo", RepositoryArgs.builder()
            .location("us-central1")
            .repositoryId("my-repository")
            .description("example virtual docker repository")
            .format("DOCKER")
            .mode("VIRTUAL_REPOSITORY")
            .virtualRepositoryConfig(RepositoryVirtualRepositoryConfigArgs.builder()
                .upstreamPolicies(                
                    RepositoryVirtualRepositoryConfigUpstreamPolicyArgs.builder()
                        .id("my-repository-upstream-1")
                        .repository(my_repo_upstream_1.id())
                        .priority(20)
                        .build(),
                    RepositoryVirtualRepositoryConfigUpstreamPolicyArgs.builder()
                        .id("my-repository-upstream-2")
                        .repository(my_repo_upstream_2.id())
                        .priority(10)
                        .build())
                .build())
            .build());

    }
}
resources:
  my-repo-upstream-1:
    type: gcp:artifactregistry:Repository
    properties:
      location: us-central1
      repositoryId: my-repository-upstream-1
      description: example docker repository (upstream source) 1
      format: DOCKER
  my-repo-upstream-2:
    type: gcp:artifactregistry:Repository
    properties:
      location: us-central1
      repositoryId: my-repository-upstream-2
      description: example docker repository (upstream source) 2
      format: DOCKER
  my-repo:
    type: gcp:artifactregistry:Repository
    properties:
      location: us-central1
      repositoryId: my-repository
      description: example virtual docker repository
      format: DOCKER
      mode: VIRTUAL_REPOSITORY
      virtualRepositoryConfig:
        upstreamPolicies:
          - id: my-repository-upstream-1
            repository: ${["my-repo-upstream-1"].id}
            priority: 20
          - id: my-repository-upstream-2
            repository: ${["my-repo-upstream-2"].id}
            priority: 10

Setting mode to VIRTUAL_REPOSITORY creates a repository that routes requests to upstream repositories based on priority. The virtualRepositoryConfig defines upstreamPolicies with priority values (lower numbers = higher priority). When you pull an artifact, the virtual repository checks upstream sources in priority order.

Cache Docker Hub images locally

Organizations reduce external dependencies and improve build performance by caching public registry images.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const my_repo = new gcp.artifactregistry.Repository("my-repo", {
    location: "us-central1",
    repositoryId: "my-repository",
    description: "example remote docker repository",
    format: "DOCKER",
    mode: "REMOTE_REPOSITORY",
    remoteRepositoryConfig: {
        description: "docker hub",
        dockerRepository: {
            publicRepository: "DOCKER_HUB",
        },
    },
});
import pulumi
import pulumi_gcp as gcp

my_repo = gcp.artifactregistry.Repository("my-repo",
    location="us-central1",
    repository_id="my-repository",
    description="example remote docker repository",
    format="DOCKER",
    mode="REMOTE_REPOSITORY",
    remote_repository_config={
        "description": "docker hub",
        "docker_repository": {
            "public_repository": "DOCKER_HUB",
        },
    })
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/artifactregistry"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := artifactregistry.NewRepository(ctx, "my-repo", &artifactregistry.RepositoryArgs{
			Location:     pulumi.String("us-central1"),
			RepositoryId: pulumi.String("my-repository"),
			Description:  pulumi.String("example remote docker repository"),
			Format:       pulumi.String("DOCKER"),
			Mode:         pulumi.String("REMOTE_REPOSITORY"),
			RemoteRepositoryConfig: &artifactregistry.RepositoryRemoteRepositoryConfigArgs{
				Description: pulumi.String("docker hub"),
				DockerRepository: &artifactregistry.RepositoryRemoteRepositoryConfigDockerRepositoryArgs{
					PublicRepository: pulumi.String("DOCKER_HUB"),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var my_repo = new Gcp.ArtifactRegistry.Repository("my-repo", new()
    {
        Location = "us-central1",
        RepositoryId = "my-repository",
        Description = "example remote docker repository",
        Format = "DOCKER",
        Mode = "REMOTE_REPOSITORY",
        RemoteRepositoryConfig = new Gcp.ArtifactRegistry.Inputs.RepositoryRemoteRepositoryConfigArgs
        {
            Description = "docker hub",
            DockerRepository = new Gcp.ArtifactRegistry.Inputs.RepositoryRemoteRepositoryConfigDockerRepositoryArgs
            {
                PublicRepository = "DOCKER_HUB",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.artifactregistry.Repository;
import com.pulumi.gcp.artifactregistry.RepositoryArgs;
import com.pulumi.gcp.artifactregistry.inputs.RepositoryRemoteRepositoryConfigArgs;
import com.pulumi.gcp.artifactregistry.inputs.RepositoryRemoteRepositoryConfigDockerRepositoryArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var my_repo = new Repository("my-repo", RepositoryArgs.builder()
            .location("us-central1")
            .repositoryId("my-repository")
            .description("example remote docker repository")
            .format("DOCKER")
            .mode("REMOTE_REPOSITORY")
            .remoteRepositoryConfig(RepositoryRemoteRepositoryConfigArgs.builder()
                .description("docker hub")
                .dockerRepository(RepositoryRemoteRepositoryConfigDockerRepositoryArgs.builder()
                    .publicRepository("DOCKER_HUB")
                    .build())
                .build())
            .build());

    }
}
resources:
  my-repo:
    type: gcp:artifactregistry:Repository
    properties:
      location: us-central1
      repositoryId: my-repository
      description: example remote docker repository
      format: DOCKER
      mode: REMOTE_REPOSITORY
      remoteRepositoryConfig:
        description: docker hub
        dockerRepository:
          publicRepository: DOCKER_HUB

Setting mode to REMOTE_REPOSITORY creates a pull-through cache. The remoteRepositoryConfig specifies the upstream source (Docker Hub in this case). When you pull an image through this repository, Artifact Registry fetches it from Docker Hub and caches it locally for subsequent pulls.

Automate artifact deletion with cleanup policies

Repositories accumulate old artifacts over time. Cleanup policies automatically delete untagged images, old prereleases, or versions beyond a retention count.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const my_repo = new gcp.artifactregistry.Repository("my-repo", {
    location: "us-central1",
    repositoryId: "my-repository",
    description: "example docker repository with cleanup policies",
    format: "DOCKER",
    cleanupPolicyDryRun: false,
    cleanupPolicies: [
        {
            id: "delete-untagged",
            action: "DELETE",
            condition: {
                tagState: "UNTAGGED",
            },
        },
        {
            id: "keep-new-untagged",
            action: "KEEP",
            condition: {
                tagState: "UNTAGGED",
                newerThan: "7d",
            },
        },
        {
            id: "delete-prerelease",
            action: "DELETE",
            condition: {
                tagState: "TAGGED",
                tagPrefixes: [
                    "alpha",
                    "v0",
                ],
                olderThan: "30d",
            },
        },
        {
            id: "keep-tagged-release",
            action: "KEEP",
            condition: {
                tagState: "TAGGED",
                tagPrefixes: ["release"],
                packageNamePrefixes: [
                    "webapp",
                    "mobile",
                ],
            },
        },
        {
            id: "keep-minimum-versions",
            action: "KEEP",
            mostRecentVersions: {
                packageNamePrefixes: [
                    "webapp",
                    "mobile",
                    "sandbox",
                ],
                keepCount: 5,
            },
        },
    ],
});
import pulumi
import pulumi_gcp as gcp

my_repo = gcp.artifactregistry.Repository("my-repo",
    location="us-central1",
    repository_id="my-repository",
    description="example docker repository with cleanup policies",
    format="DOCKER",
    cleanup_policy_dry_run=False,
    cleanup_policies=[
        {
            "id": "delete-untagged",
            "action": "DELETE",
            "condition": {
                "tag_state": "UNTAGGED",
            },
        },
        {
            "id": "keep-new-untagged",
            "action": "KEEP",
            "condition": {
                "tag_state": "UNTAGGED",
                "newer_than": "7d",
            },
        },
        {
            "id": "delete-prerelease",
            "action": "DELETE",
            "condition": {
                "tag_state": "TAGGED",
                "tag_prefixes": [
                    "alpha",
                    "v0",
                ],
                "older_than": "30d",
            },
        },
        {
            "id": "keep-tagged-release",
            "action": "KEEP",
            "condition": {
                "tag_state": "TAGGED",
                "tag_prefixes": ["release"],
                "package_name_prefixes": [
                    "webapp",
                    "mobile",
                ],
            },
        },
        {
            "id": "keep-minimum-versions",
            "action": "KEEP",
            "most_recent_versions": {
                "package_name_prefixes": [
                    "webapp",
                    "mobile",
                    "sandbox",
                ],
                "keep_count": 5,
            },
        },
    ])
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/artifactregistry"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := artifactregistry.NewRepository(ctx, "my-repo", &artifactregistry.RepositoryArgs{
			Location:            pulumi.String("us-central1"),
			RepositoryId:        pulumi.String("my-repository"),
			Description:         pulumi.String("example docker repository with cleanup policies"),
			Format:              pulumi.String("DOCKER"),
			CleanupPolicyDryRun: pulumi.Bool(false),
			CleanupPolicies: artifactregistry.RepositoryCleanupPolicyArray{
				&artifactregistry.RepositoryCleanupPolicyArgs{
					Id:     pulumi.String("delete-untagged"),
					Action: pulumi.String("DELETE"),
					Condition: &artifactregistry.RepositoryCleanupPolicyConditionArgs{
						TagState: pulumi.String("UNTAGGED"),
					},
				},
				&artifactregistry.RepositoryCleanupPolicyArgs{
					Id:     pulumi.String("keep-new-untagged"),
					Action: pulumi.String("KEEP"),
					Condition: &artifactregistry.RepositoryCleanupPolicyConditionArgs{
						TagState:  pulumi.String("UNTAGGED"),
						NewerThan: pulumi.String("7d"),
					},
				},
				&artifactregistry.RepositoryCleanupPolicyArgs{
					Id:     pulumi.String("delete-prerelease"),
					Action: pulumi.String("DELETE"),
					Condition: &artifactregistry.RepositoryCleanupPolicyConditionArgs{
						TagState: pulumi.String("TAGGED"),
						TagPrefixes: pulumi.StringArray{
							pulumi.String("alpha"),
							pulumi.String("v0"),
						},
						OlderThan: pulumi.String("30d"),
					},
				},
				&artifactregistry.RepositoryCleanupPolicyArgs{
					Id:     pulumi.String("keep-tagged-release"),
					Action: pulumi.String("KEEP"),
					Condition: &artifactregistry.RepositoryCleanupPolicyConditionArgs{
						TagState: pulumi.String("TAGGED"),
						TagPrefixes: pulumi.StringArray{
							pulumi.String("release"),
						},
						PackageNamePrefixes: pulumi.StringArray{
							pulumi.String("webapp"),
							pulumi.String("mobile"),
						},
					},
				},
				&artifactregistry.RepositoryCleanupPolicyArgs{
					Id:     pulumi.String("keep-minimum-versions"),
					Action: pulumi.String("KEEP"),
					MostRecentVersions: &artifactregistry.RepositoryCleanupPolicyMostRecentVersionsArgs{
						PackageNamePrefixes: pulumi.StringArray{
							pulumi.String("webapp"),
							pulumi.String("mobile"),
							pulumi.String("sandbox"),
						},
						KeepCount: pulumi.Int(5),
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var my_repo = new Gcp.ArtifactRegistry.Repository("my-repo", new()
    {
        Location = "us-central1",
        RepositoryId = "my-repository",
        Description = "example docker repository with cleanup policies",
        Format = "DOCKER",
        CleanupPolicyDryRun = false,
        CleanupPolicies = new[]
        {
            new Gcp.ArtifactRegistry.Inputs.RepositoryCleanupPolicyArgs
            {
                Id = "delete-untagged",
                Action = "DELETE",
                Condition = new Gcp.ArtifactRegistry.Inputs.RepositoryCleanupPolicyConditionArgs
                {
                    TagState = "UNTAGGED",
                },
            },
            new Gcp.ArtifactRegistry.Inputs.RepositoryCleanupPolicyArgs
            {
                Id = "keep-new-untagged",
                Action = "KEEP",
                Condition = new Gcp.ArtifactRegistry.Inputs.RepositoryCleanupPolicyConditionArgs
                {
                    TagState = "UNTAGGED",
                    NewerThan = "7d",
                },
            },
            new Gcp.ArtifactRegistry.Inputs.RepositoryCleanupPolicyArgs
            {
                Id = "delete-prerelease",
                Action = "DELETE",
                Condition = new Gcp.ArtifactRegistry.Inputs.RepositoryCleanupPolicyConditionArgs
                {
                    TagState = "TAGGED",
                    TagPrefixes = new[]
                    {
                        "alpha",
                        "v0",
                    },
                    OlderThan = "30d",
                },
            },
            new Gcp.ArtifactRegistry.Inputs.RepositoryCleanupPolicyArgs
            {
                Id = "keep-tagged-release",
                Action = "KEEP",
                Condition = new Gcp.ArtifactRegistry.Inputs.RepositoryCleanupPolicyConditionArgs
                {
                    TagState = "TAGGED",
                    TagPrefixes = new[]
                    {
                        "release",
                    },
                    PackageNamePrefixes = new[]
                    {
                        "webapp",
                        "mobile",
                    },
                },
            },
            new Gcp.ArtifactRegistry.Inputs.RepositoryCleanupPolicyArgs
            {
                Id = "keep-minimum-versions",
                Action = "KEEP",
                MostRecentVersions = new Gcp.ArtifactRegistry.Inputs.RepositoryCleanupPolicyMostRecentVersionsArgs
                {
                    PackageNamePrefixes = new[]
                    {
                        "webapp",
                        "mobile",
                        "sandbox",
                    },
                    KeepCount = 5,
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.artifactregistry.Repository;
import com.pulumi.gcp.artifactregistry.RepositoryArgs;
import com.pulumi.gcp.artifactregistry.inputs.RepositoryCleanupPolicyArgs;
import com.pulumi.gcp.artifactregistry.inputs.RepositoryCleanupPolicyConditionArgs;
import com.pulumi.gcp.artifactregistry.inputs.RepositoryCleanupPolicyMostRecentVersionsArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var my_repo = new Repository("my-repo", RepositoryArgs.builder()
            .location("us-central1")
            .repositoryId("my-repository")
            .description("example docker repository with cleanup policies")
            .format("DOCKER")
            .cleanupPolicyDryRun(false)
            .cleanupPolicies(            
                RepositoryCleanupPolicyArgs.builder()
                    .id("delete-untagged")
                    .action("DELETE")
                    .condition(RepositoryCleanupPolicyConditionArgs.builder()
                        .tagState("UNTAGGED")
                        .build())
                    .build(),
                RepositoryCleanupPolicyArgs.builder()
                    .id("keep-new-untagged")
                    .action("KEEP")
                    .condition(RepositoryCleanupPolicyConditionArgs.builder()
                        .tagState("UNTAGGED")
                        .newerThan("7d")
                        .build())
                    .build(),
                RepositoryCleanupPolicyArgs.builder()
                    .id("delete-prerelease")
                    .action("DELETE")
                    .condition(RepositoryCleanupPolicyConditionArgs.builder()
                        .tagState("TAGGED")
                        .tagPrefixes(                        
                            "alpha",
                            "v0")
                        .olderThan("30d")
                        .build())
                    .build(),
                RepositoryCleanupPolicyArgs.builder()
                    .id("keep-tagged-release")
                    .action("KEEP")
                    .condition(RepositoryCleanupPolicyConditionArgs.builder()
                        .tagState("TAGGED")
                        .tagPrefixes("release")
                        .packageNamePrefixes(                        
                            "webapp",
                            "mobile")
                        .build())
                    .build(),
                RepositoryCleanupPolicyArgs.builder()
                    .id("keep-minimum-versions")
                    .action("KEEP")
                    .mostRecentVersions(RepositoryCleanupPolicyMostRecentVersionsArgs.builder()
                        .packageNamePrefixes(                        
                            "webapp",
                            "mobile",
                            "sandbox")
                        .keepCount(5)
                        .build())
                    .build())
            .build());

    }
}
resources:
  my-repo:
    type: gcp:artifactregistry:Repository
    properties:
      location: us-central1
      repositoryId: my-repository
      description: example docker repository with cleanup policies
      format: DOCKER
      cleanupPolicyDryRun: false
      cleanupPolicies:
        - id: delete-untagged
          action: DELETE
          condition:
            tagState: UNTAGGED
        - id: keep-new-untagged
          action: KEEP
          condition:
            tagState: UNTAGGED
            newerThan: 7d
        - id: delete-prerelease
          action: DELETE
          condition:
            tagState: TAGGED
            tagPrefixes:
              - alpha
              - v0
            olderThan: 30d
        - id: keep-tagged-release
          action: KEEP
          condition:
            tagState: TAGGED
            tagPrefixes:
              - release
            packageNamePrefixes:
              - webapp
              - mobile
        - id: keep-minimum-versions
          action: KEEP
          mostRecentVersions:
            packageNamePrefixes:
              - webapp
              - mobile
              - sandbox
            keepCount: 5

The cleanupPolicies array defines rules for automatic deletion. Each policy has an action (DELETE or KEEP), a condition (tagState, age, tag prefixes), and optional mostRecentVersions for retention counts. Setting cleanupPolicyDryRun to false enables actual deletion; true runs policies without deleting.

Authenticate to Docker Hub with stored credentials

Private Docker Hub repositories require authentication. Artifact Registry can store credentials in Secret Manager and use them to pull images.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const project = gcp.organizations.getProject({});
const example_remote_secret = new gcp.secretmanager.Secret("example-remote-secret", {
    secretId: "example-secret",
    replication: {
        auto: {},
    },
});
const example_remote_secretVersion = new gcp.secretmanager.SecretVersion("example-remote-secret_version", {
    secret: example_remote_secret.id,
    secretData: "remote-password",
});
const secret_access = new gcp.secretmanager.SecretIamMember("secret-access", {
    secretId: example_remote_secret.id,
    role: "roles/secretmanager.secretAccessor",
    member: project.then(project => `serviceAccount:service-${project.number}@gcp-sa-artifactregistry.iam.gserviceaccount.com`),
});
const my_repo = new gcp.artifactregistry.Repository("my-repo", {
    location: "us-central1",
    repositoryId: "example-dockerhub-remote",
    description: "example remote dockerhub repository with credentials",
    format: "DOCKER",
    mode: "REMOTE_REPOSITORY",
    remoteRepositoryConfig: {
        description: "docker hub with custom credentials",
        disableUpstreamValidation: true,
        dockerRepository: {
            publicRepository: "DOCKER_HUB",
        },
        upstreamCredentials: {
            usernamePasswordCredentials: {
                username: "remote-username",
                passwordSecretVersion: example_remote_secretVersion.name,
            },
        },
    },
});
import pulumi
import pulumi_gcp as gcp

project = gcp.organizations.get_project()
example_remote_secret = gcp.secretmanager.Secret("example-remote-secret",
    secret_id="example-secret",
    replication={
        "auto": {},
    })
example_remote_secret_version = gcp.secretmanager.SecretVersion("example-remote-secret_version",
    secret=example_remote_secret.id,
    secret_data="remote-password")
secret_access = gcp.secretmanager.SecretIamMember("secret-access",
    secret_id=example_remote_secret.id,
    role="roles/secretmanager.secretAccessor",
    member=f"serviceAccount:service-{project.number}@gcp-sa-artifactregistry.iam.gserviceaccount.com")
my_repo = gcp.artifactregistry.Repository("my-repo",
    location="us-central1",
    repository_id="example-dockerhub-remote",
    description="example remote dockerhub repository with credentials",
    format="DOCKER",
    mode="REMOTE_REPOSITORY",
    remote_repository_config={
        "description": "docker hub with custom credentials",
        "disable_upstream_validation": True,
        "docker_repository": {
            "public_repository": "DOCKER_HUB",
        },
        "upstream_credentials": {
            "username_password_credentials": {
                "username": "remote-username",
                "password_secret_version": example_remote_secret_version.name,
            },
        },
    })
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/artifactregistry"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/organizations"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/secretmanager"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		project, err := organizations.LookupProject(ctx, &organizations.LookupProjectArgs{}, nil)
		if err != nil {
			return err
		}
		example_remote_secret, err := secretmanager.NewSecret(ctx, "example-remote-secret", &secretmanager.SecretArgs{
			SecretId: pulumi.String("example-secret"),
			Replication: &secretmanager.SecretReplicationArgs{
				Auto: &secretmanager.SecretReplicationAutoArgs{},
			},
		})
		if err != nil {
			return err
		}
		example_remote_secretVersion, err := secretmanager.NewSecretVersion(ctx, "example-remote-secret_version", &secretmanager.SecretVersionArgs{
			Secret:     example_remote_secret.ID(),
			SecretData: pulumi.String("remote-password"),
		})
		if err != nil {
			return err
		}
		_, err = secretmanager.NewSecretIamMember(ctx, "secret-access", &secretmanager.SecretIamMemberArgs{
			SecretId: example_remote_secret.ID(),
			Role:     pulumi.String("roles/secretmanager.secretAccessor"),
			Member:   pulumi.Sprintf("serviceAccount:service-%v@gcp-sa-artifactregistry.iam.gserviceaccount.com", project.Number),
		})
		if err != nil {
			return err
		}
		_, err = artifactregistry.NewRepository(ctx, "my-repo", &artifactregistry.RepositoryArgs{
			Location:     pulumi.String("us-central1"),
			RepositoryId: pulumi.String("example-dockerhub-remote"),
			Description:  pulumi.String("example remote dockerhub repository with credentials"),
			Format:       pulumi.String("DOCKER"),
			Mode:         pulumi.String("REMOTE_REPOSITORY"),
			RemoteRepositoryConfig: &artifactregistry.RepositoryRemoteRepositoryConfigArgs{
				Description:               pulumi.String("docker hub with custom credentials"),
				DisableUpstreamValidation: pulumi.Bool(true),
				DockerRepository: &artifactregistry.RepositoryRemoteRepositoryConfigDockerRepositoryArgs{
					PublicRepository: pulumi.String("DOCKER_HUB"),
				},
				UpstreamCredentials: &artifactregistry.RepositoryRemoteRepositoryConfigUpstreamCredentialsArgs{
					UsernamePasswordCredentials: &artifactregistry.RepositoryRemoteRepositoryConfigUpstreamCredentialsUsernamePasswordCredentialsArgs{
						Username:              pulumi.String("remote-username"),
						PasswordSecretVersion: example_remote_secretVersion.Name,
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var project = Gcp.Organizations.GetProject.Invoke();

    var example_remote_secret = new Gcp.SecretManager.Secret("example-remote-secret", new()
    {
        SecretId = "example-secret",
        Replication = new Gcp.SecretManager.Inputs.SecretReplicationArgs
        {
            Auto = null,
        },
    });

    var example_remote_secretVersion = new Gcp.SecretManager.SecretVersion("example-remote-secret_version", new()
    {
        Secret = example_remote_secret.Id,
        SecretData = "remote-password",
    });

    var secret_access = new Gcp.SecretManager.SecretIamMember("secret-access", new()
    {
        SecretId = example_remote_secret.Id,
        Role = "roles/secretmanager.secretAccessor",
        Member = $"serviceAccount:service-{project.Apply(getProjectResult => getProjectResult.Number)}@gcp-sa-artifactregistry.iam.gserviceaccount.com",
    });

    var my_repo = new Gcp.ArtifactRegistry.Repository("my-repo", new()
    {
        Location = "us-central1",
        RepositoryId = "example-dockerhub-remote",
        Description = "example remote dockerhub repository with credentials",
        Format = "DOCKER",
        Mode = "REMOTE_REPOSITORY",
        RemoteRepositoryConfig = new Gcp.ArtifactRegistry.Inputs.RepositoryRemoteRepositoryConfigArgs
        {
            Description = "docker hub with custom credentials",
            DisableUpstreamValidation = true,
            DockerRepository = new Gcp.ArtifactRegistry.Inputs.RepositoryRemoteRepositoryConfigDockerRepositoryArgs
            {
                PublicRepository = "DOCKER_HUB",
            },
            UpstreamCredentials = new Gcp.ArtifactRegistry.Inputs.RepositoryRemoteRepositoryConfigUpstreamCredentialsArgs
            {
                UsernamePasswordCredentials = new Gcp.ArtifactRegistry.Inputs.RepositoryRemoteRepositoryConfigUpstreamCredentialsUsernamePasswordCredentialsArgs
                {
                    Username = "remote-username",
                    PasswordSecretVersion = example_remote_secretVersion.Name,
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.organizations.OrganizationsFunctions;
import com.pulumi.gcp.organizations.inputs.GetProjectArgs;
import com.pulumi.gcp.secretmanager.Secret;
import com.pulumi.gcp.secretmanager.SecretArgs;
import com.pulumi.gcp.secretmanager.inputs.SecretReplicationArgs;
import com.pulumi.gcp.secretmanager.inputs.SecretReplicationAutoArgs;
import com.pulumi.gcp.secretmanager.SecretVersion;
import com.pulumi.gcp.secretmanager.SecretVersionArgs;
import com.pulumi.gcp.secretmanager.SecretIamMember;
import com.pulumi.gcp.secretmanager.SecretIamMemberArgs;
import com.pulumi.gcp.artifactregistry.Repository;
import com.pulumi.gcp.artifactregistry.RepositoryArgs;
import com.pulumi.gcp.artifactregistry.inputs.RepositoryRemoteRepositoryConfigArgs;
import com.pulumi.gcp.artifactregistry.inputs.RepositoryRemoteRepositoryConfigDockerRepositoryArgs;
import com.pulumi.gcp.artifactregistry.inputs.RepositoryRemoteRepositoryConfigUpstreamCredentialsArgs;
import com.pulumi.gcp.artifactregistry.inputs.RepositoryRemoteRepositoryConfigUpstreamCredentialsUsernamePasswordCredentialsArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        final var project = OrganizationsFunctions.getProject(GetProjectArgs.builder()
            .build());

        var example_remote_secret = new Secret("example-remote-secret", SecretArgs.builder()
            .secretId("example-secret")
            .replication(SecretReplicationArgs.builder()
                .auto(SecretReplicationAutoArgs.builder()
                    .build())
                .build())
            .build());

        var example_remote_secretVersion = new SecretVersion("example-remote-secretVersion", SecretVersionArgs.builder()
            .secret(example_remote_secret.id())
            .secretData("remote-password")
            .build());

        var secret_access = new SecretIamMember("secret-access", SecretIamMemberArgs.builder()
            .secretId(example_remote_secret.id())
            .role("roles/secretmanager.secretAccessor")
            .member(String.format("serviceAccount:service-%s@gcp-sa-artifactregistry.iam.gserviceaccount.com", project.number()))
            .build());

        var my_repo = new Repository("my-repo", RepositoryArgs.builder()
            .location("us-central1")
            .repositoryId("example-dockerhub-remote")
            .description("example remote dockerhub repository with credentials")
            .format("DOCKER")
            .mode("REMOTE_REPOSITORY")
            .remoteRepositoryConfig(RepositoryRemoteRepositoryConfigArgs.builder()
                .description("docker hub with custom credentials")
                .disableUpstreamValidation(true)
                .dockerRepository(RepositoryRemoteRepositoryConfigDockerRepositoryArgs.builder()
                    .publicRepository("DOCKER_HUB")
                    .build())
                .upstreamCredentials(RepositoryRemoteRepositoryConfigUpstreamCredentialsArgs.builder()
                    .usernamePasswordCredentials(RepositoryRemoteRepositoryConfigUpstreamCredentialsUsernamePasswordCredentialsArgs.builder()
                        .username("remote-username")
                        .passwordSecretVersion(example_remote_secretVersion.name())
                        .build())
                    .build())
                .build())
            .build());

    }
}
resources:
  example-remote-secret:
    type: gcp:secretmanager:Secret
    properties:
      secretId: example-secret
      replication:
        auto: {}
  example-remote-secretVersion:
    type: gcp:secretmanager:SecretVersion
    name: example-remote-secret_version
    properties:
      secret: ${["example-remote-secret"].id}
      secretData: remote-password
  secret-access:
    type: gcp:secretmanager:SecretIamMember
    properties:
      secretId: ${["example-remote-secret"].id}
      role: roles/secretmanager.secretAccessor
      member: serviceAccount:service-${project.number}@gcp-sa-artifactregistry.iam.gserviceaccount.com
  my-repo:
    type: gcp:artifactregistry:Repository
    properties:
      location: us-central1
      repositoryId: example-dockerhub-remote
      description: example remote dockerhub repository with credentials
      format: DOCKER
      mode: REMOTE_REPOSITORY
      remoteRepositoryConfig:
        description: docker hub with custom credentials
        disableUpstreamValidation: true
        dockerRepository:
          publicRepository: DOCKER_HUB
        upstreamCredentials:
          usernamePasswordCredentials:
            username: remote-username
            passwordSecretVersion: ${["example-remote-secretVersion"].name}
variables:
  project:
    fn::invoke:
      function: gcp:organizations:getProject
      arguments: {}

The upstreamCredentials block references a Secret Manager secret containing the password. The Artifact Registry service account needs secretAccessor permissions on the secret. Setting disableUpstreamValidation to true skips validation during repository creation, useful when credentials aren’t immediately available.

Beyond these examples

These snippets focus on specific repository-level features: standard, virtual, and remote repository modes, multi-region replication and customer-managed encryption, cleanup policies and Docker-specific configuration, and upstream authentication with Secret Manager. They’re intentionally minimal rather than full artifact management solutions.

The examples may reference pre-existing infrastructure such as Cloud KMS keys for encryption, Secret Manager secrets for upstream credentials, and IAM permissions for service accounts. They focus on configuring the repository rather than provisioning everything around it.

To keep things focused, common repository patterns are omitted, including:

  • Maven, NPM, Python, APT, and YUM format-specific configuration
  • Vulnerability scanning configuration (vulnerabilityScanningConfig)
  • Common repository configuration for pull-through caching
  • IAM policies and access control (separate from repository resource)

These omissions are intentional: the goal is to illustrate how each repository feature is wired, not provide drop-in artifact management modules. See the Artifact Registry Repository resource reference for all available configuration options.

Let's create GCP Artifact Registry Repositories

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Repository Configuration & Immutability
What repository properties can't I change after creation?
Several properties are immutable: format, location, repositoryId, project, mode, kmsKeyName, and remoteRepositoryConfig. Changing any of these requires destroying and recreating the repository.
What repository formats are supported?
Artifact Registry supports multiple formats including DOCKER, MAVEN, NPM, PYTHON, APT, and YUM. Alpha formats require membership in the alpha user group.
How do I create a multi-region repository?
Set location to asia, europe, or us instead of a specific region like us-central1.
Encryption & IAM Requirements
How do I enable customer-managed encryption with Cloud KMS?
Set kmsKeyName to your KMS key resource name. You must first grant the Artifact Registry service account (service-{project-number}@gcp-sa-artifactregistry.iam.gserviceaccount.com) the roles/cloudkms.cryptoKeyEncrypterDecrypter role on the key, and use dependsOn to ensure proper ordering.
Why do I need IAM bindings for remote repository authentication?
When using upstreamCredentials with passwordSecretVersion, the Artifact Registry service account (service-{project-number}@gcp-sa-artifactregistry.iam.gserviceaccount.com) needs the roles/secretmanager.secretAccessor role on the secret to retrieve credentials.
Repository Modes & Types
What's the difference between standard, virtual, and remote repositories?
STANDARD_REPOSITORY (default) stores artifacts directly. VIRTUAL_REPOSITORY aggregates multiple upstream repositories. REMOTE_REPOSITORY proxies artifacts from external sources like Docker Hub or custom registries.
How do I configure a virtual repository with multiple upstream sources?
Set mode to VIRTUAL_REPOSITORY and configure virtualRepositoryConfig.upstreamPolicies with an array of upstream repositories. Each policy includes an id, repository reference, and priority (lower numbers are checked first).
How do I set up a remote repository to pull from Docker Hub?
Set mode to REMOTE_REPOSITORY, format to DOCKER, and configure remoteRepositoryConfig.dockerRepository.publicRepository to DOCKER_HUB.
How do I authenticate to a custom remote registry?
Configure remoteRepositoryConfig.upstreamCredentials.usernamePasswordCredentials with a username and passwordSecretVersion pointing to a Secret Manager secret. Grant the Artifact Registry service account access to the secret.
How do I create a pull-through cache of another Artifact Registry repository?
Set mode to REMOTE_REPOSITORY and configure remoteRepositoryConfig.commonRepository.uri with either the upstream repository ID or its full URL (e.g., https://us-central1-docker.pkg.dev/{project}/{repo}).
Cleanup & Lifecycle Management
How do I automatically clean up old or untagged images?
Configure cleanupPolicies with conditions like tagState (TAGGED/UNTAGGED), newerThan/olderThan (e.g., “7d”, “30d”), tagPrefixes, and packageNamePrefixes. Set action to DELETE or KEEP. Use cleanupPolicyDryRun: true to test without deleting.
How do cleanup policy priorities work when I have multiple policies?
Cleanup policies are evaluated in order. KEEP actions take precedence over DELETE actions. You can use mostRecentVersions.keepCount to preserve a minimum number of versions regardless of other conditions.
Docker-Specific Configuration
How do I prevent Docker tags from being overwritten?
Set dockerConfig.immutableTags to true. This prevents existing tags from being pushed with different image digests.
Why should I use effectiveLabels instead of labels?
The labels field is non-authoritative and only manages labels in your configuration. Use the effectiveLabels output property to see all labels on the resource, including those added by other clients or services.

Using a different cloud?

Explore containers guides for other cloud providers: