The gcp:secretmanager/secret:Secret resource, part of the Pulumi GCP provider, defines a Secret Manager secret container: its replication strategy, metadata, and lifecycle policies. This guide focuses on four capabilities: regional replication control, client tool metadata with annotations, version destruction delays, and customer-managed encryption keys.
Secrets are containers for versioned secret data; the actual secret values are stored separately via SecretVersion resources. The examples are intentionally small. Combine them with your own SecretVersion resources and access policies.
Replicate secrets across specific regions
Applications running in multiple regions need secrets available in each location to minimize latency and maintain availability during regional outages.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const secret_basic = new gcp.secretmanager.Secret("secret-basic", {
secretId: "secret",
labels: {
label: "my-label",
},
replication: {
userManaged: {
replicas: [
{
location: "us-central1",
},
{
location: "us-east1",
},
],
},
},
deletionProtection: false,
});
import pulumi
import pulumi_gcp as gcp
secret_basic = gcp.secretmanager.Secret("secret-basic",
secret_id="secret",
labels={
"label": "my-label",
},
replication={
"user_managed": {
"replicas": [
{
"location": "us-central1",
},
{
"location": "us-east1",
},
],
},
},
deletion_protection=False)
package main
import (
"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 {
_, err := secretmanager.NewSecret(ctx, "secret-basic", &secretmanager.SecretArgs{
SecretId: pulumi.String("secret"),
Labels: pulumi.StringMap{
"label": pulumi.String("my-label"),
},
Replication: &secretmanager.SecretReplicationArgs{
UserManaged: &secretmanager.SecretReplicationUserManagedArgs{
Replicas: secretmanager.SecretReplicationUserManagedReplicaArray{
&secretmanager.SecretReplicationUserManagedReplicaArgs{
Location: pulumi.String("us-central1"),
},
&secretmanager.SecretReplicationUserManagedReplicaArgs{
Location: pulumi.String("us-east1"),
},
},
},
},
DeletionProtection: pulumi.Bool(false),
})
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 secret_basic = new Gcp.SecretManager.Secret("secret-basic", new()
{
SecretId = "secret",
Labels =
{
{ "label", "my-label" },
},
Replication = new Gcp.SecretManager.Inputs.SecretReplicationArgs
{
UserManaged = new Gcp.SecretManager.Inputs.SecretReplicationUserManagedArgs
{
Replicas = new[]
{
new Gcp.SecretManager.Inputs.SecretReplicationUserManagedReplicaArgs
{
Location = "us-central1",
},
new Gcp.SecretManager.Inputs.SecretReplicationUserManagedReplicaArgs
{
Location = "us-east1",
},
},
},
},
DeletionProtection = false,
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
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.SecretReplicationUserManagedArgs;
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 secret_basic = new Secret("secret-basic", SecretArgs.builder()
.secretId("secret")
.labels(Map.of("label", "my-label"))
.replication(SecretReplicationArgs.builder()
.userManaged(SecretReplicationUserManagedArgs.builder()
.replicas(
SecretReplicationUserManagedReplicaArgs.builder()
.location("us-central1")
.build(),
SecretReplicationUserManagedReplicaArgs.builder()
.location("us-east1")
.build())
.build())
.build())
.deletionProtection(false)
.build());
}
}
resources:
secret-basic:
type: gcp:secretmanager:Secret
properties:
secretId: secret
labels:
label: my-label
replication:
userManaged:
replicas:
- location: us-central1
- location: us-east1
deletionProtection: false
The replication property controls where secret data is stored. With userManaged replication, you specify exact locations via the replicas array. Each replica object requires a location property naming a GCP region. This gives you precise control over data residency and availability zones.
Add client tool metadata with annotations
Client tools and automation systems often need to store their own metadata alongside secrets without requiring a separate database.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const secret_with_annotations = new gcp.secretmanager.Secret("secret-with-annotations", {
secretId: "secret",
labels: {
label: "my-label",
},
annotations: {
key1: "someval",
key2: "someval2",
key3: "someval3",
key4: "someval4",
key5: "someval5",
},
replication: {
auto: {},
},
});
import pulumi
import pulumi_gcp as gcp
secret_with_annotations = gcp.secretmanager.Secret("secret-with-annotations",
secret_id="secret",
labels={
"label": "my-label",
},
annotations={
"key1": "someval",
"key2": "someval2",
"key3": "someval3",
"key4": "someval4",
"key5": "someval5",
},
replication={
"auto": {},
})
package main
import (
"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 {
_, err := secretmanager.NewSecret(ctx, "secret-with-annotations", &secretmanager.SecretArgs{
SecretId: pulumi.String("secret"),
Labels: pulumi.StringMap{
"label": pulumi.String("my-label"),
},
Annotations: pulumi.StringMap{
"key1": pulumi.String("someval"),
"key2": pulumi.String("someval2"),
"key3": pulumi.String("someval3"),
"key4": pulumi.String("someval4"),
"key5": pulumi.String("someval5"),
},
Replication: &secretmanager.SecretReplicationArgs{
Auto: &secretmanager.SecretReplicationAutoArgs{},
},
})
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 secret_with_annotations = new Gcp.SecretManager.Secret("secret-with-annotations", new()
{
SecretId = "secret",
Labels =
{
{ "label", "my-label" },
},
Annotations =
{
{ "key1", "someval" },
{ "key2", "someval2" },
{ "key3", "someval3" },
{ "key4", "someval4" },
{ "key5", "someval5" },
},
Replication = new Gcp.SecretManager.Inputs.SecretReplicationArgs
{
Auto = null,
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
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 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 secret_with_annotations = new Secret("secret-with-annotations", SecretArgs.builder()
.secretId("secret")
.labels(Map.of("label", "my-label"))
.annotations(Map.ofEntries(
Map.entry("key1", "someval"),
Map.entry("key2", "someval2"),
Map.entry("key3", "someval3"),
Map.entry("key4", "someval4"),
Map.entry("key5", "someval5")
))
.replication(SecretReplicationArgs.builder()
.auto(SecretReplicationAutoArgs.builder()
.build())
.build())
.build());
}
}
resources:
secret-with-annotations:
type: gcp:secretmanager:Secret
properties:
secretId: secret
labels:
label: my-label
annotations:
key1: someval
key2: someval2
key3: someval3
key4: someval4
key5: someval5
replication:
auto: {}
Annotations store tool-specific metadata that doesn’t affect secret behavior. Unlike labels, annotations aren’t used for filtering or organization; they’re purely for client tool state. The replication property here uses auto mode, letting Google manage replica placement automatically.
Delay version destruction with a grace period
Production systems need time to roll back changes when secret rotations cause issues.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const secret_with_version_destroy_ttl = new gcp.secretmanager.Secret("secret-with-version-destroy-ttl", {
secretId: "secret",
versionDestroyTtl: "2592000s",
replication: {
auto: {},
},
});
import pulumi
import pulumi_gcp as gcp
secret_with_version_destroy_ttl = gcp.secretmanager.Secret("secret-with-version-destroy-ttl",
secret_id="secret",
version_destroy_ttl="2592000s",
replication={
"auto": {},
})
package main
import (
"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 {
_, err := secretmanager.NewSecret(ctx, "secret-with-version-destroy-ttl", &secretmanager.SecretArgs{
SecretId: pulumi.String("secret"),
VersionDestroyTtl: pulumi.String("2592000s"),
Replication: &secretmanager.SecretReplicationArgs{
Auto: &secretmanager.SecretReplicationAutoArgs{},
},
})
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 secret_with_version_destroy_ttl = new Gcp.SecretManager.Secret("secret-with-version-destroy-ttl", new()
{
SecretId = "secret",
VersionDestroyTtl = "2592000s",
Replication = new Gcp.SecretManager.Inputs.SecretReplicationArgs
{
Auto = null,
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
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 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 secret_with_version_destroy_ttl = new Secret("secret-with-version-destroy-ttl", SecretArgs.builder()
.secretId("secret")
.versionDestroyTtl("2592000s")
.replication(SecretReplicationArgs.builder()
.auto(SecretReplicationAutoArgs.builder()
.build())
.build())
.build());
}
}
resources:
secret-with-version-destroy-ttl:
type: gcp:secretmanager:Secret
properties:
secretId: secret
versionDestroyTtl: 2592000s
replication:
auto: {}
The versionDestroyTtl property sets a grace period before version destruction. When you delete a secret version, it enters a disabled state rather than being destroyed immediately. The actual destruction happens after the TTL expires (30 days in this example), giving you time to recover from rotation issues.
Encrypt secrets with customer-managed keys
Organizations with strict compliance requirements need to control the encryption keys used to protect secret data.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const project = gcp.organizations.getProject({});
const kms_secret_binding = new gcp.kms.CryptoKeyIAMMember("kms-secret-binding", {
cryptoKeyId: "kms-key",
role: "roles/cloudkms.cryptoKeyEncrypterDecrypter",
member: project.then(project => `serviceAccount:service-${project.number}@gcp-sa-secretmanager.iam.gserviceaccount.com`),
});
const secret_with_automatic_cmek = new gcp.secretmanager.Secret("secret-with-automatic-cmek", {
secretId: "secret",
replication: {
auto: {
customerManagedEncryption: {
kmsKeyName: "kms-key",
},
},
},
}, {
dependsOn: [kms_secret_binding],
});
import pulumi
import pulumi_gcp as gcp
project = gcp.organizations.get_project()
kms_secret_binding = gcp.kms.CryptoKeyIAMMember("kms-secret-binding",
crypto_key_id="kms-key",
role="roles/cloudkms.cryptoKeyEncrypterDecrypter",
member=f"serviceAccount:service-{project.number}@gcp-sa-secretmanager.iam.gserviceaccount.com")
secret_with_automatic_cmek = gcp.secretmanager.Secret("secret-with-automatic-cmek",
secret_id="secret",
replication={
"auto": {
"customer_managed_encryption": {
"kms_key_name": "kms-key",
},
},
},
opts = pulumi.ResourceOptions(depends_on=[kms_secret_binding]))
package main
import (
"fmt"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/kms"
"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
}
kms_secret_binding, err := kms.NewCryptoKeyIAMMember(ctx, "kms-secret-binding", &kms.CryptoKeyIAMMemberArgs{
CryptoKeyId: pulumi.String("kms-key"),
Role: pulumi.String("roles/cloudkms.cryptoKeyEncrypterDecrypter"),
Member: pulumi.Sprintf("serviceAccount:service-%v@gcp-sa-secretmanager.iam.gserviceaccount.com", project.Number),
})
if err != nil {
return err
}
_, err = secretmanager.NewSecret(ctx, "secret-with-automatic-cmek", &secretmanager.SecretArgs{
SecretId: pulumi.String("secret"),
Replication: &secretmanager.SecretReplicationArgs{
Auto: &secretmanager.SecretReplicationAutoArgs{
CustomerManagedEncryption: &secretmanager.SecretReplicationAutoCustomerManagedEncryptionArgs{
KmsKeyName: pulumi.String("kms-key"),
},
},
},
}, pulumi.DependsOn([]pulumi.Resource{
kms_secret_binding,
}))
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 kms_secret_binding = new Gcp.Kms.CryptoKeyIAMMember("kms-secret-binding", new()
{
CryptoKeyId = "kms-key",
Role = "roles/cloudkms.cryptoKeyEncrypterDecrypter",
Member = $"serviceAccount:service-{project.Apply(getProjectResult => getProjectResult.Number)}@gcp-sa-secretmanager.iam.gserviceaccount.com",
});
var secret_with_automatic_cmek = new Gcp.SecretManager.Secret("secret-with-automatic-cmek", new()
{
SecretId = "secret",
Replication = new Gcp.SecretManager.Inputs.SecretReplicationArgs
{
Auto = new Gcp.SecretManager.Inputs.SecretReplicationAutoArgs
{
CustomerManagedEncryption = new Gcp.SecretManager.Inputs.SecretReplicationAutoCustomerManagedEncryptionArgs
{
KmsKeyName = "kms-key",
},
},
},
}, new CustomResourceOptions
{
DependsOn =
{
kms_secret_binding,
},
});
});
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.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.inputs.SecretReplicationAutoCustomerManagedEncryptionArgs;
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 kms_secret_binding = new CryptoKeyIAMMember("kms-secret-binding", CryptoKeyIAMMemberArgs.builder()
.cryptoKeyId("kms-key")
.role("roles/cloudkms.cryptoKeyEncrypterDecrypter")
.member(String.format("serviceAccount:service-%s@gcp-sa-secretmanager.iam.gserviceaccount.com", project.number()))
.build());
var secret_with_automatic_cmek = new Secret("secret-with-automatic-cmek", SecretArgs.builder()
.secretId("secret")
.replication(SecretReplicationArgs.builder()
.auto(SecretReplicationAutoArgs.builder()
.customerManagedEncryption(SecretReplicationAutoCustomerManagedEncryptionArgs.builder()
.kmsKeyName("kms-key")
.build())
.build())
.build())
.build(), CustomResourceOptions.builder()
.dependsOn(kms_secret_binding)
.build());
}
}
resources:
kms-secret-binding:
type: gcp:kms:CryptoKeyIAMMember
properties:
cryptoKeyId: kms-key
role: roles/cloudkms.cryptoKeyEncrypterDecrypter
member: serviceAccount:service-${project.number}@gcp-sa-secretmanager.iam.gserviceaccount.com
secret-with-automatic-cmek:
type: gcp:secretmanager:Secret
properties:
secretId: secret
replication:
auto:
customerManagedEncryption:
kmsKeyName: kms-key
options:
dependsOn:
- ${["kms-secret-binding"]}
variables:
project:
fn::invoke:
function: gcp:organizations:getProject
arguments: {}
Customer-managed encryption (CMEK) uses your own KMS keys instead of Google-managed keys. The customerManagedEncryption block within auto replication specifies the kmsKeyName. The CryptoKeyIAMMember resource grants the Secret Manager service account permission to use your key; this binding must exist before creating the secret, enforced via dependsOn.
Beyond these examples
These snippets focus on specific secret-level features: replication strategies, metadata and annotations, and version lifecycle and CMEK encryption. They’re intentionally minimal rather than full secrets management solutions.
The examples may reference pre-existing infrastructure such as GCP project with Secret Manager API enabled, and KMS keys and IAM bindings for CMEK examples. They focus on configuring the secret container rather than provisioning everything around it.
To keep things focused, common secret patterns are omitted, including:
- Secret rotation configuration (rotation property)
- Pub/Sub topic integration for notifications (topics property)
- Expiration policies (ttl and expireTime)
- Version aliases for stable references (versionAliases)
These omissions are intentional: the goal is to illustrate how each secret feature is wired, not provide drop-in secrets management modules. See the Secret Manager Secret resource reference for all available configuration options.
Let's create GCP Secret Manager Secrets
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Configuration & Immutability
project, replication, secretId, and tags. You’ll need to recreate the secret to change any of these.Expiration & Lifecycle
expireTime sets an absolute expiration timestamp (RFC3339 format), while ttl sets a relative duration (e.g., “3.5s”). You can only use one, not both.versionDestroyTtl is set, calling destroy doesn’t immediately delete the version. Instead, it goes to a disabled state, and actual destruction happens after the TTL expires.Replication & Encryption
You have two options:
- Automatic replication - Use
replication.autofor Google-managed replication - User-managed replication - Use
replication.userManaged.replicasto specify exact locations
replication.auto.customerManagedEncryption.kmsKeyName with your KMS key. You must also grant the Secret Manager service account (service-PROJECT_NUMBER@gcp-sa-secretmanager.iam.gserviceaccount.com) the roles/cloudkms.cryptoKeyEncrypterDecrypter role on the key.Labels & Annotations
labels is non-authoritative and only manages labels in your Pulumi configuration. effectiveLabels shows all labels on the resource, including those set by other clients and services.annotations is non-authoritative and only manages annotations in your configuration. effectiveAnnotations shows all annotations on the resource, including those set by other tools.[\p{Ll}\p{Lo}][\p{Ll}\p{Lo}\p{N}_-]{0,62}. Label values must be 0-63 characters matching [\p{Ll}\p{Lo}\p{N}_-]{0,63}. Maximum 64 labels per secret, with 128-byte UTF-8 encoding limit.Rotation & Notifications
rotation property with nextRotationTime and rotationPeriod. You must also set the topics property with up to 10 Pub/Sub topics to receive rotation notifications.topics property must be set to configure rotation. Secret Manager sends Pub/Sub notifications to these topics at rotation time.