
Sam Gabrail
President at TeKanAid
Terraform CLI is a command-line interface tool that enables you to manage Infrastructure as Code (IaC) using the HashiCorp Configuration Language (HCL). With Terraform CLI, you can define, provision, and manage infrastructure resources in a human-readable format that can be versioned, reused, and shared across teams.
Let’s take a look at our setup.
TL;DR: You can find the repo here.
Terraform CLI offers several benefits, including:
To install Terraform CLI, follow the instructions for your operating system by clicking on the appropriate tab in the documentation. If you follow along with our guide using GitHub codespaces, you will have the Terraform CLI already installed for you.
Once Terraform CLI is installed, you can start configuring it by creating a new directory for your configuration and initializing the directory with the terraform init command. We will see this in action shortly.
We will use a simple example to demonstrate the terraform CLI commands. The example pulls an NGINX docker image from Dockerhub and runs an NGINX container. Below are the three configuration files used.
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "3.0.0"
}
}
}
provider "docker" {}
resource "docker_image" "nginx_image" {
name = "${var.image_name}:${var.image_tag}"
}
resource "docker_container" "nginx_container" {
name = "webserver-${terraform.workspace}"
image = docker_image.nginx_image.image_id
ports {
internal = var.internal_port
external = terraform.workspace == "prod" ? var.external_port_prod : var.external_port_dev
}
}
variable "image_name" {
type = string
description = "The name of the Docker image"
}
variable "image_tag" {
type = string
description = "The tag of the Docker image"
}
variable "internal_port" {
type = number
description = "The internal port number for the container"
default = 80
}
variable "external_port_dev" {
type = number
description = "The external port number for the container"
default = 8090
}
variable "external_port_prod" {
type = number
description = "The external port number for the container"
default = 8080
}
image_name = "nginx"
image_tag = "1.23.3"
internal_port = 80
external_port_dev = 8090
external_port_prod = 8080
outputs.tf
output "container_id" {
value = docker_container.nginx_container.id
description = "The container ID"
}
Terraform CLI offers a variety of commands for managing infrastructure resources. To view every terraform command available, you can simply run terraform or terraform -h. Here are the main commands:
Let's go ahead and run terraform init to initialize our directory.
[.code]terraform init[.code]
Output:
As you can see in the output of the terraform init command below are the steps it takes:
Take a look at the file structure after running terraform init. You will see that it creates a hidden directory called .terraform where it installs the providers and terraform modules.
The terraform init command also accepts some options that can modify its behavior, such as -input, -lock, -upgrade, and -from-module. You can use these options to control how Terraform interacts with user input, state locking, provider and module upgrading, and module copying.
The -upgrade flag is the most important to mention here as it allows you to update your providers and modules to the latest compatible versions, or when the versions defined in the lock file conflict with the versions defined in the required_providers block. However, you should use this option with caution, as upgrading providers and modules may introduce breaking changes or unexpected behavior.
Terraform offers the terraform validate command to check the syntax of our configuration files. Let's try it out now.
[.code]terraform validate[.code]
Output:
If all looks good you will get the following message.
Let's introduce an error by commenting out the resource "docker_image" in main.tf like this:
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "3.0.1"
}
}
}
provider "docker" {}
# resource "docker_image" "nginx_image" {
# name = "${var.image_name}:${var.image_tag}"
# }
resource "docker_container" "nginx_container" {
name = "web-server"
image = docker_image.nginx_image.image_id
ports {
internal = var.internal_port
external = var.external_port
}
}
Now run terraform validate again and check the error message below:
You can see the error message letting us know that we referenced an undeclared resource and pointed to the offending line in the main.tf file.
It's a good practice to include the terraform validate command in CI/CD pipelines so the pipeline would fail early on if there is an issue with the Terraform syntax.
Next, we will run the terraform plan command. This terraform command allows us to view the intent of what Terraform is about to do. It's basically a dry run. First, remember to uncomment the resource "docker_image" in main.tf.
[.code]terraform plan[.code]
Output:
The output screenshots demonstrate how terraform plan produces an execution plan, which enables you to review the changes that Terraform will make to your infrastructure. Terraform performs the following steps by default when creating a plan:
Since this is the first time to run terraform plan, we only see that it plans to create two resources with nothing to change and nothing to destroy.
The terraform plan command can be useful for checking whether the proposed changes match what you expected before you apply them or share them with your team for broader review.
The terraform plan command also supports multiple flags that let you modify its behavior, such as -target, -refresh-only, -destroy, etc. To learn more about terraform plan, check out our detailed blog post on terraform plan.
Once we're satisfied with what is about to get provisioned, we can go ahead and run the terraform apply command.
terraform apply
Output:
When you run terraform apply, Terraform always runs terraform plan first. Then you will get a prompt to accept the changes. You need to enter yes and as shown in the screenshot above, Terraform will create the resources requested in the configuration files.
Now you can see our NGINX container running on port 8090 and we're greeted with the standard NGINX welcome page shown below.
To summarize, when you run terraform apply, Terraform will:
You can use the terraform apply -auto-approve option to skip the prompt which is helpful in CI/CD pipelines. However, a peer review approval step needs to be taken upstream.
You can also use other options to modify the behavior of the apply command, such as -lock, -parallelism, -destroy, etc
The terraform state file is a file that Terraform uses to store information about your managed infrastructure and configuration. This state file keeps track of resources created by your configuration and maps them to real-world resources.
It is written in JSON format and it records the identity of remote objects against a particular resource instance, and then potentially updates or deletes that object in response to future configuration changes.
The terraform local state file is saved by default in a local file named "terraform.tfstate" at the root of your project. However, you can also use a remote state stored in something like an S3 Bucket, which is important to do when collaborating with a team.
Below is our file structure after running the terraform apply command. Notice two new files appear: terraform.tfstate and terraform.tfstate.backup
Here is a portion of the terraform.tfstate file:
{
"version": 4,
"terraform_version": "1.5.0",
"serial": 9,
"lineage": "c4728c78-1e32-5b26-0aec-93cf5970d73a",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "docker_container",
"name": "nginx_container",
"provider": "provider[\"registry.terraform.io/kreuzwerker/docker\"]",
"instances": [
{
"schema_version": 2,
"attributes": {
"attach": false,
"bridge": "",
"capabilities": [],
"cgroupns_mode": null,
"command": [
"nginx",
"-g",
"daemon off;"
],
"container_logs": null,
"container_read_refresh_timeout_milliseconds": 15000,
"cpu_set": "",
"cpu_shares": 0,
"destroy_grace_seconds": null,
"devices": [],
…truncated
The terraform.tfstate.backup file is used to show a previous version of the state file in case we need to revert back to it.
A handy terraform command used when you need to build an environment for testing and to tear it down after is the terraform destroy command. Let's go ahead and try it out.
[.code]terraform destroy[.code]
Output:
The terraform destroy command is the inverse of terraform apply in that it terminates all the resources specified in your Terraform state file. It does not destroy resources running elsewhere that are not managed by the current Terraform project.
When you run terraform destroy, Terraform will:
You can use the -auto-approve option to skip interactive approval of the plan, but it's definitely not recommended in a production environment. You can also use the command terraform apply -destroy with similar results. This command is helpful in CI/CD pipelines.
The terraform console command provides an interactive console for evaluating and experimenting with expressions. You can use it to test interpolations before using them in configurations and to interact with any values currently saved in the state. The terraform console command does not modify your state, configuration files, or resources.
When you run terraform console, Terraform will:
Let's try it out.
[.code]terraform console[.code]
To close the console, enter the exit command or press Control-C or Control-D.
The terraform fmt command is very useful to format our configuration files. When you run terraform fmt, Terraform will:
The terraform taint command informs Terraform that a particular object has become degraded or damaged. Terraform represents this by marking the object as "tainted" in the Terraform state, and Terraform will propose to replace it in the next plan you create.
The terraform taint command can be useful for:
When you run terraform taint, Terraform will:
Warning: This command is deprecated. The recommendation is to use the -replace option with terraform plan and apply instead. Using the -replace option gives you more visibility into the impact of the change before you apply it. With taint, you risk having another person run a new plan on your tainted object without your knowledge of the outcomes.
The terraform workspace command is used to manage workspaces. Workspaces are named containers for Terraform state that allow you to isolate and switch between different configurations and environments.
The terraform workspace command has several subcommands:
You can use the terraform workspace to organize your Terraform projects into multiple logical environments, such as development, staging, and production. You can also use workspaces to manage multiple instances of the same configuration with different variables or providers.
Notice our file structure below. Terraform creates a new folder called terraform.tfstate.d and a folder per workspace. Each workspace folder contains its own state file.
We’ve gone through the main Terraform CLI commands, however, there are more. Below is the rest of the available commands as of Terraform version 1.5.0.
To get the most out of Terraform CLI, follow these best practices:
For advanced Terraform CLI usage, consider exploring:
In this blog post, we saw how to use the various Terraform CLI commands available to us today. We also discussed some best practices and explored advanced Terraform CLI usage. By following these best practices and usage patterns, you can effectively manage your infrastructure resources across your hybrid cloud environments.
Use custom workflows to model any process
Visualize all IaC changes pre and post-deployment
Gain code-to-cloud visibility and governance
Improve developer experience and collaboration
Use custom workflows to model any process
Visualize all changes pre and post-deployment
Gain code-to-cloud visibility and governance
Improve developer experience and collaboration
env0 is the best way to deploy, scale, and manage your Terraform and other Infrastructure as Code tools.