GOOGLE CLOUD → STACKIT · TUTORIAL

GCP Terraform to STACKIT: the migration workflow

StackToIT reads your Google provider Terraform offline, without a Google Cloud account, and writes a STACKIT draft plus the list of decisions it cannot make for you. This tutorial runs one demo estate twice: first as it stands, then with the missing variable and the first answer supplied. You see what the console reports, which files to open, which blocks the run writes and what a person still has to do before a plan.

First run: one command, one summary

A demo estate written for this page and assessed by StackToIT on 4 November 2026 with the google provider pinned to 8.4.0: a network with one subnetwork, a firewall rule, a virtual machine, a bucket and a Cloud SQL instance with its user. Top: the command, with the output path shortened. Bottom: selected lines of the console summary, verbatim. The demo was run with the optional source validation switched off, because no provider mirror was at hand.

Commandshell
stacktoit assess . ./stacktoit-out --offline --no-validate
Console summary (selected lines)terminal output
Source        7 resources · 6 units · visibility: Variable unresolved
Migration     5 drafted · 1 need no resource (of 6 units the draft considered)
Attention     3 decisions to make (1 of them with the draft's DEFAULT to confirm or change, 1 of the 3 holds plan steps) · 5 units to review · 6 open requests (2 block the draft, marked in inputs-template.yaml)
  • Seven resources, six units

    The estate holds seven Terraform resources. The run groups them into six units to assess, because the Cloud SQL user is part of the unit of its instance. Every unit is classified separately, so the plan is read per unit and not per file.

  • Nothing connects to Google Cloud

    The StackToIT documentation states that the Google Cloud input is read from Terraform and OpenTofu root modules and that the tool does not connect to a Google Cloud project. The run needs your code and variable values, not credentials.

  • The variable that has no value

    db_password is sensitive and has no default, so the source is reported as variable unresolved. The run says that this does not stop the assessment and that no value is ever asked for. The unit that reads it cannot be assessed completely until a variable file supplies it.

  • Why every unit sits at medium confidence

    The demo has no lock file, so the run cannot prove which google provider version a plan would use. It states this once for the whole estate and caps the confidence of all six units at medium. The run states that the cap holds until the exact version is pinned by a committed .terraform.lock.hcl.

Second run: supply the variable and the first answer

The first run wrote review/inputs.yaml. Its first group holds the requests that change the draft when answered. Left: the request for the target region as the template wrote it, with the value empty. Then the command of the second run (output path shortened), the variable file it read and the same two console lines afterwards. The answer eu01 was written into a copy of the file; the variable file holds a placeholder that is not a real secret.

Answer template (excerpt)review/inputs.yaml
  # target.region (TARGET_SELECTION, request state OPEN)
  # needed: The STACKIT region the migrated estate is built in.
  - id: INP-TSE-1eaf5b60fa62
    kind: TARGET_SELECTION
    topic: target.region
    scope:
      profile: default
    # options: eu01 | eu02
    value: ""                # pick one of the options above
Second commandshell
stacktoit assess . ./stacktoit-out2 --offline --no-validate \
  --var-file ./vars.tfvars \
  --inputs ./inputs.yaml
Variable filevars.tfvars
db_password = "demo-only-not-a-real-secret"
Console summary (selected lines)terminal output
Source        7 resources · 6 units · visibility: Complete
Attention     3 decisions to make (1 of them with the draft's DEFAULT to confirm or change, 1 of the 3 holds plan steps) · 5 units to review · 4 open requests
  • Source complete, requests down from six to four

    With the variable file the source reads as complete. The two requests that blocked the draft, the region and the variable, are closed, and four open requests remain. The template binds each answer to this source with fields you must not edit, and a stub left empty is never treated as a default.

  • The Terraform did not change

    Comparing the terraform folders of both runs shows one difference: the run identifier and the source digest in terraform/README.md. The region variable already defaulted to eu01. Answers do not always rewrite the draft; here they changed the state of the plan.

  • Answers live outside the run folder

    The documentation advises keeping your completed answers outside the generated report directory and passing them with --inputs on the next run. According to the documentation, a rerun on the same output folder moves the earlier run into a folder named previous-<run id> by default. For this page the second run went to a separate folder, ./stacktoit-out2, so both runs could be compared side by side.

Which file answers which question

Paths inside the output folder of the second run. All of them exist in the demo run; open them in this order.

index.html
The summary and the decisions to make. Start here.
assessment.json
Per unit: class, confidence, target service, reason and findings.
terraform/
The STACKIT Terraform draft, one file per area (network, security, compute, data, storage), plus variables.tf and terraform.tf.
terraform/README.md
What the draft holds, the services to enable first, the target security checklist and what is not carried.
engineering/not-carried.csv
Every source property the draft does not carry, property by property.
migration/technical-sequencing.md
Waves computed from the dependencies between units.
execution/
Operator files: manual steps, the OpenTofu steps and checks.
review/
The answer templates: inputs.yaml, decisions.yaml and the target values file.

How the demo estate was classified

Google Cloud resourceSTACKIT targetClass
google_compute_networkNo resource: each subnetwork becomes a network in the projectReconfigure
google_compute_subnetworkstackit_network with the same IPv4 prefixReconfigure
google_compute_firewallstackit_security_group with one rule per allow ruleReconfigure
google_compute_instancestackit_server with a network interfaceReconfigure
google_sql_database_instance with its userstackit_postgresflex_instance and stackit_postgresflex_userReplatform
google_storage_bucketstackit_objectstorage_bucketReconfigure

Classes as the run decided them, all at medium confidence. The network container needs no STACKIT resource; the draft says so and counts it as one unit that needs no resource.

Network, firewall and server, converted

First pane: the firewall rule and the virtual machine from the input. Then the blocks the engine wrote for network, security group with its rule, and server, one pane per file; the review comments above each block in the file are left out here, and so are the interface block and the database.

Example inputmain.tf
resource "google_compute_firewall" "web" {
  name      = "allow-web"
  network   = google_compute_network.portal.name
  direction = "INGRESS"

  allow {
    protocol = "tcp"
    ports    = ["443"]
  }

  source_ranges = ["0.0.0.0/0"]
  target_tags   = ["web"]
}

resource "google_compute_instance" "web" {
  name         = "portal-web-1"
  machine_type = "e2-standard-4"
  zone         = "europe-west3-a"
  tags         = ["web"]

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-12"
      size  = 50
    }
  }

  network_interface {
    subnetwork = google_compute_subnetwork.app.id
  }
}
STACKIT: networkterraform/network.tf
resource "stackit_network" "app" {
  project_id       = var.project_id
  name             = "app"
  ipv4_prefix      = "10.20.0.0/24"
  ipv4_nameservers = var.default_ipv4_nameservers
}
STACKIT: security group and ruleterraform/security.tf
resource "stackit_security_group" "web" {
  project_id = var.project_id
  name       = "allow-web"
}

resource "stackit_security_group_rule" "web_allow_web" {
  project_id        = var.project_id
  security_group_id = stackit_security_group.web.security_group_id
  direction         = "ingress"
  ether_type        = "IPv4"
  ip_range          = "0.0.0.0/0"
  port_range = {
    max = 443
    min = 443
  }
  protocol = {
    name = "tcp"
  }
}
STACKIT: serverterraform/compute.tf
resource "stackit_server" "web" {
  project_id        = var.project_id
  name              = "portal-web-1"
  machine_type      = var.web_machine_type
  availability_zone = var.availability_zone
  boot_volume = {
    source_type           = "image"
    source_id             = var.web_image_id
    size                  = 50
    delete_on_termination = true
  }
  network_interfaces = [stackit_network_interface.web_1.network_interface_id]
}
  • Tags become an explicit choice

    The firewall rule selects servers by the network tag web. STACKIT security groups have no tag selector, so the draft creates the group and the rule and leaves the attachment open: you decide which network interfaces list the group. The run names this as its first next action, owned by the security architect.

  • Priority and policy layers are not carried

    STACKIT has no rule priority, and the scan cannot see an organisation or folder policy that may restrict the source further. The comments in the draft state that the rules written are what the scanned rules allow and may allow more than the source does in effect.

  • Zone and image are yours

    The source zone does not carry over. The server is placed in a zone variable, and the machine type and image are variables without defaults, with the source values named in their descriptions.

The workflow in order

  1. Pin the provider and commit the lock file

    Declare the google version in required_providers and keep .terraform.lock.hcl with the code. Without a lock entry the run warns that the version is not proven and caps confidence at medium.

  2. Assess offline

    Run the command from the folder with the root module, not a subfolder of a larger configuration. With the offline option the run uses only local files and caches and makes no outbound requests.

  3. Supply variables and answers

    Give a variable file for every sensitive input, for example with the var-file option, and fill the value lines of the inputs file. A stub left empty stays an open request and is not an error.

  4. Read the first next action

    The console ends with one next action and an owner role. Here it is the security architect naming the servers a security group is meant for.

  5. Review the draft with the owners

    Open terraform/ and engineering/not-carried.csv with the platform, network and security owners. Each block keeps a short comment on its origin, its values and what is not carried.

  6. Plan in a test project

    Run tofu init, validate and plan against a test project. The draft README states that StackToIT never runs apply.

What to change before the first plan

  • Four variables without defaults

    postgresflex_storage_class, project_id, web_image_id and web_machine_type have no default. The target values file in review/ lists them with their descriptions.

  • Database access

    The Cloud SQL instance is public with one authorized network. The draft restricts the STACKIT instance by an access list of the same CIDR, a variable you can change. The user's password is not copied: STACKIT generates it, and you hand the new one to the application.

  • Roles of the database user

    Cloud SQL users are members of a superuser role the draft does not carry. The roles variable defaults to login only; choose the roles per user.

  • Egress and DNS

    The run cannot prove whether the source subnet had an internet route, so it keeps the STACKIT default gateway and states the doubt. The nameservers default to the STACKIT resolver addresses; private names need a DNS server of your own.

  • Credentials and state

    The draft README recommends Workload Identity Federation for the provider and warns that state locking on an S3 backend on STACKIT Object Storage has no effect; run apply from one job at a time or use the PostgreSQL backend the provider points to.

  • Versioned bucket

    The bucket has versioning on. The bucket resource has no versioning attribute, so you enable it through the S3 API after the bucket exists.

Questions platform teams ask

Do we need Google Cloud credentials to run it?

No. The StackToIT documentation says the Google Cloud input is read from Terraform and OpenTofu root modules that use the Google provider and that no connection to a Google Cloud project is made.

Why does the run report the source as unresolved?

A sensitive variable without a default or a value cannot be read. The run names the variable and the file and line that read it, continues the assessment and asks for a variable file; it never asks for the value itself.

Do the answers change the Terraform?

Sometimes. In this demo the region answer closed a blocking request without changing a line of the draft, because the default already matched. Answers that name a value the draft uses do rewrite the affected blocks on the next run.

Can we apply the draft as it is?

No. It is a proposal for review. Fill the variables, decide which interfaces get the security group, plan in a test project and let an architect settle the open decisions first.

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.