Terraform Execution¶
Purpose¶
The exact commands for every Terraform lifecycle stage, clearly marked by how safe/reversible each one is - and an explicit statement of which ones this documentation portal (and any automated process building it) will never run against shared infrastructure.
Safety classification¶
| Command | Touches remote state? | Touches real infrastructure? | Needs credentials? |
|---|---|---|---|
terraform fmt -check |
No | No | No |
terraform validate -backend=false |
No (skips backend init) | No | No |
terraform init |
Yes - reads/creates the state file | No | Yes (state backend credentials) |
terraform plan |
Yes - reads state | No (plan-only, makes no changes) | Yes |
terraform output |
Yes - reads state | No | Yes |
terraform apply |
Yes | Yes | Yes |
terraform destroy |
Yes | Yes - destructive | Yes |
terraform fmt - safe, no credentials¶
# Linux/Bash, containerized (no local terraform binary required)
docker run --rm -v "$(pwd)/terraform:/tf" -w /tf hashicorp/terraform:1.9 fmt -recursive -check -diff
-diff shows exactly what would change.
Drop -check to actually reformat in place.
terraform validate - safe, no credentials, no state¶
docker run --rm -v "$(pwd)/terraform:/tf" -w "/tf/environments/staging" hashicorp/terraform:1.9 init -backend=false -input=false
docker run --rm -v "$(pwd)/terraform:/tf" -w "/tf/environments/staging" hashicorp/terraform:1.9 validate
-backend=false on init is what keeps this safe - it downloads providers/modules for syntax/
type checking only, never contacts the real state backend, never needs real credentials. Mount the
whole terraform/ tree (not just one environment directory) so relative module paths like
../../modules/hetzner-network resolve.
# PowerShell equivalent
docker run --rm -v "${PWD}\terraform:/tf" -w "/tf/environments/staging" hashicorp/terraform:1.9 init -backend=false -input=false
docker run --rm -v "${PWD}\terraform:/tf" -w "/tf/environments/staging" hashicorp/terraform:1.9 validate
terraform init (real) / terraform plan / terraform output - need real credentials¶
These read the real state backend and, for plan, compute what would change - plan itself
makes no changes. Requires AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY (R2's S3-compatible API
keys - see Cloudflare) and the environment's own terraform.tfvars or
TF_VAR_* environment variables. Not run as part of building this documentation portal - only
by whoever is deliberately planning a real infrastructure change.
terraform apply - destructive potential, real infrastructure change¶
Provisions/modifies real Hetzner VMs and Cloudflare resources, and costs real money. Never run
by this documentation portal, or by any automated process, against platform, staging, or
production. A human reviews the plan output first, every time.
terraform destroy - always destructive, marked clearly¶
⚠️ Deletes real infrastructure - VMs, DNS records, R2 buckets (and, for a data VM, the MySQL/
Redis data on it, unless backed up first - see Operations → Backup).
Never run against any shared environment casually; never run by this documentation portal under
any circumstance.
State handling¶
Backend: Cloudflare R2, via Terraform's S3-compatible backend "s3" support - one state key
per environment root inside one bucket, created once by terraform/bootstrap/ (see
Environments).
Never commit:
Already enforced byterraform/.gitignore. State contains resource attributes derived from
sensitive variables (MySQL/Redis passwords end up in a data VM's rendered cloud-init user_data,
which state records) - the R2 bucket holding it must be restricted to the same people/CI who'd
otherwise have those secrets directly. No state locking is configured (R2 has no DynamoDB-style
locking equivalent as of this writing) - avoid two people/pipelines running apply against the
same root concurrently.
Secret handling¶
No credential is ever a literal in any .tf/.tfvars file committed to this repository - every
sensitive variable (hcloud_token, cloudflare_api_token, mysql_root_password,
mysql_app_password, redis_password) is sensitive = true with no default, supplied via a
gitignored terraform.tfvars (copy from the committed terraform.tfvars.example, which holds
only <CHANGE_ME>-style placeholders) or TF_VAR_<name> environment variables (preferred for
CI/Jenkins).
cp terraform/environments/staging/terraform.tfvars.example terraform/environments/staging/terraform.tfvars
# edit terraform.tfvars with real values - it is gitignored, never committed
What this portal actually runs¶
Only terraform fmt -check and terraform validate -backend=false - both credential-free and
state-free - are run as part of building/validating this documentation. Anything beyond that is
explicitly out of scope here; see Terraform overview "Safe commands only".
Related pages¶
- Environments
- Hetzner · Cloudflare
- Operations → Rollback "Terraform rollback"