AWS, AZURE, GOOGLE CLOUD → STACKIT · TERRAFORM AND OPENTOFU
Terraform and OpenTofu migration to STACKIT, step by step
Your Terraform or OpenTofu code already states what your estate is meant to be, so it is the best starting point for a migration to STACKIT, provided the tool sees every module and every variable. StackToIT reads the configuration, maps each unit to a STACKIT target and writes a new STACKIT Terraform draft. This guide walks through that path on a small estate with two module calls, a variable file and a queue, and shows what happens when an input is missing. The demo estate is AWS; the StackToIT guides for Azure and Google Cloud document the same assess command for azurerm, azapi and Google provider code, and their mappings differ per service.
The path in seven steps
From a source repository to a reviewed STACKIT configuration.
Collect the roots and their variable files
List every root module you want assessed, the variable files that give it values in your pipelines (terraform.tfvars, environment files, TF_VAR settings) and the .terraform.lock.hcl. The assessed folder is the folder that declares the infrastructure. Without a lock file the demo run warned that no entry pins the AWS provider 5.100.0, and the docs name the lock file as a condition for validating the source.
Make the inputs complete
A variable without default and without value refuses every unit that reads it. In the demo all three units read the environment name, so the first run wrote no draft. Files with standard names are read anyway; a file such as prod.tfvars is passed with --var-file, in the order OpenTofu would read the files.
Run the assessment
stacktoit assess on the root folder. Add --offline when no network access to remote modules is allowed. Local modules are loaded from disk.
Read the module summary
The console lists each module call, whether it is local or external, and how many units and resources came out of it. A module that was not downloaded or is invalid leaves no STACKIT package for its units.
Review the drafted Terraform
The draft is a new configuration with its own provider block, not a patched copy of the source. Review it with the platform team before any apply.
Supply values and decisions, then rerun
Open variables go into review/target-values.tfvars, design answers into review/inputs.yaml, and the next run reads them.
Apply to a fresh state
Apply the reviewed draft to a state of its own, with the apply order and prerequisites written in execution/terraform/apply.md. The source state stays where it is until you retire the source.
The input: two module calls, one variable file, one queue
A small AWS estate written for this page. The module is instantiated twice, once with a variable and once with a literal value, and prod.tfvars supplies the environment name.
variable "environment" {
type = string
}
variable "retention_days" {
type = number
default = 30
}
module "exports" {
source = "./modules/bucket"
bucket_name = "acme-${var.environment}-exports"
retention_days = var.retention_days
}
module "archive" {
source = "./modules/bucket"
bucket_name = "acme-${var.environment}-archive"
retention_days = 365
}
resource "aws_sqs_queue" "jobs" {
name = "acme-${var.environment}-jobs"
visibility_timeout_seconds = 60
}resource "aws_s3_bucket" "this" {
bucket = var.bucket_name
}
resource "aws_s3_bucket_versioning" "this" {
bucket = aws_s3_bucket.this.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_lifecycle_configuration" "this" {
bucket = aws_s3_bucket.this.id
rule {
id = "expire"
status = "Enabled"
filter {}
expiration {
days = var.retention_days
}
}
}environment = "prod"
retention_days = 90Why this shape
Real estates reuse modules and read values from files. If the tool ignores either, the draft is wrong without looking wrong.
Without values the run refuses to guess
Both runs were made by StackToIT on 4 November 2026 against the same folder. The first has no variable file and stops short of a draft. The second passes --var-file and assesses all seven resources.
Source inputs missing: 1 required input is not in the package, so what reads it was not assessed.
variable environment · main.tf:39 · aws_sqs_queue.jobs.name, module.archive.aws_s3_bucket.this.bucketResources scanned: 7 (1 in the root module · 6 inside 2 module calls)
Migration units: 3 (3 instances)
Unit dependencies: 0
Modules: 2 module calls (0 external, 2 local): 2 loaded, 0 not downloaded, 0 invalid
module.archive ./modules/bucket 1 unit (RECONFIGURE 1) · 3 resources
module.exports ./modules/bucket 1 unit (RECONFIGURE 1) · 3 resources
Source checks: Built-in source checks passed — OpenTofu validation of the source was not runWhat the refusal protects
The first run wrote no terraform folder and no waves. All three units read the missing value: the two bucket names and the queue name. The tool invents no value for acme-${var.environment}-exports.
Declared omissions
If an input is missing on purpose, --omitted-inputs declares it. The run is then partial: units that read it are refused by name and the others are drafted.
The drafted STACKIT Terraform
Resource blocks from the second run. Review comments above the blocks are left out here; the S3 API documents are the files the run writes for settings the provider has no attributes for.
terraform {
# Write-only attributes (e.g. a VPN pre-shared key) need Terraform/OpenTofu 1.11 or later.
required_version = ">= 1.11.0"
required_providers {
stackit = {
source = "stackitcloud/stackit"
version = "0.121.0"
}
}
}
provider "stackit" {
default_region = var.region
}resource "stackit_objectstorage_bucket" "archive_this" {
project_id = var.project_id
name = "acme-prod-archive"
}
resource "stackit_objectstorage_bucket" "exports_this" {
project_id = var.project_id
name = "acme-prod-exports"
}{
"Rules": [
{
"ID": "expire",
"Status": "Enabled",
"Filter": {
"Prefix": ""
},
"Expiration": {
"Days": 90
}
}
]
}{
"Rules": [
{
"ID": "expire",
"Status": "Enabled",
"Filter": {
"Prefix": ""
},
"Expiration": {
"Days": 365
}
}
]
}Variables were resolved per module
The lifecycle rule of module.exports carries 90 days from prod.tfvars, the rule of module.archive carries the literal 365 days.
One draft, flat
Each module instance became its own bucket resource (archive_this, exports_this). The module structure is yours to rebuild on the target side if you want it.
What the run did with each source construct
Read from the second run.
| Source construct | Result in the run | Where to read it |
|---|---|---|
| module "exports" and module "archive", local source | Two module calls loaded, 6 resources scanned inside them, 1 unit each | Console summary, architect-report.md |
| Module resources bucket, versioning, lifecycle | One unit per instance: RECONFIGURE, STACKIT Object Storage | migration/technical-sequencing.md |
| var.retention_days with default 30, set to 90 in prod.tfvars | Rule expires after 90 days; the literal 365 stays 365 | terraform/s3api/*/lifecycle.json |
| aws_sqs_queue.jobs | REPLATFORM to a STACKIT RabbitMQ instance with open variables for plan, version and access list | terraform/messaging.tf, review/target-values.tfvars |
| provider region eu-central-1 | Placed in var.region, default eu01; data residency changes and is to be confirmed once | engineering/architect-report.md |
| required_version >= 1.6 | The draft requires >= 1.11.0; its header comment ties that floor to write-only attributes, for example a VPN pre-shared key, so check your pipeline version | terraform/terraform.tf |
Classes are a proposal. A class names the type of work and does not authorise it.
Decisions your architects own
The run drafts. These choices stay with you.
Module structure on the target
The draft is flat. Decide whether to wrap repeated buckets in a module again and how to name them.
State and pipeline
Decide where the new state lives and which pipeline applies it. Check that your OpenTofu or Terraform version meets the draft requirement of 1.11.0 or later.
Provider version
The draft pins the STACKIT provider at 0.121.0. The registry record read on 4 November 2026 shows 0.122.0 as latest, published on 3 November. Review the version against the registry before you apply.
API opt-in for S3 settings
Versioning and lifecycle go through STACKIT S3 API calls (four scripts under execution/api) that the plan makes only after you answer the opt-in decision. The same decision holds an alternative path for the queue through a STACKIT messaging API that is not generally available; the RabbitMQ blocks of the draft do not wait for it.
Queue design
The RabbitMQ instance needs a plan, a version and an access list that the source cannot supply. The run leaves them as variables with no default.
What stays outside Terraform
Everything the provider cannot express remains a documented step: the run lists those settings as not carried or as S3 API documents. Two notes from the run: once versioning is enabled on STACKIT it can only be suspended, not disabled, and until the lifecycle rules are applied nothing expires.
Questions teams ask about Terraform and OpenTofu
Does StackToIT read my state file?
The documented inputs are configuration, variables and modules, and a state file is not among them. The assessment describes what your code declares. Compare with your state yourself if drift matters.
What about remote modules?
Remote modules are resolved from existing local sources first. Network retrieval is used when needed unless --offline is set. In the demo both module calls were local, and the console counted 0 external.
Can I convert one resource at a time?
The run assesses the whole root module so that dependencies are visible, then orders units into waves. You choose which blocks of the draft you apply first.
Does it validate the source with OpenTofu?
Yes, automatically, when the machine meets the conditions in the docs: OpenTofu on the path, a network-denied sandbox and the providers in a local mirror (--provider-mirror) or plugin cache; for the source also a lock file. Nothing is downloaded. --no-validate skips it, --validate fails the run if it cannot run. The demo runs used --no-validate, and the console says so.
Is the output safe to apply as written?
No. The package is a proposal and nothing in it is approved. Apply only after your review, in the order the apply file gives.
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.