Infrastructure as Code for Tractus-X Umbrella, managed with OpenTofu + Terragrunt. Modular, multi-cloud- and multi-stage-ready, with a chained CI/CD pipeline that runs only the affected stacks and promotes changes through stages with a manual gate.
The whole pipeline uses the tofu binary: every terragrunt.hcl sets
terraform_binary = "tofu" and providers come from the OpenTofu registry.
iac/
|-- modules/<cloud>/ # Reusable modules
| |-- label/ # Name generator (Azure convention)
| |-- resource-group/ # Resource group
| |-- storage-account/ # Storage account
| |-- storage-container/ # Blob container
| |-- remote-state/ # Composition: resource-group + storage-account + storage-container
| |-- networking/ # Network, subnet, security group
| |-- identity/ # Managed identity
| |-- dns/ # DNS zone
| `-- aks/ # AKS: system + workloads pools, autoscaler
|-- bootstrap/<cloud>/<stage>/ # State backend bootstrap (run once, local backend)
`-- live/<cloud>/<stage>/ # App Terragrunt stacks (one state per folder)
|-- networking/
|-- identity/
|-- dns/
`-- aks/ # depends on networking + identity
Resource names are generated by the label module from project + stage + region (in
env.hcl) - nothing is hardcoded.
Install locally (with your tool manager of choice, e.g. asdf / mise, or manually):
| Tool | Version |
|---|---|
| OpenTofu | 1.9.0 |
| Terragrunt | 0.69.0 |
| TFLint | 0.52.0 |
| Trivy | latest |
| Checkov | latest |
CI pins its own versions in each workflow's
env:block - keep them in sync.
make tools # prints installed tofu / terragrunt / tflint versions
# Authenticate to your cloud and export the credentials the provider needs
# (e.g. the subscription/account id):
export ARM_SUBSCRIPTION_ID="<SUBSCRIPTION_ID>"iac/bootstrap/<cloud>/<stage> creates the resource group + state Storage Account + container
(composing the resource-group, storage-account, storage-container modules) on a local
backend. It lives outside live/ and is a one-time, run-locally step (its local state is
not persisted in CI). Everything else uses the remote backend it creates.
export ARM_CLIENT_ID=... ARM_CLIENT_SECRET=... ARM_TENANT_ID=... ARM_SUBSCRIPTION_ID=...
make bootstrap # apply iac/bootstrap/azure/dev (once, locally)After bootstrap, the app stacks (networking, identity, dns, aks) reference the resource group
by name (generated by label), not via a state dependency - so CI never touches the bootstrap.
Since the bootstrap lives outside live/, the CI run-all over live/ only sees the app stacks.
make help # list targets
make fmt-check lint # formatting + tflint
make bootstrap # one-time: RG + state storage (local backend)
make plan-all # plan the app stacks
make UNIT=aks plan # plan a single stack
make apply-all # apply the app stacks
make trivy checkov # security + complianceThree workflows in .github/workflows/, chained with workflow_run (Azure service-principal).
Only the affected cloud/stage combos run, and apply promotes test -> dev -> prod with a
manual gate:
PR -> detect -> validate -> test (plan comments on PR)
push main -> detect -> validate -> test -> plan -> apply-test -> apply-dev -> apply-prod (manual approval)
iac-validate.yml:detect(affected combos) ->validate(make fmt-check+make lint+ Trivy + Checkov +make validate-all) ->test(make test-all).iac-plan.yml: runsmake plan-allfor the affected combos; on PRs comments the plan.iac-apply.yml:apply-test, thenapply-dev, thenapply-prod(needs: apply-dev,environment: prod-> manual approval). Only onmain.
The CI calls the same make targets used locally (single source of truth); only Trivy and
Checkov run as dedicated marketplace actions. Full details (affected detection, promotion,
multi-cloud) in .github/workflows/README.md.
- New
cloud/stagecombos light up automatically once theiriac/live/<cloud>/<stage>/stacks are created - no workflow edits needed for a new stage of an existing cloud.- Set up Environments
test,devandprod(Settings -> Environments); add required reviewers toprodfor the manual gate.workflow_runworkflows run with the definition from the default branch (main).
terraform_binary = "tofu"in the root and in the bootstrap stack (which does not include the root).- Naming via the
labelmodule - every resource name is generated fromproject+stage+region. The state backend names (RG + Storage Account) are reproduced in the root with the same convention, since the Terragruntremote_stateblock cannot call a Terraform module. - Resource group create-or-reuse:
create_resource_group+resource_group_nameinenv.hcllet you create a new RG or reuse an existing one (e.g. when the SP only has rights on that RG). - Backend bootstrap outside
live/(iac/bootstrap/, local backend), run once - see Bootstrap. - Identity -> AKS: the cluster consumes the
identitymodule's managed identity and is granted the network role on the subnet (CNI requirement). - Tests run as the CI
testjob (make test-all, onetofu testper module) - not as apply hooks, to keep module tests isolated from live inputs.