Managing GCP Cloud Storage with Pulumi

Learn how to provision a Cloud Storage bucket with versioning, enforced public access prevention, and lifecycle rules using Pulumi and TypeScript

Managing GCP Cloud Storage with Pulumi

This guide provisions a Cloud Storage bucket with Pulumi - uniform bucket-level access, enforced public access prevention, versioning, and lifecycle rules that move old objects to cheaper storage before deleting them entirely.

The Bucket

// index.ts
import * as gcp from "@pulumi/gcp";

const bucket = new gcp.storage.Bucket("app-data", {
    name: "my-app-data-bucket-001",
    location: "US",
    forceDestroy: false,
    uniformBucketLevelAccess: true,
    publicAccessPrevention: "enforced",
    versioning: {
        enabled: true,
    },
    lifecycleRules: [{
        condition: { age: 30 },
        action: { type: "SetStorageClass", storageClass: "NEARLINE" },
    }, {
        condition: { age: 365 },
        action: { type: "Delete" },
    }],
});

export const bucketName = bucket.name;
export const bucketUrl = bucket.url;

uniformBucketLevelAccess: true disables per-object ACLs entirely in favor of IAM-only access control - Google’s current recommended default, and a prerequisite for publicAccessPrevention to have any effect (ACL-based access can otherwise make individual objects public regardless of IAM policy). publicAccessPrevention: "enforced" is the hard override: nothing in the bucket can ever be made public, full stop, the direct equivalent of the four blockPublic*/ignorePublicAcls/restrictPublicBuckets flags on aws.s3.BucketPublicAccessBlock in the S3 guide - GCP just expresses the same guarantee as a single setting.

location: "US" is a multi-region location (not a specific region like us-central1) - Cloud Storage buckets can be regional, dual-region, or multi-region, trading availability for latency and cost; a multi-region bucket like this one survives the loss of an entire region.

Lifecycle Rules

The two rules above run independently and are evaluated in order against every object’s age: at 30 days an object transitions to Nearline storage (cheaper, with a minimum-30-day retention charge and a per-retrieval fee), and at 365 days it’s deleted outright - the same “cool down, then expire” pattern as the S3 guide’s lifecycle configuration and the Azure Storage guide’s management policy, expressed with Cloud Storage’s own condition/action shape.

Best Practices

  1. uniformBucketLevelAccess: true and publicAccessPrevention: "enforced" by default on every bucket, the same reasoning as blocking public access by default on S3 and Azure Storage - opt out per-bucket only with a specific, reviewed reason.
  2. Enable versioning before anything writes to the bucket, not after - an object overwritten while versioning was off has no prior version to recover.
  3. Pair versioning with a lifecycle rule targeting noncurrent versions, not just the rules shown here (which apply to age regardless of version state) - otherwise every historical version of every object accumulates storage cost indefinitely.

Conclusion

Compare the mechanism here to S3 and Azure Storage: all three clouds converge on the same shape (a bucket/account resource, a public-access lockdown, versioning, and age-based lifecycle rules) even though the exact property names and defaults differ enough that copying one cloud’s checklist onto another isn’t safe without checking.

For more Pulumi topics, check out: