Tags: Substrate · Modeling · Automation · Self-host Substrate: bare Terraform + GitHub Actions
The same default-shipping integration as the one-file-per-tenant variant, with a different repo structure and stronger per-tenant isolation. Use this pattern when isolation between tenants matters more than DRY config: each tenant gets its own Terraform module and state file, so a problem with one tenant can't affect another.
OnboardTenant runs placement, creates a Tenant entity in provisioning, then the
pull-request trigger commits a whole directory — tenants/acme/{main.tf,backend.tf,versions.tf}
— in one PR. On merge, the GitHub Actions workflow uses git diff to detect only the new
tenants/acme/ directory and runs terraform init && terraform apply for that tenant alone.
Terrantula transitions the tenant to active when the merge webhook fires.
| One file per tenant | Module per tenant | |
|---|---|---|
| Per-tenant state | Shared (one big state file) | Isolated (one state file per tenant) |
| Version drift | All tenants on the same module version | Each tenant can pin its own version |
| Failure blast radius | A bad apply can affect all tenants | A bad apply affects one tenant |
| PR commits | One file: tenants/<id>.tfvars.json | A directory: tenants/<id>/{main.tf,backend.tf,versions.tf} |
The Terrantula blueprint is structurally identical between the two patterns — only the
pull-request trigger's files array changes. The workflow applies; Terrantula never runs
terraform apply.
Without a real GitHub token the PR step fails with a 401 (expected); placement, entity creation, and lifecycle still validate. With a real token:
To wire your repo, drop in .github/workflows/terrantula-tf-dispatch.yml (it applies only the
changed tenant directory per merge) and add the required secrets:
| Secret | Value |
|---|---|
AWS_DEPLOY_ROLE_ARN | The IAM role to assume for applies (trusts GitHub OIDC). |
AWS_REGION | The AWS region for Terraform operations. |
| File | What it is |
|---|---|
blueprint.yaml | The Terrantula schema + OnboardTenant action. |
cluster-seed.yaml | Two TenantCluster entities for the demo's cell. |
run-demo.sh | The end-to-end demo runner. |
terraform/main.tf | The shared module each tenant directory calls. |
tenants/.gitkeep | Placeholder; per-tenant directories are added by Terrantula PRs. |
.github/workflows/terrantula-tf-dispatch.yml | The workflow that applies only changed tenant dirs (with OIDC). |
pull-request trigger.