What the terraform CLI is, and how to actually use it
The Terraform CLI is a remarkably complete tool for a single engineer running commands against a single state file. However, the CLI has no idea who else might be running a command against your infrastructure at the same moment, which causes problems once a team grows past one person.
init, plan, apply, state, workspace, console, and a few more) with real examples.Every Terraform engineer starts in the same place: a terminal, a terraform binary, and a handful of commands that do almost everything day to day.
This article walks through what the terraform CLI, or Terraform Command Line Interface, actually is, how to install it properly across macOS, Windows, and Linux, and the commands worth knowing beyond init, plan, and apply, with real, runnable examples.
We also cover what happens to that same workflow once more than one engineer, or more than one pipeline, starts running Terraform commands against the same infrastructure.
The CLI's design doesn't change to accommodate that, so knowing exactly where it stops accommodating is useful for any team.
What is the Terraform CLI?
The terraform binary is the client. It reads your configuration files, talks to whichever providers your configuration declares, and reads and writes the state that tracks what it's already created.
That's the whole job description, and it's worth being precise about it: "Terraform" gets used loosely to describe several different things at once.
HCP Terraform (the commercial platform HashiCorp sells, formerly branded Terraform Cloud) is not the CLI. It's a hosted service that can run the CLI's plan and apply steps for you, store your state remotely, and add a run queue, policy checks, and a web UI on top of it.
You can use the CLI without ever touching HCP Terraform, and plenty of teams do exactly that, pointing init at a self-managed backend instead.
Providers aren't the CLI either.
Each provider (hashicorp/aws, hashicorp/google, and so on) is a separate plugin binary the CLI downloads during init and talks to over a defined protocol. The CLI orchestrates; the provider does the actual work of calling a cloud API.
Observation
It's important to understand that boundary once you start debugging: a plan that fails oddly is more often a provider bug or a stale schema than a bug in the CLI itself.
How to install the terraform CLI
Installation itself is intentionally basic. HashiCorp ships Terraform as a single, statically linked binary named terraform, distributed through the usual package managers as well as a direct download.
There are some differences between operating systems:
- On macOS, Homebrew is the fastest path:
brew tap hashicorp/tap, thenbrew install hashicorp/tap/terraform. - On Windows, Chocolatey handles it with
choco install terraform, though the package isn't maintained by HashiCorp directly, so it's worth checking which version you need. - On Debian or Ubuntu, add HashiCorp's apt repository and GPG key, then run
sudo apt-get install terraform; RHEL, Fedora, and Amazon Linux follow the equivalent yum or dnf pattern against HashiCorp's own repository.
If none of that fits your setup, download the appropriate zip for your operating system, unzip it, and drop the terraform binary somewhere on your PATH.
Once it's installed, terraform version (or the older -version flag) confirms both the installed version and whether a newer release is available, assuming you haven't disabled that check by setting CHECKPOINT_DISABLE.
The install step that is most important for any team is to make sure everyone ends up on the same version.
A version manager like tfenv or tfswitch reads a version file and switches binaries per project, which closes the gap between "works on my machine" and a shared, reproducible toolchain.
Terraform CLI commands and workflow essentials
The workflow that forms the basis of almost every Terraform CLI session runs through the same handful of commands, in roughly the same order: init, validate, plan, apply, and occasionally destroy.
Here's what each one actually does, against a small, realistic example.
terraform init runs first in any new working directory. It downloads the aws provider, sets up the backend, and writes a dependency lock file recording the exact provider versions it resolved. It's safe to run repeatedly; init never touches real infrastructure.
terraform validate checks that the Terraform configuration is internally consistent (references resolve, types match, required arguments are present) without contacting AWS at all. It's the cheapest possible place to fail, and it belongs before plan in the pipeline.
terraform plan is where the CLI actually reasons about your infrastructure: it refreshes what it knows about existing resources, diffs that against the configuration, and prints an execution plan describing what it would create, change, or destroy. Running terraform plan -out=web.tfplan saves that exact plan to a file rather than only printing it.
terraform apply web.tfplan then applies precisely that saved plan, no re-planning, no surprises from something that changed in the meantime. Run terraform apply on its own, and Terraform generates a fresh plan and asks for confirmation before it touches anything.
terraform destroy runs the whole thing in reverse: a plan to remove everything the current configuration manages, gated behind the same confirmation step apply uses.
Working with state, workspaces, and outputs
The state file is what makes the second and third commands above possible at all. Terraform writes terraform.tfstate locally by default (a JSON document mapping every resource address to the real-world object it corresponds to), though any team beyond one person should be pointing at a remote backend instead, such as an S3 bucket, a GCS bucket, an Azure blob container, or HCP Terraform.
A local state file works fine for a single engineer, but stops working the moment a second person or a CI runner needs the same state and doesn't have a copy of that file sitting on disk.
terraform state list shows every resource instance the current state is tracking, with no risk of changing anything. terraform state show aws_instance.web prints that resource's full attributes as Terraform understands them at that point in time, usually the fastest way to confirm what Terraform believes about a resource before digging through the cloud console instead.
terraform state mv and terraform state rm do write to state (renaming an address or handing a resource off without destroying it), so treat them with more care than the read-only pair above.
Workspaces let a single configuration maintain more than one state file, which is useful for near-identical environments that differ mainly in variable values: terraform workspace new staging, then terraform workspace select staging to switch back and forth, or terraform workspace show to confirm the current workspace before running anything destructive.
terraform output prints the output values your root module exposes; add -json when a downstream script or CI step needs to consume them in machine-readable JSON format rather than parsing human-readable text.
A few more commands worth knowing
Beyond that core loop, a handful of other terraform CLI commands earn a place in regular use:
terraform fmtrewrites your Terraform configuration files into HashiCorp's canonical style; add-checkin CI to fail the build on unformatted Terraform code instead of silently reformatting it.terraform consoleopens an interactive console for testing expressions against your current state and configuration, for example> join(",", ["staging", "production"]), without running a full plan.terraform providerslists every provider your configuration requires and the versions it resolved. Adding-chdir=environments/productionbefore any command lets you run it against a different working directory without a literal cd first, handy when a script needs to target several environments in turn.- On bash or zsh,
terraform -install-autocompletesets up shell tab completion for every subcommand and most of their flags.
That's the shortlist of the commands you'll encounter most often. For every terraform CLI command mapped to what it does to state and whether it acquires a lock, Stategraph's terraform commands cheat sheet covers them all.
Why the CLI's single-state model breaks down at enterprise scale
None of this is a criticism of the CLI so much as a description of what it was built to do.
A CLI is a client: it runs a command, reasons about the state it can see at that moment, and exits. It has no persistent process watching your infrastructure, no notion of who else is holding a state lock right now, and no memory between invocations beyond whatever it wrote to state.
That design is invisible for one engineer working alone. It stops being invisible the moment a second engineer, or a CI pipeline, runs a command against the same state.
Lock Granularity Principle
terraform apply acquires a lock on the entire state file before it writes anything, and that lock has no concept of overlap: an engineer rotating an IAM policy and a pipeline scaling an ECS service both queue behind the same lock, even though their changes touch nothing in common.
The other half of the same design shows up as configuration drift. Terraform only knows what a given plan tells it, at the moment that plan runs. If someone changes a security group rule by hand in the console on a Tuesday and nobody runs plan again until Friday, the CLI has no way to have noticed it on Wednesday. Standing awareness of your infrastructure isn't a gap in any individual command; it's simply outside the scope of what a CLI, by definition, does.
A plan diff, however accurate, doesn't tell a reviewer the blast radius of a change beyond the lines sitting in front of them. Forty resources in a plan output look the same on the page whether those forty are inert tags or the load-bearing dependencies of three other services.
Keeping terraform versions and providers consistent across a team
Version drift is the quieter version of the same underlying issue: nothing in the CLI itself enforces that everyone is running the same one.
required_version stops init from proceeding on a Terraform binary outside the range you've declared, which catches the obvious case (someone on a six-month-old CLI) before it produces a confusing plan.
Implementation Detail
The dependency lock file that init writes goes further: it pins the exact provider versions and checksums resolved the last time someone ran init against this configuration, so two engineers on the same commit resolve the same providers rather than whatever the version constraint happens to allow that week. Commit that lock file.
terraform init -upgrade is the explicit, occasional exception, run deliberately when newer provider or module versions are actually wanted, not a routine step.
None of that solves the harder version of the problem: enforcing the same CLI version and the same committed lock file across every engineer's laptop and every CI runner, without a central point that can see who's drifted.
required_version catches drift once someone runs a command, but it doesn't prevent the drift from existing in the meantime, or tell anyone it happened.
Where graph-aware tooling extends the CLI
Every gap named above (lock granularity, drift between runs, blast radius past the plan diff, etc.) is a property of running the CLI directly against a flat state file. None of it requires you to replace the CLI. You just require a different place to store and coordinate state.
That's the gap Stategraph Velocity is built to close.
It stores your infrastructure's dependency graph in PostgreSQL instead of a flat JSON file, and it plans and applies against that graph using the same Terraform and OpenTofu binaries you already have installed underneath, so the commands covered earlier don't change.
Because the graph is explicit rather than reconstructed from scratch on every run, Stategraph can check for conflicts at the level of an individual resource instead of the whole file, so an engineer changing a security group and a pipeline resizing an autoscaling group can plan and apply at the same time, with a conflict only surfacing if their changes actually overlap.
A change that spans more than one state file gets the same treatment through stategraph tf mtx, which plans and applies multiple states as one atomic transaction. Plus, because state lives in a queryable database rather than a file nobody reads until the next plan, Stategraph Insights runs blast radius analysis directly against the graph instead of a plan diff, while Orchestration adds scheduled drift scans that catch a manual change on a fixed cadence instead of waiting for someone to happen to run plan again.
This infrastructure is built for teams who've already outgrown running the plain CLI solo or in a small, uncontended environment. If one engineer, one state file, and the occasional plan and apply already cover your week, the CLI on its own is doing exactly what it's supposed to, but you could consider Orchestration if you want to lose some limits.
Conclusion
The terraform CLI is the same tool whether you're running it against a single S3 bucket for a side project or as part of a pipeline touching thousands of resources: one binary, five core commands most of the time, and a state file that becomes the most important artifact in the whole workflow.
Installing it takes a minute. Getting comfortable with init, plan, apply, and the state and workspace commands takes an afternoon.
Knowing exactly where that comfort runs out (the lock that doesn't know about overlap, the drift nobody's watching for, or the blast radius a plan diff can't show you) is what separates a team that's outgrown the CLI's design from one that hasn't yet.
If you're in the first group, try Stategraph free and see the same plan and apply commands running against a graph instead of a flat file.
Terraform CLI FAQs
Is the terraform CLI free to use?
Yes. The terraform binary itself costs nothing to download and run if you want to use it to build and manage your own infrastructure.
What's the difference between the terraform CLI and HCP Terraform?
The CLI is the binary that runs on your machine or your CI runner. HCP Terraform (HashiCorp's commercial platform and formerly Terraform Cloud) is a hosted service that can execute the CLI's plan and apply steps for you, store state remotely, and add a run queue, policy checks, and a web UI.
You can use the CLI entirely on its own against a self-managed backend.
Does OpenTofu use the same CLI commands as Terraform?
Yes, by design. OpenTofu is an open-source fork of Terraform that aims to stay compatible with existing configurations and workflows, so init, plan, apply, and the rest of the commands covered here work the same way against the tofu binary. The main practical difference day to day is which binary name a script or CI job calls.
Can I use the terraform CLI without creating an account?
Yes. Nothing about init, plan, apply, or any other core command requires an account or a login. An account only becomes relevant if you choose to use HCP Terraform for remote state or remote runs, in which case terraform login handles authentication.