How to Setup Custom GCP Storage as a Partner
Granting Access for SpinAI Service Account
Create the Bucket
Before granting access, please make sure the target bucket is configured according to the following requirements:
- Location: we strongly recommend creating the bucket in the
us-central1region or as a Multi-region bucket inUS. - Storage class: managed by our platform. No action is required on the client side.
- Versioning: managed by our platform. Do not enable Object Versioning manually.
- Object Lifecycle: managed by our platform. Do not configure lifecycle rules manually.
- Soft delete policy: must be enabled on the bucket with a retention duration of 30 days.
If a bucket has already been created, make sure it meets the requirements and then you can skip the step of creating a bucket and go to step 2 (Grant Access to the Service Account).
If the bucket does not yet exist, it can be created in one of three ways. In all examples below, replace
BUCKET_NAMEwith the actual bucket name you want to use.Option A — GCP Console
Steps:
- Open the Cloud Console (https://console.cloud.google.com/) and make sure the correct project is selected.
- Navigate to Cloud Storage → Buckets and click Create.
- Name your bucket: enter
BUCKET_NAME. - Choose where to store your data:
- Location type: Region — select
us-central1 - or
- Location type: Multi-region — select
us(United States)
- Location type: Region — select
- Choose a default storage class: leave the default (Standard). It will be managed by our platform.
- Choose how to control access: leave default settings.
- Choose how to protect object data:
- Soft delete policy: set retention duration to 30 days.
- Object Versioning: leave disabled.
- Object lifecycle: do not configure any rules.
- Click Create.
Option B — gcloud CLI
Authenticate as a user with sufficient permissions and select the target project:
gcloud auth login gcloud config set project CLIENT_PROJECT_IDCreate the bucket (regional,
us-central1):gcloud storage buckets create gs://BUCKET_NAME \ --location=us-central1 \ --default-storage-class=STANDARD \ --uniform-bucket-level-access \ --soft-delete-duration=30dOr, to create a multi-region bucket in
US:gcloud storage buckets create gs://BUCKET_NAME \ --location=us \ --default-storage-class=STANDARD \ --uniform-bucket-level-access \ --soft-delete-duration=30dReplace
BUCKET_NAMEwith the actual bucket name.Note: do not pass
--enable-versioningand do not configure--lifecycle-file. These aspects are managed by our platform.Option C — Terraform
Add the following configuration. The bucket name is parameterized via a variable so it can be passed in at apply time.
terraform { required_version = ">= 1.3" required_providers { google = { source = "hashicorp/google" version = "~> 5.0" } } } variable "project_id" { type = string description = "GCP project ID where resources will be created" } variable "bucket_name" { type = string description = "Name of the GCS bucket to create" } variable "bucket_location" { type = string description = "Bucket location: 'us-central1' for a regional bucket or 'US' for a multi-region bucket" default = "us-central1" } provider "google" { project = var.project_id region = var.bucket_location } resource "google_storage_bucket" "main" { name = var.bucket_name location = var.bucket_location storage_class = "STANDARD" uniform_bucket_level_access = true soft_delete_policy { retention_duration_seconds = 2592000 # 30 days } # Versioning and lifecycle are managed by the platform — do not configure here. lifecycle { prevent_destroy = true } }Apply the configuration:
terraform plan \ -var="project_id="gcp-project" -var="bucket_name=BUCKET_NAME" terraform apply -var="project_id="gcp-project" -var="bucket_name=BUCKET_NAME"The
terraform.tfstatefile is critically important – be sure to back it up in a safe place.Notes on the configuration:
- The
lifecycle { prevent_destroy = true }block protects the bucket from accidental destruction. If a future plan would destroy or recreate this resource (for example, due to a state mismatch or a change to an immutable attribute such as name or location), Terraform will refuse to apply and exit with an error. Removing or destroying the bucket would require explicitly removing this protection first. storage_classis set toSTANDARDat creation time; ongoing storage class management is handled by Spin platform.- No
versioningblock and nolifecycle_ruleblocks are defined — these are managed by our platform.
- Location: we strongly recommend creating the bucket in the
Grant Access to the Service Account
You must grant permissions to SpinAI’s service account on the target bucket. The role “Storage Object Admin” allows the SpinAI service account to read, write, and delete objects in the bucket, without granting any control over the bucket itself (its settings, IAM policy, or lifecycle).
This can be done in one of three ways: via the GCP Console, gcloud, or Terraform.
Who should perform this: a user in the client’s project who holds the “Storage Admin” or “Owner” role on the project (or on the specific bucket). The “Editor” role is not sufficient, as it does not include permission to modify bucket IAM policies.
Option A — GCP Console
Steps:
- Open the Cloud Console (https://console.cloud.google.com/) and make sure the correct project is selected.
- Navigate to Cloud Storage → Buckets and select the desired bucket.
- Go to the Permissions tab and click Grant Access.
- In the dialog that appears:
- New principals: add the service account
custom-storage-sa@spinbackup-for-work.iam.gserviceaccount.com - Select a role: choose Cloud Storage → Storage Object Admin
- New principals: add the service account
- Click Save.
Option B — gcloud CLI
Grant the role on the bucket:
gcloud storage buckets add-iam-policy-binding gs://BUCKET_NAME \ --member="serviceAccount:gcp-us-b2b-custom-sa-stage@sb-for-work-stage.iam.gserviceaccount.com" \ --role="roles/storage.objectAdmin"Replace
BUCKET_NAMEwith the name of the target bucket.Option C — Terraform
Add the following resource to your Terraform configuration (reusing the
bucket_namevariable defined above):terraform { required_version = ">= 1.0" required_providers { google = { source = "hashicorp/google" version = "~> 5.0" } } } provider "google" { project = var.project_id region = var.region } variable "project_id" { description = "Project ID GCP" type = string } variable "region" { description = "Region GCP" type = string default = "us-central1" } variable "bucket_name" { description = "The name of the GCS bucket for which permissions are being granted" type = string } resource "google_storage_bucket_iam_member" "external_sa_object_admin" { bucket = var.bucket_name role = "roles/storage.objectAdmin" member = "serviceAccount:custom-storage-sa@spinbackup-for-work.iam.gserviceaccount.com" }Apply the change:
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/sa-key.json" terraform init terraform plan -var="project_id=company-project" -var="bucket_name=company-bucket" terraform apply -var="project_id=company-project" -var="bucket_name=company-bucket"The
google_storage_bucket_iam_memberresource adds this single binding without affecting any other permissions already configured on the bucket.The
terraform.tfstatefile is critically important – be sure to back it up in a safe place.Confirm Access Has Been Granted
Once the bucket has been created and the role has been assigned, please notify us so we can verify access from our side.