DELIVERY · CI/CD AND IAC
CI/CD and infrastructure-as-code delivery on STACKIT
Deliver the migrated infrastructure from a pipeline, not from a laptop: assess with a strict gate, keep the generated package as a build artifact, authenticate the STACKIT provider with a short-lived OIDC token instead of a stored key, put state in a backend you protect, and apply only a plan file somebody reviewed. This guide follows a small AWS estate through that chain and marks which parts come from a StackToIT run and which from the STACKIT provider documentation.
The delivery chain, in order
1. Assess in the pipeline
Run the assessment with --strict and --format json. The report is written even when the gate fails, so the job keeps the output folder as an artifact and records the exit code beside it.
2. Verify the folder
stacktoit verify checks the run against its manifest and checks that the generated files agree with each other. It is not proof that anything ran in a cloud, and it is not a signature.
3. Answer the open requests in files
The run writes inputs-template.yaml and decisions-template.yaml into its output folder. Copy them outside the generated folder, fill them, commit them, and run again with --inputs and --decisions. A recorded review alone does not rewrite the draft.
4. Commit the reviewed draft
The terraform folder pins the provider to one version and requires Terraform or OpenTofu 1.11 or later. Treat the draft as a proposal: it goes through a normal pull request.
5. Authenticate without a stored key
Create a service account, attach a federated identity provider for your CI system, and let the provider exchange the pipeline's OIDC token. Keep the assertions as narrow as the token allows.
6. Choose and protect the state
The draft declares no backend. An S3 backend on Object Storage works with the AWS-specific checks skipped, but locking does not; and the state holds some secrets in plain text.
7. Plan, review, apply the plan file
Run init, validate and plan -out as separate steps, have a person read the plan, then apply that exact file.
8. Re-run after every change
New inputs or decisions change the draft and the execution plan. Assess again and review the new plan rather than patching the old one.
The gate, as the run reports it
An estate of five resources written for this page, assessed by StackToIT on 4 November 2026 with --strict and --format json, with --no-validate, so OpenTofu validation was skipped. First: the two commands the StackToIT automation documentation gives for a gate, each verbatim from its own code block. Then: the verification event and the summary record the run printed, pretty-printed, with only the fields discussed here (the records hold more, such as run id, file list and step counts).
stacktoit assess ./terraform --strict --format json > assessment-events.jsonlstacktoit verify ./terraform-stacktoit --output json{
"type": "event",
"event_code": "RUN_VERIFIED",
"phase": "publish",
"severity": "INFO",
"human_summary": "The package passed the checks of stacktoit verify (7 checks, PASSED) before it was published; the record is in run_verification of manifest.json."
}
{
"type": "summary",
"exit_code": 7,
"exit_reasons": [
{
"code": 7,
"reason": "PACKAGE_NOT_EXECUTION_READY",
"detail": "package PARTIALLY_EXECUTABLE"
}
],
"run_status": "COMPLETE",
"package": "FULL",
"verification": "PASSED",
"summary": {
"package_state": "PARTIALLY_EXECUTABLE",
"architect_approval": "NOT_PROVIDED",
"safe_to_apply": "NOT_ASSERTED"
}
}Exit 7 is the strict gate working
The package was complete and verified, yet the exit code is 7 with the reason that the package is not ready to execute: it is partially executable. Of its 12 automated steps, one waits for a design decision (the boot image), and none can run as written until project_id has a value. A first assessment will normally fail a strict gate; that is the point of the gate.
Treat the code and the artifact together
The documentation asks automation to record the exit code and keep the generated files where they exist. Other codes: 3 means validation rejected the source or the draft, 4 an incomplete source, 5 a locked output folder, 6 a failed verification.
Approval stays a human field
The summary shows architect approval not provided and safe-to-apply not asserted. A pipeline does not fill those in; a person does, through the decisions file.
What the draft hands to the pipeline
Same run. Left: terraform/terraform.tf, which pins the STACKIT provider. Right: the commands execution/terraform/apply.md lists. The run applies nothing; the file says nothing in this folder has been run.
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
}cd terraform
tofu init
tofu validate
tofu plan -out=stackit.tfplan
tofu apply stackit.tfplanOne provider version
The draft pins the provider to a single version, so every pipeline run plans against the same schema. Raise the pin on purpose, in a pull request.
Variables without defaults stay open
The project ID, the image, the machine type and the boot volume size have no default. Supply them through a variables file or TF_VAR environment variables, never in a committed file when they are sensitive.
Manual steps and a two-run apply
apply.md opens with "Do not apply yet": one OpenTofu step waits for a decision. It has a section for manual steps that must be complete before tofu apply (none in this run), because OpenTofu applies the whole draft at once and does not enforce them, and here it splits the apply into two runs: the two security groups first, then two STACKIT API calls that delete default egress rules, then the rest.
Authentication with workload identity federation
Verbatim from the STACKIT provider's Workload Identity Federation guide, not produced by a run. Left: the federated identity provider the guide configures on the service account for its own repository (replace the repository and branch in sub with yours). Right: the plan and apply steps of its GitHub Actions example; the job also needs the id-token: write permission.
- **Provider Name**: GitHub # This is just an example, you can choose any name you want
- **Issuer URL**: https://token.actions.githubusercontent.com # This is the public issuer for GitHub Actions OIDC tokens
- **Assertions**:
- **sub**->equals->repo:stackitcloud/terraform-provider-stackit:ref:refs/heads/main # This is the repository and branch where the action will run
- **aud**->equals->sts.accounts.stackit.cloud # Mandatory value - name: Terraform Plan
run: |
terraform plan -out=tfplan
env:
STACKIT_USE_OIDC: "1"
STACKIT_SERVICE_ACCOUNT_EMAIL: "terraform-example@sa.stackit.cloud"
- name: Terraform Apply
run: terraform apply -auto-approve tfplan
env:
STACKIT_USE_OIDC: "1"
STACKIT_SERVICE_ACCOUNT_EMAIL: "terraform-example@sa.stackit.cloud"No static secret
The provider documents this flow as the way that removes the need for long-lived secrets in CI/CD. It looks for the OIDC setting in the provider block, then in environment variables, then in a credentials file. The guide's example runs Terraform and applies with -auto-approve in the same job; the run's package uses tofu, and this page did not check the flow under OpenTofu. Put the plan review between the two steps.
The audience is mandatory
The STACKIT documentation requires an assertion on the audience; only the equality operator is supported, and a token is accepted only when every assertion holds. The guide advises the most restrictive assertions available.
Other CI systems
The guide covers GitHub Actions and Azure DevOps and says the principles may apply to other CI/CD platforms that support OIDC.
Stages, settings and gates
| Stage | Command or setting | Gate and what to keep |
|---|---|---|
| Assess | stacktoit assess ./terraform --strict --format json | Exit 0 is a published report; 7 is a strict failure. Keep the output folder either way. |
| Verify | stacktoit verify RUN_DIR | Exit 0 passes, 6 reports findings. Compare two runs with --same-as. |
| Review inputs | --inputs and --decisions files in version control | Reviews tied to a different source may be reported as stale. |
| Authenticate | STACKIT_USE_OIDC and STACKIT_SERVICE_ACCOUNT_EMAIL | Key flow stays available through STACKIT_SERVICE_ACCOUNT_KEY_PATH. |
| State | S3 backend on Object Storage, or a PostgreSQL backend | use_lockfile has no effect on STACKIT Object Storage: run one apply job at a time. |
| Plan and apply | tofu plan -out, then tofu apply on the plan file | Store the plan; a person approves before apply. |
The run did not test locking on any backend, and it says so. The state also holds the Object Storage secret access key in plain text, so the state is itself a secret.
Decisions the architect owns
Who approves the plan
A reviewer from the owning team, with the plan file attached to the change. The run records approval as not provided until a person answers.
State location and encryption
The run advises encrypting the state and limiting read access to the pipeline, and points to OpenTofu state encryption or an encrypted backend.
One job at a time
Without working locks, serialise applies in the pipeline configuration.
Service account permissions
The service account needs the roles of the services the draft uses; the requirements file names no role, so look up the minimum for each service.
Strict or advisory gate
Decide whether exit 7 blocks the merge or only labels it. As practice: advisory for a first assessment, strict for the apply stage.
Delivery questions
Does StackToIT apply anything from the pipeline?
No. Assess reads and writes files. Terraform or OpenTofu applies, run by your pipeline or a person.
Why does my strict gate fail on a clean assessment?
Because strict mode turns partial and unverified outcomes into failures. A package that is partially executable, with decisions open, returns exit 7 although the report and the verification are fine.
Can I lock state on Object Storage?
Not with the S3 backend's lock file: the provider documentation states it has no effect on STACKIT Object Storage and points to a PostgreSQL backend for locking.
Should the pipeline hold a service account key?
Prefer workload identity federation. The key flow exists, needs a service account key file, and the key is a long-lived secret.
Where do secrets in the state come from?
The pinned provider writes some attributes, such as an Object Storage credential's secret access key, into the state. sensitive hides them from the plan output only.
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.