Skip to main content

erun terraform

Run a hosted platform's per-environment Terraform without hand-running terraform or cd-ing into a folder. erun terraform is for platform deployments whose Terraform is laid out per environment — one folder per env under terraform-<tenant>/, scaffolded by the erun-blueprint-platform skill. erun resolves the env's root from the current scope — terraform-<tenant>/<environment>/ at the project root, or <tenant>-devops/terraform-<tenant>/<environment>/ when the tenant keeps its whole devops footprint (docker/, k8s/, terraform-<tenant>/) under <tenant>-devops/ (the same -devops convention build/deploy use) — picks up the symlinked common.tf, and runs that env's own main.tf with its <environment>.tfvars. The terraform-<tenant> base is the default; relocate it with paths.terraform in .erun/config.yaml (erun still appends /<environment>).

Terraform's mutable artifacts live off the playbook tree: erun keeps the local-backend state file, the plan file, and TF_DATA_DIR (downloaded providers/modules) under ~/.erun/terraform/<tenant>/<environment>/ on the durable home directory (the /home/erun PVC in a runtime pod). State and the provider cache therefore survive a pod restart, while the image-baked playbooks stay read-only. The one file Terraform insists on keeping next to main.tf is .terraform.lock.hclerun terraform init generates it (you commit it) so a read-only runtime env never has to write it.

erun terraform init frs prod # once: download providers + record the provider lock
erun terraform apply frs prod # fmt → plan → confirm → apply
erun terraform plan frs prod # read-only: plan
erun terraform destroy frs prod # plan a destroy → confirm → apply
erun terraform apply frs prod --dry-run # preview the resolved terraform commands

With no TENANT/ENVIRONMENT arguments, the command resolves the configured default scope. Run init once per environment (and again after you change providers) before plan/apply/destroy — they no longer init implicitly, matching how you run Terraform by hand.

What it does

TF_DATA_DIR is set to ~/.erun/terraform/<tenant>/<env>/data for every step, so downloaded providers and modules persist on the durable home directory across runs and restarts.

Run erun terraform init <tenant> <env> once first (and again after you change providers). It points the local backend at the durable state file (-backend-config=path=~/.erun/terraform/<tenant>/<env>/terraform.tfstate) and downloads providers into TF_DATA_DIR:

  • On a writable checkout it generates .terraform.lock.hcl and records provider hashes for linux/amd64 and linux/arm64 (erun's deploy targets, via terraform providers lock). Commit that lock so it bakes into the runtime image — then a read-only runtime env can initialize from it. A single committed lock works on every env because it covers both deploy architectures.
  • On a tree that already has a committed lock it runs terraform init -lockfile=readonly, refreshing only the PVC provider cache and never rewriting the (read-only) tree.

erun terraform apply <tenant> <env> then resolves the env's root (see above) and runs Terraform in that folder, so the env picks up its symlinked common.tf and adds its own services via main.tf — with no implicit init:

  1. terraform fmt -check -recursive ..verifies formatting across the tree without rewriting it (the playbooks are read-only in a runtime release); a formatting drift fails the step.
  2. terraform plan -input=false -var-file=<env>.tfvars -out ~/.erun/terraform/<tenant>/<env>/apply.tfplan — using the env's var file when present.
  3. Confirm: you are prompted to type the environment name before anything is applied — the guard against applying to the wrong environment. A wrong or empty entry aborts before the apply, so nothing is applied unless the name matches.
  4. terraform apply -input=false ~/.erun/terraform/<tenant>/<env>/apply.tfplan — applies the exact plan you reviewed.

plan stops after step 2 (read-only — no fmt, no -out, no confirm). destroy plans a -destroy and applies it behind the same confirm gate. All three assume init has already run; if it hasn't, Terraform stops with a "run terraform init" error.

When the env has a Cloudflare alias, its CLOUDFLARE_API_TOKEN is forwarded to Terraform as TF_VAR_cloudflare_api_token (so the edge module's cert-manager DNS-01 solver can prove control of the zone). The token rides in the environment — it never appears in the command line or the trace.

When the env's <env>.tfvars sets dns01_provider = "powerdns-rfc2136", erun reads the cluster-edge module's own RFC2136 TSIG secret back from its Kubernetes Secret and forwards it as TF_VAR_rfc2136_tsig_secret — you never export or type it yourself. Before running any terraform command, erun checks that the Secret exists and holds a value; if it doesn't, the command aborts up front naming the missing Secret and key, instead of letting Terraform print a partial plan and fail its own precondition mid-run.

Flags

FlagDescription
--tenant <name>Target a specific tenant (defaults to the current scope).
--environment <name>Target a specific environment (defaults to the tenant's default).
--confirm-environment <env>Restate the environment name to confirm, bypassing the interactive prompt — for non-interactive use. apply/destroy only.
--dry-runResolve and print the full sequence of terraform commands without running any of them.

Error behaviour

ConditionWhat happensRecover
Not in a project (no git repo found on the host)Aborts: cannot find git project; exit 1.Run from inside your project checkout. This doesn't occur inside a runtime pod, where erun resolves the project tree automatically even though it has no .git.
No Terraform root at either candidateAborts: no Terraform root for <tenant>/<env> — looked under terraform-<tenant>/<env>/ and <tenant>-devops/terraform-<tenant>/<env>/ …; exit 1.Scaffold the per-env root with erun-blueprint-platform, or create it at either location with its main.tf and <env>.tfvars.
Environment not configuredSurfaces the config load error; exit 1.Create the env (erun init) or fix the tenant/env name.
No default tenant/environment and none passedAborts: default tenant/environment not configured; exit 1.Pass TENANT ENVIRONMENT (or set a default scope).
Confirmation doesn't match the env nameAborts before the apply step: confirmation "…" does not match environment "…"; aborting <operation>; exit 1. The earlier read-only steps (plan, plus fmt for apply) have already run; only the apply is gated.Re-run and type the exact environment name (or pass --confirm-environment <env>).
Providers not initialized (init not run)A plan/apply/destroy terraform step stops with Terraform's "Module not installed / please run terraform init" error; exit 1.Run erun terraform init <tenant> <env> first — once per environment, and again after changing providers.
init on a read-only tree with no committed lockerun terraform init aborts before running Terraform: the playbook tree is not writable and has no .terraform.lock.hcl to init read-only from; exit 1.Run erun terraform init on a writable env (e.g. <tenant>-local), commit the generated .terraform.lock.hcl, and rebuild/redeploy so it bakes into the image.
dns01_provider is powerdns-rfc2136 but its RFC2136 TSIG Secret is missing or emptyAborts before any terraform command runs, naming the Secret and key it looked for; exit 1.Apply once with the TSIG secret supplied directly (see erun-enable-hosting-edge) so the module materializes its Secret, then re-run.
A terraform step fails (live run)Surfaces the underlying terraform error and stops at that step; exit 1.Fix the reported Terraform/cloud issue and re-run — init/plan/apply are safe to repeat.

See also