ORACLE CLOUD → STACKIT · TUTORIAL
The OCI Terraform workflow, from root module to review package
This walk-through takes a small Oracle Cloud root module from the folder on disk to the files your architects review. You commit a lock file, run one command, read the console summary, answer the two requests the run marks as blocking and run again. The demo estate was written for this page and assessed on 4 November 2026; every excerpt below is copied from that run. For an Oracle Cloud source the run classifies and sequences every unit; the STACKIT resources themselves are built by your team.
The workflow in six steps
The order matters less than the habit of reading what each step records before the next one starts.
1. Commit the lock file
StackToIT reads Terraform and OpenTofu root modules that use the oracle/oci provider and never connects to a tenancy. The mappings were checked against version 9.11.0 of that provider. Commit .terraform.lock.hcl next to the module: without a lock file entry for oci, the run states that the provider version a plan would use is not proven and caps every unit at MEDIUM confidence.
2. Run the assessment
From the folder that holds the module, run stacktoit assess with the path of the module. The demo run added --offline, which prohibits outbound network requests; the documentation lists it among the source options together with --var-file, --provider-mirror and --validate-source.
3. Read the console summary
The summary names the resources scanned, the units found and the class counts. In the demo it reported 4 resources: 3 units known to exist (VCN, subnet, bucket) and 1 conditional unit, the instance block, whose count depends on a variable. The source completeness is therefore RUNTIME_TOPOLOGY_PARTIAL.
4. Answer the blocking requests
The run writes inputs-template.yaml. Two requests are marked as blocking: the STACKIT region (eu01 or eu02) and the value of the root variable app_names. Copy the file, fill the value lines and hand it back with --inputs. Four further requests, such as the target project, are listed at the end as not applicable in this build, each with the place where the setting is made instead.
5. Run again on the same output folder
The next run reads your answers. If you want to compare the two runs side by side, pass --keep-previous: the documentation describes it as keeping the earlier run in OUT/previous-<run id>/. Supply app_names with --var-file as well, so the instance unit stops being a prerequisite.
6. Review the report, then build
Open engineering/architect-report.md and migration/technical-sequencing.md. For an Oracle Cloud source the terraform/ folder holds only the provider and variable files, so your team writes the STACKIT resources from the report, then initialises, validates and plans them against a test project. StackToIT itself never runs apply.
A variable-driven resource, assessed
Left: the instance block of the demo input, exactly as written. Right: the record the run keeps for it in assessment.json. Three servers come from one block, so the unit carries instances: 3, and the record says why it is only conditional. assessment.json is written as a single line, so the pane shows selected fields of each unit, indented and nested as in the file, with the values unchanged.
resource "oci_core_instance" "app" {
for_each = toset(var.app_names)
compartment_id = var.compartment_id
availability_domain = var.availability_domain
display_name = each.key
shape = "VM.Standard.E5.Flex"
shape_config {
ocpus = 1
memory_in_gbs = 8
}
source_details {
source_type = "image"
source_id = var.image_id
}
create_vnic_details {
subnet_id = oci_core_subnet.app.id
}
}{
"id": "oci_core_instance.app",
"presence": "CONDITIONAL",
"presence_cause": {
"kind": "root-variable-overridable",
"where": "for_each",
"expr": "toset(var.app_names)",
"variables": [
"var.app_names"
]
},
"instances": 3,
"wave": 0,
"result": {
"class": "RECONFIGURE",
"confidence": "MEDIUM",
"target": {
"service": "STACKIT Compute Engine"
},
"reason": "A compute instance becomes a STACKIT server. The shape becomes a STACKIT flavor, the image is rebuilt or imported, and the attached volumes and network interfaces move with it."
}
}Defaults are read, not trusted
The variable app_names has a default of three names (web-01, web-02, worker-01), so the run counts three instances and records the state as present on defaults. Because a variable file, -var or TF_VAR_app_names can override that default, the unit is marked CONDITIONAL with the cause root-variable-overridable.
A prerequisite, not a guess
technical-sequencing.md lists the unit under prerequisites, outside the waves, and names the remedy: supply the variable value with --var-file. Its default is not taken as the answer, and wave 0 in the record means it holds no wave yet.
Class and target stay readable
The server becomes a STACKIT server on Compute Engine, class RECONFIGURE. The shape becomes a flavor, the image is rebuilt or imported, and attached volumes and network interfaces move with it.
Where each answer lives in the output folder
- index.html
- The unit-by-unit view, readable offline in a browser.
- assessment.json
- Every unit with class, confidence, target, reason, wave, presence and the settings left to you.
- engineering/architect-report.md
- Per unit: target, reason, critical settings and warnings, plus the validation matrix.
- migration/technical-sequencing.md
- Waves from dependencies, and the prerequisites that are not wave members.
- migration/data-movement.md
- Stateful units for which a person must choose a data-movement method.
- execution/manual/actions.md
- The manual steps, grouped into work packages with an owner and a done condition.
- inputs-template.yaml
- The questions of the run, as stubs to fill and hand back.
- review/
- Copies to edit: decisions.yaml, inputs.yaml, progress.yaml and target-values.tfvars.
- terraform/
- Only terraform.tf, variables.tf and a README for an Oracle Cloud source: no STACKIT resource blocks.
The two requests that block the run
Both stubs come from inputs-template.yaml of the demo run. Left: the region, with the two options the run offers. Right: the value of the root variable app_names.
# 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
# explanation: why you chose this (optional; remove the # to write it)
answered_by: CUSTOMER_ARCHITECT # a role, not a person
answered_at: "2026-11-04" # YYYY-MM-DD
recorded_with:
basis_digest: sha256:94a1663bf63dd33750f12f962a5adedcc2bf291ebf4b101eee03238a48e95e54 # source.variable (SOURCE_PARAMETER, request state OPEN)
# needed: The value the running environment gives the root variable app_names, which the source sets only as a default that a variable file or the command line can override.
- id: INP-SPA-5bd4d3b73362
kind: SOURCE_PARAMETER
topic: source.variable
scope:
profile: default
subject: app_names
value: "" # fill one value
# explanation: why you chose this (optional; remove the # to write it)
answered_by: CUSTOMER_ARCHITECT # a role, not a person
answered_at: "2026-11-04" # YYYY-MM-DD
recorded_with:
basis_digest: sha256:9ba8056e684e8cca3711c1ee31ff62a5a4e562be04ff3b7c83ee925980b200c5Answer the region first
The template says so itself: requests that depend on the region are derived from the eu01 catalogue until the region is answered. Choose eu02 and such an answer recorded for eu01 is marked STALE_CATALOG, not applied, and asked again.
Fill one value, leave the binding alone
A stub you leave empty is no answer and no error: the request stays open and the answers you did fill still apply. id, kind, topic, scope and recorded_with bind the answer to this source and this knowledge base; do not edit them.
Roles, not people
answered_by takes a role such as CUSTOMER_ARCHITECT, and answered_at the day of the answer. The optional explanation line is where the reason for a choice is kept for the next reviewer.
What the four resources became
| OCI resource | STACKIT target | Class and wave |
|---|---|---|
| oci_core_vcn.main | STACKIT network | RECONFIGURE, wave 1 |
| oci_objectstorage_bucket.assets | STACKIT Object Storage | RECONFIGURE, wave 1 |
| oci_core_subnet.app | STACKIT network | RECONFIGURE, wave 2 |
| oci_core_instance.app (3 instances) | STACKIT Compute Engine | RECONFIGURE, prerequisite |
All four are MEDIUM confidence: without a lock file entry for oci, units are capped there. The terraform/ folder holds no resource block for any of them; the Oracle Cloud adapter classifies and orders the units, and the STACKIT side is built by your team.
What to change before you apply anything
Pin and validate the source
Commit the lock file and make the provider packages available through --provider-mirror or a plugin cache; the documentation also requires OpenTofu on the PATH and a network-denied sandbox on the host, and nothing is downloaded during validation. In the demo the OpenTofu check of the source did not run (LOCK_FILE); the built-in checks passed. That is neither a pass nor a failure of the source.
Authenticate the provider
When your team writes the STACKIT resources, it needs a service account. The documentation of the STACKIT provider 0.121.0, the version the run pins, names two ways to authenticate: workload identity federation and the key flow with a service account key. The provider tries workload identity federation first and looks for credentials in the provider block, then in environment variables, then in a credentials file. Configure a state backend too; none is declared.
Decide the 4 units
All four are yours: the run writes no block for them. Build the STACKIT counterparts from the targets in the report (network, Object Storage, Compute Engine) or record that one needs none.
Mind the numbers
The console reports 12 of 12 invariants passed, and 9 of them checked nothing because no resource was generated. Read that as an empty check, not as assurance.
Questions teams ask before the first run
Does the run need access to our Oracle Cloud tenancy?
No. It reads the Terraform or OpenTofu files of the root module and does not connect to a tenancy. Values that are only known at plan time, such as an overridden variable, are asked for in the answer file.
Why does the summary say 3 units while 4 resources were scanned?
The 3 are the units known to exist: VCN, subnet and bucket, each with one instance. The fourth resource, the instance block, is counted separately as 1 conditional unit, because its for_each depends on a variable. The class bar (RECONFIGURE 3) counts only the known units; the conditional unit would also be RECONFIGURE.
Do we need OpenTofu installed?
Not for the assessment itself. The documentation says OpenTofu is used for source validation when the sandbox and the provider packages are available. The demo run found OpenTofu 1.13.1 but skipped the source check for lack of a lock file; the built-in checks still ran.
Does the run write STACKIT Terraform for an Oracle Cloud source?
No. For Oracle Cloud the terraform/ folder holds only the provider and variable files. The value of the run lies in the classification, the targets, the waves and the open questions; your architects build the STACKIT resources from those.
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.