GOOGLE CLOUD → STACKIT · CASE STUDY
Cloud Run and Cloud SQL: a complete assessment walkthrough
One realistic estate, start to finish: a public Cloud Run API, a regional Cloud SQL database on a private network, a secret, a bucket and a registry. StackToIT read 13 resources, formed 10 migration units, drafted STACKIT Terraform for six of them and left 22 pieces of manual work, with 29 critical settings waiting for a decision. This page shows what that looks like.
The run in figures
Assessed by StackToIT on 4 November 2026, offline, on an estate written for this page. All numbers are from the run’s console output and assessment.json.
| Figure | Value | Meaning |
|---|---|---|
| Resources and units | 13 resources, 10 units | Three resources (the database, the secret binding and the invoker binding) are not units of their own; 12 dependencies link the units. |
| Classes | 1 CONTAINERIZE, 8 RECONFIGURE, 1 REPLATFORM | The service is containerized, the database changes platform, the rest is reconfigured. |
| Draft | 6 Terraform-only, 1 manual, 3 need no resource | Six units have a block in the STACKIT draft; the registry is set up by hand; the VPC network, the reserved private range and the private services connection need no block. |
| Waves | 4 | Foundations in wave 1, database last in wave 4. Units in a wave can move in parallel. |
| Open work | 22 manual pieces, 29 critical settings in 9 units | Each is a person’s decision or step. None runs by itself. |
| Readiness | 0 of 7 automated steps can run as written | The package is a proposal; the example has no lock file for the Google provider and the run was not asked to validate with OpenTofu. |
The run finished with 12 of 12 global invariants passing, three of which checked nothing for this estate.
The database: Cloud SQL to PostgreSQL Flex
Left: the regional PostgreSQL 16 instance of the input, with private IP and point-in-time recovery. Right: the blocks of the STACKIT Terraform the run wrote; the review comments above each block in the file are left out here.
resource "google_sql_database_instance" "orders" {
name = "orders-pg"
region = "europe-west3"
database_version = "POSTGRES_16"
depends_on = [google_service_networking_connection.sql]
settings {
tier = "db-custom-2-8192"
availability_type = "REGIONAL"
disk_size = 100
disk_type = "PD_SSD"
ip_configuration {
ipv4_enabled = false
private_network = google_compute_network.shop.id
}
backup_configuration {
enabled = true
point_in_time_recovery_enabled = true
}
}
}
resource "google_sql_database" "orders" {
name = "orders"
instance = google_sql_database_instance.orders.name
}resource "stackit_postgresflex_instance" "orders" {
project_id = var.project_id
name = "orders-pg"
version = "16"
flavor_id = "2.16-replica"
storage = {
class = var.postgresflex_storage_class
size = 100
}
backup_schedule = "0 2 * * *"
network = {
acl = var.orders_allowed_cidrs
}
retention_days = 32
}
resource "stackit_postgresflex_database" "orders_orders" {
project_id = var.project_id
instance_id = stackit_postgresflex_instance.orders.instance_id
name = "orders"
owner = var.orders_orders_owner
}Version, storage and database carry over
PostgreSQL 16, 100 GB and the database name are in the draft. The regional tier db-custom-2-8192 becomes flavor 2.16-replica, which the run's comment describes as 2 vCPU and 16 GiB: the smallest replica flavor in eu01 that covers 2 vCPU and 8 GB.
Private IP has no counterpart
The instance is reached at the endpoint STACKIT assigns, limited by an ACL. The list of allowed addresses is a variable you fill.
Point-in-time recovery is not a setting
STACKIT’s backup-and-clone how-to confirms it: restoring a backup, including to a point in time, creates a new instance in the same project, and there is no in-place restore. The input sets no retention, so Cloud SQL keeps its default of seven automated backups; the run records that as 7 days, and the draft sets retention_days = 32. The 02:00 daily backup schedule is a default the run says you should confirm.
The service: Cloud Run to a workload on SKE
Left: the Cloud Run service with its secret reference, bucket variable and VPC access. Right: the cluster the run drafted to host it as a Deployment.
resource "google_cloud_run_v2_service" "orders" {
name = "orders-api"
location = "europe-west3"
ingress = "INGRESS_TRAFFIC_ALL"
template {
service_account = google_service_account.api.email
scaling {
min_instance_count = 1
max_instance_count = 10
}
vpc_access {
egress = "PRIVATE_RANGES_ONLY"
network_interfaces {
network = google_compute_network.shop.id
subnetwork = google_compute_subnetwork.apps.id
}
}
containers {
image = "europe-west3-docker.pkg.dev/shop-prod/images/orders-api:2.4.0"
resources {
limits = {
cpu = "1"
memory = "512Mi"
}
}
env {
name = "RECEIPTS_BUCKET"
value = google_storage_bucket.receipts.name
}
env {
name = "DB_PASSWORD"
value_source {
secret_key_ref {
secret = google_secret_manager_secret.db_password.secret_id
version = "latest"
}
}
}
}
}
}resource "stackit_ske_cluster" "containers" {
project_id = var.project_id
name = "containers"
node_pools = [{
name = "workloads"
machine_type = var.containers_machine_type
minimum = var.containers_nodes_min
maximum = var.containers_nodes_max
availability_zones = var.availability_zones
}]
}The image moves, the manifest is yours
The run writes the cluster. The Deployment and Service manifests are not generated; name, environment, resources, service account and network settings are listed as not carried.
Public access is flagged
The allUsers invoker binding raises a warning: anyone may call the service today, and your ingress on SKE must decide who may in future.
Scaling bounds go into your manifest
The source keeps 1 to 10 instances. The cluster has no workload yet, so set replicas and an autoscaler (for example a HorizontalPodAutoscaler) yourself. The run also warns that scale-to-zero needs KEDA or Knative on SKE; with a minimum of one instance this service does not rely on it.
Every unit, its wave and its target
From migration/technical-sequencing.md. A unit moves only after the units it depends on.
| Unit (Google Cloud) | Wave and class | STACKIT target in the run |
|---|---|---|
| Artifact Registry repository | 1 · RECONFIGURE | STACKIT Container Registry, set up by hand |
| VPC network | 1 · RECONFIGURE | Project networking, with a Network Area where needed; no block in the draft |
| Service account | 1 · RECONFIGURE | STACKIT service account |
| Storage bucket | 1 · RECONFIGURE | STACKIT Object Storage bucket |
| Reserved private range, subnetwork, secret | 2 · RECONFIGURE | No resource; STACKIT network 10.20.0.0/24; key in Secrets Manager |
| Cloud Run service | 3 · CONTAINERIZE | SKE workload, or a Cloud Foundry app from the image |
| Private services connection | 3 · RECONFIGURE | Not needed: managed services have their own endpoints and ACLs |
| Cloud SQL instance | 4 · REPLATFORM | STACKIT PostgreSQL Flex |
The interesting decisions
Each comes from a warning or a not-carried line the run wrote next to the block.
How clients reach the database
The source database has no public address. On STACKIT it has an endpoint with an ACL. Decide the allowed ranges, and whether the new workload reaches it through the cluster’s egress addresses.
Recovery and retention
Point-in-time recovery restores into a new instance on PostgreSQL Flex, and the draft keeps backups 32 days, where the source relies on Cloud SQL’s default of seven automated backups. Check both against your recovery point objective and update the recovery runbook, including where clients connect after a restore.
Instance size
The run picks 2.16-replica because the source is regional (a standby in a second zone). The run itself notes that no STACKIT page states the zone of each replica node or how a failover works, so confirm the flavor and the availability you need before you apply.
Protection against deletion
Terraform’s deletion protection on the Cloud SQL instance guards only the Terraform workflow, and the draft does not add a counterpart. Add lifecycle { prevent_destroy = true } as the run advises; the PostgreSQL Flex documentation also recommends instance protection for critical instances.
Who may call the API
The source allows anyone. Decide whether the new ingress stays public, sits behind a gateway or requires a token.
Secret and its readers
The secret is recreated as a key by hand; the value is never copied into the draft, and the accessor binding is not carried.
Bucket versioning
The source bucket is versioned. The run notes that STACKIT Object Storage supports versioning through the S3 API, but the bucket resource has no versioning attribute, so the draft block sets only name and project. Turn versioning on after the bucket exists; once on, it can only be suspended.
Data movement
migration/data-movement.md lists the database and the bucket as decisions with no mechanism chosen, because the source states neither data size nor tolerated downtime. The unit reasons name pg_dump with pg_restore or logical replication for the database and rclone for the bucket.
Where to read each answer
- index.html
- The summary and the decision pages, offline
- assessment.json
- Every unit with class, target, reason and findings
- terraform/
- The STACKIT draft, one file per area
- migration/technical-sequencing.md
- The waves above
- migration/data-movement.md
- Database and bucket with the open data decisions
- execution/manual/actions.md
- The 16 work packages of manual steps, each with an owner
- engineering/architect-report.md
- Findings, validation matrix and what is not proven
Questions architects ask about this run
Is the STACKIT Terraform ready to apply?
No. The run calls it a draft that needs architect review, and none of the seven automated steps can run as written until decisions and values are in. The example also has no provider lock file and was not validated with OpenTofu.
Why does the database come last?
It depends on the network and on the private services connection, so the run puts it in wave 4. The order is a starting point for your wave plan, not a schedule.
Why is the Cloud Run service not Terraform?
The run drafts the SKE cluster that hosts it. The Deployment and Service manifests are not generated, so you write them from the not-carried list.
Does the run move any data?
No. StackToIT writes files and changes nothing in any cloud. The data steps are decisions for your team.
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.