Terraform Plan, Apply, and Destroy: A Beginner’s Workflow

Abstract infrastructure resources progressing through preview, deployment, and safe removal stages

What You’ll Learn

Terraform uses a predictable workflow for managing infrastructure: you preview changes with terraform plan, create or update resources with terraform apply, and remove managed resources with terraform destroy.

  • Understand what Terraform plans before making changes.
  • Apply a configuration to create a small development environment.
  • Review and safely destroy resources that are no longer needed.
  • Recognize the role of Terraform state in this workflow.
Ad

The Concept

Terraform is a declarative infrastructure-as-code tool. Instead of writing step-by-step instructions such as “create this file, then configure that service,” you describe the desired result in a Terraform configuration.

Terraform compares your configuration with its state and the resources that currently exist. The three main workflow commands are:

  • terraform plan: Shows what Terraform intends to create, change, or remove. It does not make changes.
  • terraform apply: Executes the proposed changes.
  • terraform destroy: Removes the resources managed by the current configuration.

In this lesson, the development environment is represented by local files. This keeps the example safe to run without cloud credentials or unexpected cloud charges. The same workflow applies when your resources are hosted by AWS, Azure, Google Cloud, or another provider.

Before using these commands, run terraform init. Initialization downloads the providers required by the configuration and prepares the working directory.

Basic Example

Create a new directory and save the following configuration as main.tf. The local provider lets Terraform manage files on your computer.

terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }

  required_version = ">= 1.5.0"
}

provider "local" {}

variable "environment_name" {
  description = "Name of the development environment"
  type        = string
  default     = "development"
}

resource "local_file" "app_config" {
  filename = "${path.module}/dev-environment-app-config.txt"

  content = <<-EOT
    environment=${var.environment_name}
    app_port=8080
    log_level=debug
  EOT
}

resource "local_file" "readme" {
  filename = "${path.module}/dev-environment-README.txt"

  content = <<-EOT
    Development Environment

    Environment: ${var.environment_name}
    Start the application on port 8080.
  EOT
}

Run the commands below from the directory containing main.tf.

terraform init
terraform plan -out=dev-environment.tfplan
terraform apply dev-environment.tfplan
terraform destroy

Terraform will ask for confirmation before applying or destroying changes. Type yes when you have reviewed the proposed action and are ready to continue.

Expected Output

The exact output depends on your Terraform version, but the plan should summarize two resources to create:

Plan: 2 to add, 0 to change, 0 to destroy.

After applying, these files should appear in the Terraform directory:

  • dev-environment-app-config.txt
  • dev-environment-README.txt

After terraform destroy completes, Terraform removes both files because they are resources managed by the configuration.

How the Code Works

A top-to-bottom Terraform workflow: initialize the working directory, compare configuration and state to preview changes, review the saved plan, apply it to create or update resources, then review and confirm destruction to remove managed resources while Terraform state tracks them.
Terraform initializes providers, previews changes against configuration and state, applies the reviewed plan, and safely destroys the managed development environment after confirmation.

The terraform block declares the required local provider. Providers are plugins that allow Terraform to work with a particular system, such as local files, AWS services, or Azure resources.

The provider "local" {} block enables the provider. It has no settings because local file operations do not require credentials or a remote account.

The environment_name variable gives the configuration a reusable value. Its default value is development, so Terraform can run without an additional variable file or command-line argument.

Each resource "local_file" block describes one file that Terraform should manage:

  • filename identifies the file Terraform creates and removes.
  • content defines the file contents.
  • ${var.environment_name} inserts the variable value into the content.
  • ${path.module} refers to the directory containing the current Terraform module.

The -out=dev-environment.tfplan option saves the reviewed plan to a file. Applying that saved plan helps ensure that Terraform applies the changes you just inspected rather than recalculating a different plan.

Terraform also creates a state file named terraform.tfstate. State records which real resources correspond to the resources in your configuration. Do not casually delete or edit it. In team projects, state is commonly stored in a protected remote backend instead of only on a developer’s computer.

For a complementary example of deploying infrastructure and checking a resulting endpoint, see this guide to deploy and verify an API endpoint.

Another Example

The next example uses the same workflow with an AWS S3 bucket. It creates a private bucket for development artifacts and adds a small text object. This example requires AWS credentials, an AWS account, and a globally unique bucket name. Cloud resources can incur charges, so destroy the environment when you finish practicing.

Save this configuration as a separate main.tf file in another directory.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    random = {
      source  = "hashicorp/random"
      version = "~> 3.6"
    }
  }

  required_version = ">= 1.5.0"
}

provider "aws" {
  region = var.aws_region
}

variable "aws_region" {
  description = "AWS region for the development environment"
  type        = string
  default     = "us-east-1"
}

variable "project_name" {
  description = "Short project name used in the bucket name"
  type        = string
  default     = "dcg-dev"
}

resource "random_id" "bucket_suffix" {
  byte_length = 4
}

resource "aws_s3_bucket" "artifacts" {
  bucket = "${var.project_name}-${random_id.bucket_suffix.hex}"

  tags = {
    Environment = "development"
    ManagedBy   = "Terraform"
  }
}

resource "aws_s3_object" "readme" {
  bucket  = aws_s3_bucket.artifacts.id
  key     = "README.txt"
  content = "Temporary development artifacts belong in this bucket."
}

Use the same sequence, but save the plan under a name that identifies this environment:

terraform init
terraform plan -out=aws-dev-environment.tfplan
terraform apply aws-dev-environment.tfplan
terraform destroy

The random identifier makes the bucket name more likely to be globally unique. The object refers to aws_s3_bucket.artifacts.id, so Terraform understands that the bucket must exist before it creates the object. During destruction, Terraform removes the object and then the bucket.

Common Mistakes

Skipping the plan

Running terraform apply directly still creates a plan internally, but reviewing a separate plan first makes the change easier to inspect and discuss. For important environments, save the plan with -out and apply that saved file.

Destroying the wrong directory

Terraform commands operate on the configuration and state in the current directory. Before running terraform destroy, check your current directory and review the resources Terraform says it will remove.

Expecting plan to make changes

terraform plan is a preview. It does not create files, buckets, networks, or other infrastructure. Use terraform apply after reviewing the plan.

Deleting the state file manually

The state file helps Terraform track resources. Deleting it does not reliably remove the resources; it only makes Terraform forget its tracking information. If resources still exist, they may become unmanaged.

Putting secrets in configuration files

Do not place passwords, private keys, or access tokens directly in Terraform code or commit them to source control. For sensitive infrastructure data, learn how to protect sensitive infrastructure data with AWS KMS and use an appropriate secrets-management workflow.

Try It Yourself

Use the first local-file example and make these changes:

  • Change the default environment name to testing.
  • Change the application port from 8080 to 3000.
  • Run terraform plan and confirm that Terraform proposes changes.
  • Run terraform apply and inspect the updated files.
  • Run terraform destroy and confirm that the files are removed.

For extra safety, you can first validate deployment input in Bash before passing environment names or other values into an automation script.

Challenge

Create a local development environment configuration with three managed files:

  • An application configuration file containing the environment name and port.
  • A README file explaining how to start the application.
  • A ports file listing the application port and a metrics port.

Use a variable named environment_name with a default value of development. Then run terraform init, preview the changes with a saved plan, apply the saved plan, and finally destroy the environment.

Solution

Save the following as main.tf in a new directory:

terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }

  required_version = ">= 1.5.0"
}

provider "local" {}

variable "environment_name" {
  description = "Name of the development environment"
  type        = string
  default     = "development"
}

resource "local_file" "application_config" {
  filename = "${path.module}/challenge-app-config.txt"

  content = <<-EOT
    environment=${var.environment_name}
    app_port=3000
  EOT
}

resource "local_file" "readme" {
  filename = "${path.module}/challenge-README.txt"

  content = <<-EOT
    Development Environment

    Start the application with the configuration in challenge-app-config.txt.
  EOT
}

resource "local_file" "ports" {
  filename = "${path.module}/challenge-ports.txt"

  content = <<-EOT
    application_port=3000
    metrics_port=9090
  EOT
}

Run the complete workflow:

terraform init
terraform plan -out=challenge.tfplan
terraform apply challenge.tfplan
terraform destroy

The plan should report three resources to add. Applying the plan creates the three files, and destroying the environment removes all three resources tracked in the state.

Key Takeaways

  • terraform plan previews infrastructure changes without applying them.
  • terraform apply creates or updates resources according to the configuration.
  • terraform destroy removes resources managed by the current Terraform state.
  • A saved plan helps you apply the exact changes you reviewed.
  • Always check the current directory, plan output, and resource scope before making changes.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top
Ad
Ad
Ad