Skip to main content
RunBook Academy

TerraformIII · Installing and Versioning TerraformProduction Terraform

Verifying the Installation

Foundation⏱ ~10 minbash

What you'll learn

  • Run the canonical post-install checks: terraform version, autocomplete, provider resolution
  • Distinguish a healthy install from one that is technically present but operationally broken
  • Set up a CI gate that fails the build when the Terraform binary is wrong
  • Define an audit cadence that detects drift between hosts over time
  • Recognise the symptoms of a partially-installed or tampered Terraform

Prerequisites

None — start here.

Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-13

Not yet marked complete on this device.

A binary being present in $PATH is not a verified install. It is the first check of many. A production install is verified when we have evidence that the binary is the version we expect, that it can resolve the providers the configuration requires, that it can reach the state backend, and that the same evidence can be produced by a CI runner on every commit. This lesson is the checklist.

The four checks

A verified install is the conjunction of four independent checks.

+--------------------+ +--------------------+ +--------------------+ +--------------------+
| Binary check       | | Autocomplete check | | Provider check     | | Backend reachability|
| - terraform version| | - autocomplete     | | - terraform init   | | - curl / API call  |
| - hash             | | - shell hooks      | | - provider mirrors | | - IAM role / token |
+--------------------+ +--------------------+ +--------------------+ +--------------------+

Run them in this order. A failure in an earlier check usually makes later checks meaningless.

1. The binary check

# READ-ONLY
terraform version
Terraform v1.9.8
on linux_amd64

What to confirm:

  • The version string matches the team’s pin. A 1.9.8 binary is fine when the team is on 1.9.8. A 1.10.0 binary in a 1.9.x fleet is a rollout failure.
  • The architecture line is correct. linux_amd64 is the production shape on Ubuntu 24.04 and Debian 12. A darwin_arm64 on a Linux server is the wrong binary.
  • The provider line, if present, lists the cached providers. This is informational; it is the next check that confirms the providers actually resolve.

For the standalone binary, a second check is the hash:

# READ-ONLY
sha256sum $(which terraform)

Compare against the upstream SHA256SUMS. A mismatch is an integrity incident.

2. The autocomplete check

# CONFIGURATION: install autocomplete for the current shell
terraform -install-autocomplete
You can now run terraform -install-autocomplete to install autocomplete
for a different shell, or to output the autocomplete script to stdout.

What this does: writes a completion script to the user’s bash or zsh configuration. It is idempotent. A fresh host returns the above message and adds the completion script to ~/.bashrc.

What to confirm:

  • The exit code is zero. A non-zero exit means the install could not write the completion file (typically a permissions issue on ~/.bashrc).
  • The completion is in effect. Open a new shell and type terraform p followed by the Tab key. The completion should offer plan, providers, push.

If the team’s workstations use zsh, run terraform -install-autocomplete once for each shell the team uses. The command supports bash and zsh out of the box.

3. The provider resolution check

A binary that runs is not enough. The CLI must be able to resolve the providers the configuration uses. The check is a minimal configuration and a dry init.

# READ-ONLY: minimal working directory
mkdir -p /tmp/tf-verify && cd /tmp/tf-verify
# /tmp/tf-verify/main.tf
terraform {
  required_providers {
    null = {
      source  = "hashicorp/null"
      version = "~> 3.0"
    }
  }
}

resource "null_resource" "smoke" {
  count = 1
}
# READ-ONLY: confirm the CLI resolves the provider
terraform init -backend=false
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/null...
- Installing hashicorp/null v3.2.3...
- Installed hashicorp/null v3.2.3 (unauthenticated)
Terraform has been successfully initialized!

What to confirm:

  • The init succeeds. A failure here is the most common post-install problem and almost always points at network or mirror configuration.
  • The provider version matches the lock file. If the team’s configuration locks null at 3.2.2 and the install resolves 3.2.3, the install is healthy but the working directory is not.
  • The (unauthenticated) warning is acceptable in this context. It refers to the registry’s signature; in production the mirror should be authenticated, but for a verification smoke test against the public registry, the warning is normal.

Clean up:

# CONFIGURATION: remove the smoke working directory
rm -rf /tmp/tf-verify

4. The state-backend reachability check

The CLI on a host can be perfectly installed and still fail every apply because the backend is unreachable. The check depends on the backend.

S3 backend. The CLI uses the AWS SDK’s credentials chain. The reachability check is whether the SDK can resolve the bucket.

# READ-ONLY
aws s3 ls s3://acme-tf-state-eu-west-1/
2025-09-12 14:02:18 prod
2025-09-12 14:02:18 staging

Self-hosted terraform serve.

# READ-ONLY
curl -fsS -H "Authorization: Bearer $TF_TOKEN" \
  https://tf-state.internal.example.com:9080/_health
{"status":"ok"}

Terraform Cloud.

# READ-ONLY: confirm the saved API token is valid
terraform login

A non-zero exit or an authentication error is the failure to debug, not the install.

The CI gate

The post-install checks are necessary but not sufficient. The team also needs a CI gate that fails the build if the binary is wrong, so a host cannot merge a configuration change while running a different Terraform than the rest of the team.

# .github/workflows/tf-verify.yml (illustrative)
name: tf-verify
on: [pull_request]

jobs:
  verify:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4

      # CONFIGURATION: install the pinned Terraform
      - name: Install Terraform 1.9.8
        run: |
          wget -q https://releases.hashicorp.com/terraform/1.9.8/terraform_1.9.8_linux_amd64.zip
          wget -q https://releases.hashicorp.com/terraform/1.9.8/terraform_1.9.8_SHA256SUMS
          sha256sum -c <(grep linux_amd64 terraform_1.9.8_SHA256SUMS) < terraform_1.9.8_linux_amd64.zip
          sudo unzip -o terraform_1.9.8_linux_amd64.zip -d /usr/local/bin

      # READ-ONLY: verify
      - name: Verify
        run: |
          terraform version
          terraform -install-autocomplete
          terraform init -backend=false
          terraform validate
          terraform plan -input=false

The four commands in the Verify step are the CI gate. A failure on any of them fails the build. The exact commands depend on the backend and the configuration; the principle does not.

The audit cadence

The four checks run at install time. They do not run again automatically. Over weeks and months, a host can drift: an operator upgrades Terraform locally, a CI runner is rebuilt with the wrong image, a new bastion is provisioned with a different mirror. The audit cadence catches the drift.

Daily. The CI gate runs on every pull request. If a host is on the wrong version, it cannot merge a plan.

Weekly. A scheduled job runs terraform version on every host the team owns and reports the result to a central dashboard. The hosts whose version is wrong are the hosts to fix.

Quarterly. A live rollback rehearsal on a non-production host. Pick a host, downgrade to the previous known-good version, run the smoke test, then upgrade back. The point is to confirm that the rollback actually works and that the previous version is retained.

Per release. When HashiCorp ships a new minor, or when the team decides to adopt a new OpenTofu release, the upgrade is a deliberate event: read the changelog, run the upgrade on staging, confirm the dev plan is empty, then roll the team forward.

Production failure modes

1. Binary present but wrong version. Symptom: terraform version returns 1.10.0 on a host the team believes is on 1.9.8. Recovery: downgrade using the standard install method; re-run the audit job to confirm.

2. Autocomplete install fails silently. Symptom: a new engineer cannot get completion working; the bashrc is owned by root because the engineer was provisioned as root. Recovery: fix the home-directory ownership, re-run terraform -install-autocomplete.

3. Provider resolution fails after a private-mirror cutover. Symptom: terraform init fails with “provider not available in the mirror”; the CLI does not fall back to the upstream registry because the direct { exclude } line excludes everything. Recovery: either populate the mirror with the missing provider, or remove the exclude clause for that provider, depending on the team’s policy.

4. CI gate green but production apply fails. Symptom: the CI plan succeeded; the production apply failed on terraform init because the production host’s ~/.terraformrc points at a mirror that does not exist. Recovery: the CI gate must run with the same client configuration as the production hosts. Bake the terraformrc into the CI image.

5. The audit job is ignored. Symptom: the weekly version report shows five hosts on the wrong version for a month; nobody reads the report. Recovery: page on a sustained version mismatch. The audit is only useful if someone acts on it.

6. The smoke test does not match production. Symptom: the smoke test runs against the dev backend; production is on a different backend with different IAM. A host that passes the smoke test fails production. Recovery: the smoke test is a subset, not the whole. The CI plan against staging is the closer match; the production apply is the only true test.

Security and performance

  • Hash verification is part of the binary check. The sha256sum against the upstream SHA256SUMS is the integrity boundary for the standalone binary. The apt repository verifies GPG. Either is fine; the rule is “verify before trust”.
  • Provider cache. Once the providers are cached, init is near-instant. The cache lives in ~/.terraform.d/plugin-cache by default; on a CI runner, point it at a path that survives between jobs.
  • Reachability checks are not free. A curl to a state API on every CI run is cheap; a terraform plan against a real backend on every CI run is expensive. The CI gate should distinguish between the smoke test (every PR) and the full plan (merge to main).

Production guidance

  • Run the four checks on every new host. Bake them into the bootstrap script; do not rely on the engineer remembering.
  • Make the CI gate fail loud. A non-zero exit on any check fails the build. The configuration is not safe to merge if the host cannot verify the install.
  • Audit weekly. A scheduled job that reports every host’s version. Page on a sustained mismatch.
  • Document the rollback. The previous version is retained; the downgrade command is in the runbook; the rehearsal is quarterly.

What comes next

The next lesson covers the team-wide rollout procedure that ties the install method, the version pin, the ~/.terraformrc, and the smoke test into a single change applied to every host the team owns.

Verification

Run the four checks against your own host.

# READ-ONLY
terraform version
which terraform
terraform -install-autocomplete
# READ-ONLY: provider resolution
mkdir -p /tmp/tf-verify && cd /tmp/tf-verify
cat > main.tf <<'EOF'
terraform {
  required_providers {
    null = {
      source  = "hashicorp/null"
      version = "~> 3.0"
    }
  }
}
resource "null_resource" "smoke" { count = 1 }
EOF
terraform init -backend=false
terraform validate
cd /tmp && rm -rf /tmp/tf-verify
# READ-ONLY: backend reachability (example for self-hosted)
curl -fsS -H "Authorization: Bearer $TF_TOKEN" \
  https://tf-state.internal.example.com:9080/_health

A clean output of every command is the verification.

Knowledge check · 7 questions

  1. Q1. What is the FIRST post-install verification?

  2. Q2. What does terraform -install-autocomplete do?

  3. Q3. A terraform version that returns the expected version string is sufficient verification for a production install.

  4. Q4. Which reachability check applies to a self-hosted terraform serve backend?

  5. Q5. Which of the following are part of a healthy post-install verification? (Select all that apply.)

  6. Q6. The CI gate runs terraform version and terraform validate. The production apply fails on terraform init. What is the most likely gap?

  7. Q7. How often should the team audit Terraform version drift across hosts?

Passing score: 75%. Answers are checked in this browser.