GOOGLE CLOUD STORAGE → STACKIT OBJECT STORAGE · TUTORIAL
Google Cloud Storage to STACKIT Object Storage
A Cloud Storage bucket becomes a STACKIT Object Storage bucket, and the run writes that bucket with its name and nothing else. Versioning, lifecycle rules, CORS, the website setting, public access and locks are set through the S3 API or the project, and the run lists each one it leaves. This tutorial follows a demo estate of three buckets from input to a to-do list, then shows the calls that carry the settings over and the limits to check first.
Three buckets in, three buckets out
A demo estate written for this page and assessed by StackToIT on 4 November 2026 with the google provider pinned to 8.4.0: an asset bucket with versioning, lifecycle rules and CORS, a bucket that serves a website to everyone, and an audit bucket with a locked retention policy. Left: the asset bucket and the audit bucket from the input. Right: the three bucket blocks the engine wrote, in file order; the review comments above each block are left out here. The input blocks are not adjacent in main.tf, so each has its own pane, verbatim.
resource "google_storage_bucket" "media" {
name = "media-prod-assets"
location = "EUROPE-WEST3"
storage_class = "STANDARD"
uniform_bucket_level_access = true
public_access_prevention = "enforced"
versioning {
enabled = true
}
lifecycle_rule {
condition {
age = 30
with_state = "ARCHIVED"
num_newer_versions = 3
}
action {
type = "Delete"
}
}
lifecycle_rule {
condition {
age = 365
}
action {
type = "SetStorageClass"
storage_class = "COLDLINE"
}
}
cors {
origin = ["https://www.example.com"]
method = ["GET", "HEAD"]
response_header = ["Content-Type"]
max_age_seconds = 3600
}
}resource "google_storage_bucket" "audit" {
name = "media-prod-audit"
location = "EUROPE-WEST3"
uniform_bucket_level_access = true
retention_policy {
retention_period = 31536000
is_locked = true
}
labels = {
purpose = "audit"
}
}resource "stackit_objectstorage_bucket" "audit" {
project_id = var.project_id
name = "media-prod-audit"
}
resource "stackit_objectstorage_bucket" "media" {
project_id = var.project_id
name = "media-prod-assets"
}
resource "stackit_objectstorage_bucket" "site" {
project_id = var.project_id
name = "media-prod-site"
}The bucket resource is a name
The run states that stackit_objectstorage_bucket has a name and an object lock setting only. Everything else on the Google bucket, from storage class to retention, is either set through the S3 API or not carried.
One unit per bucket, one class
Each bucket is its own unit, classified RECONFIGURE at medium confidence. The service account of the demo, which uploads to the asset bucket, is a separate unit and becomes a STACKIT service account.
Bucket names are not yours alone
Bucket names are 3 to 63 characters long. On uniqueness the STACKIT pages differ: the limits page says a name must be unique within the whole system, across projects, while the concepts page says unique within a region, with EU01 and EU02 independent. Either way a name that is free on Google Cloud can be taken here, so reserve it early; the run asks you to confirm the name against the applications that use it.
What the draft does not carry
Verbatim lines of the comments above the asset bucket and the website bucket in terraform/storage.tf, then the two findings for the versioned bucket and the locked bucket as assessment.json holds them, with their evidence lists cut. The last pane shows the console lines of the run.
# NOT CARRIED (google_storage_bucket.media): lifecycle_rule — stackit_objectstorage_bucket has a name and object_lock only (pinned provider): lifecycle rules (expiry, storage-class transitions) are not generated.
# NOT CARRIED (google_storage_bucket.media): cors — stackit_objectstorage_bucket has no CORS attribute (pinned provider): the CORS configuration is not generated.
# NOT CARRIED (google_storage_bucket.media): storage_class = STANDARD — stackit_objectstorage_bucket has a name and object_lock only (pinned provider): the bucket's storage class, Autoclass, hierarchical-namespace and requester-pays settings are not generated.# NOT CARRIED (google_storage_bucket.site): website — stackit_objectstorage_bucket has no static-website setting (pinned provider): the index and error pages are not generated — serve the site through the STACKIT CDN or a web server.{"check_id":"gcs-versioning","severity":"warning","message":"Object versioning is enabled. STACKIT Object Storage supports versioning through the S3 API (put-bucket-versioning; once enabled it can only be suspended), but stackit_objectstorage_bucket has no versioning attribute — enable it after the bucket exists."
{"check_id":"gcs-retention-lock","severity":"info","message":"A retention policy (WORM) is set; STACKIT Object Storage supports object lock — check the retention period against its limits."Source 6 resources · 4 units · visibility: Complete
Migration 4 drafted (of 4 units the draft considered)Lifecycle, CORS and storage class
Three lines for the asset bucket: lifecycle rules, CORS and the storage class are not generated. They are critical settings that stay open until a person sets them on the bucket or records that production does not need them.
The website stays a web server's job
STACKIT Object Storage lists no website operation, and the run says to serve the content through the STACKIT CDN with the bucket as origin, or a web server. The index and error page names are written down as not carried.
Versioning needs a call after creation
STACKIT supports versioning through the S3 API, but the bucket resource has no versioning attribute, so you enable it once the bucket exists. The retention lock is a different case: STACKIT's lock is a property of the bucket at creation.
Cloud Storage setting by setting
| Cloud Storage | STACKIT Object Storage | What to do |
|---|---|---|
| Location EUROPE-WEST3 | Region of the provider, eu01 or eu02 | Confirm residency; the draft sets no region on the bucket |
| Storage class STANDARD, COLDLINE rule | One class; no transition documented | Compare capacity cost; drop the class rule |
| Versioning | Put-bucket-versioning; suspend only, never disable | Enable after creation; up to 1,000 versions per object |
| Delete rule on archived versions | Expiry of non-current versions in days | Rebuild by days; a count of newer versions has no documented counterpart |
| CORS rule | Put-bucket-cors, rules with origins, methods, headers | Google returns response_header in both Access-Control-Allow-Headers and Access-Control-Expose-Headers, so list it under AllowedHeaders and ExposeHeaders |
| Website main page and 404 | Not listed among supported operations | CDN with the bucket as origin, or a web server |
| IAM member allUsers, objectViewer | Bucket policy with Principal * | Re-create only if still intended |
| IAM member service account, objectAdmin | Credentials group and a bucket policy naming it | Credentials are scoped to the project |
| Locked retention policy, one year | Object Lock, set at bucket creation, at most 365 days | Needs a project-level Compliance Lock first |
Classes and open settings as the run decided them; STACKIT limits from its Object Storage reference pages.
Carrying the settings over by hand
Not engine output. The calls follow the STACKIT how-to pages for versioning, CORS and bucket policies, and the Object Lock page for the audit bucket. The values come from the demo input.
# Written by hand from the STACKIT documentation, not engine output.
# Endpoint of region eu01: https://object.storage.eu01.onstackit.cloud
EP=https://object.storage.eu01.onstackit.cloud
# 1. Versioning (media-prod-assets): once enabled it can only be suspended
aws s3api put-bucket-versioning --bucket media-prod-assets \
--versioning-configuration Status=Enabled --endpoint-url $EP
# 2. CORS (media-prod-assets): cors-rules.json
# response_header in Google is returned as Access-Control-Allow-Headers and
# Access-Control-Expose-Headers (XML API), so list it in both
{
"CORSRules": [{
"AllowedOrigins": ["https://www.example.com"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedHeaders": ["Content-Type"],
"ExposeHeaders": ["Content-Type"],
"MaxAgeSeconds": 3600
}]
}
aws s3api put-bucket-cors --bucket media-prod-assets \
--cors-configuration file://cors-rules.json --endpoint-url $EP
# 3. Public read (media-prod-site): policy.json
{
"Statement": [{
"Sid": "public-read",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "urn:sgws:s3:::media-prod-site/*"
}]
}
aws s3api put-bucket-policy --bucket media-prod-site \
--policy file://policy.json --endpoint-url $EP# Written by hand from the STACKIT documentation, not engine output.
# Project level, once: POST .../v1/project/{projectId}/compliance-lock
# Per bucket, at creation only:
POST https://object-storage.api.{region}.stackit.cloud/v1/project/{projectId}/bucket/media-prod-audit?objectLockEnabled=true
# Default retention (applies to objects uploaded after the update):
PUT .../v1/project/{projectId}/bucket/media-prod-audit/default-retention
{ "mode": "COMPLIANCE", "days": 365 }Lock is decided at creation
STACKIT states that Object Lock can only be enabled when the bucket is created and cannot be disabled afterwards. The project needs its Compliance Lock first, and a project with that lock cannot be deleted. Decide it before the first apply, not after the data is in.
365 days is the ceiling
The demo's retention period is 31,536,000 seconds, exactly 365 days, which is the maximum retention STACKIT lists for the Compliance Lock. A Google policy longer than a year exceeds that maximum. The default retention also applies only to objects uploaded after it is set, so plan for copied objects to start their period when they are uploaded, and test this on a scratch bucket.
Credentials are project-wide
STACKIT credentials are scoped to the project, so one credential reaches every bucket in it. To separate teams, use credentials groups in bucket policies, or separate projects. The bucket policy size limit is 20,480 bytes.
The migration in order
Inventory the buckets and their clients
List buckets, object counts and sizes, and who reads or writes. Find applications that use the Google client libraries or signed URLs: they need a code change to an S3 SDK. The run does not see objects or clients.
Create buckets, decide the lock first
Apply the generated bucket blocks in a test project. Create any bucket that needs Object Lock with the lock enabled at creation. Check the name and the region.
Set versioning, lifecycle, CORS and policy
Make the S3 API calls above. Read each configuration back with the matching get call and compare it with the source.
Create credentials
Create Object Storage credentials in the STACKIT project and replace the HMAC keys or service-account access that applications used on Google Cloud.
Copy the data
Copy with a tool that speaks both sides, for example rclone with a Google Cloud Storage remote and an S3 remote for the STACKIT endpoint; the run names rclone as the copy method. S3 tools can also read the source through the XML API with HMAC keys. Run a bulk copy, then a delta copy at cutover. This is general practice, not a step the engine runs.
Verify, switch, keep the source
Compare object counts and checksums, switch the application endpoints and keys, and keep the Google buckets read-only until the new ones have served real traffic.
Questions storage teams ask
Do the applications need code changes?
Clients that already use the S3-compatible XML API with HMAC keys change endpoint and keys. Clients that use the Google Cloud Storage libraries over the JSON API, and signed URLs made by them, switch to an S3 SDK. The run lists this as a review step.
Can lifecycle rules be copied one to one?
No. STACKIT documents expiry of current objects and expiry of non-current versions, by days, with prefix and tag filters, up to 1,000 rules. Storage-class transitions and counts of newer versions are not in that reference, so those rules are rebuilt or dropped.
What happens to the static website?
STACKIT Object Storage lists no website operation. Serve the content through the STACKIT CDN with the bucket as origin or through a web server, and re-create the public read policy only if it is still intended.
Does the locked retention policy move?
Not as a setting. Object Lock is enabled when a bucket is created, needs the project's Compliance Lock, and allows at most 365 days. Compare your retention requirement with that before choosing it.
Sources
Your next move: STACKIT.
Tell us about your estate. We show you what StackToIT delivers for it: target architecture, Terraform artifacts and a structured migration plan.