Bare Terraform + GitHub Actions (one file per tenant)

Tags: Quickstart · Substrate · Automation · Self-host Substrate: bare Terraform + GitHub Actions

The default-shipping integration for the silent majority of Terraform shops — teams running terraform apply from a GitHub Actions workflow with state in S3, no fancy tooling. If that's your stack, this is your starting point.

The pattern: each tenant is one file under tenants/. Terraform iterates over the directory with for_each. Adding a tenant = adding one file. Atomic adds, no merge conflicts on simultaneous onboards, trivial to reason about.

What you'll see

OnboardTenant runs least-loaded placement over the prod-clusters cell, creates a Tenant entity in provisioning, then the pull-request trigger commits a single file tenants/acme.tfvars.json to a branch and opens a PR. A reviewer merges it; the customer's existing GitHub Actions workflow runs terraform apply (it sees the new file under tenants/ and runs for_each). The reference terrantula-tf-dispatch.yml workflow signals back to Terrantula on completion, and the tenant transitions to active.

The customer's review workflow, CI workflow, state file, and AWS credentials don't change. Terrantula adds placement + constraint enforcement + lifecycle tracking + visibility; the IaC update flows through the normal PR mechanism. The workflow applies; Terrantula never runs terraform apply.

Migrating from a single

tenants.tfvars.json If your repo holds all tenants in one file, you don't have to refactor. The pull-request trigger supports a json-array-appendpatch that inserts into a JSON array at a pointer, leaving the rest of the file untouched.

Try it

run-demo.sh boots a local stack, applies this directory's manifests, and exercises OnboardTenant. Without a real GitHub token the PR step fails with a 401 (expected) — placement, lifecycle, and schema all still validate.

bash examples/bare-tf-gh-actions-onefile-per-tenant/run-demo.sh

With a real PR:

export GITHUB_TOKEN=<your-pat-with-repo-scope>
export GITHUB_TARGET_REPO=<your-org>/<your-fork>
bash examples/bare-tf-gh-actions-onefile-per-tenant/run-demo.sh

Setup for this integration is short:

  1. Drop .github/workflows/terrantula-tf-dispatch.yml into your repo.
  2. Add a GitHub secret with the Terrantula callback token.
  3. Apply the Terrantula blueprint.
  4. Fire your first OnboardTenant.

Key files

FileWhat it is
blueprint.yamlThe Terrantula schema + OnboardTenant action (self-contained).
cluster-seed.yamlTwo TenantCluster entities for the demo's cell.
run-demo.shThe end-to-end runner (local stack + assertions).
terraform/main.tfThe TF module that iterates over tenants/ with for_each.
tenants/.gitkeepThe directory Terrantula's PR commits one file per tenant into.
.github/workflows/terrantula-tf-dispatch.ymlThe reference workflow Terrantula dispatches to.

View on GitHub

examples/bare-tf-gh-actions-onefile-per-tenant