DEV Community

Cover image for Terraform: Installation & Basic Usage
AF uarabei
AF uarabei

Posted on • Updated on

Terraform: Installation & Basic Usage

Terraform’s basics: basic usage

Installation

You have read through 2 long articles that explained why Terraform is a good tool and what problems it can solve. Now, let's start using it!

In this article we will cover installation and our first configuration deployment. It will be just one EC2 instance, but the goal is to show you how it works.

==============

Prerequisites

  1. Installation instructions for Terraform.
  2. Installation instructions for AWS CLI
  3. AWS account and credentials that would allow Terraform to manage resources. For this part we are interested in IAM User with programmatic access. We will require AWS Access Key and AWS Secret Access Key.

Once you installed Terraform, AWS CLI, and have your AWS credentials on hand we would need to add access keys environment variables. Your key will go between double-quotes, without the carets.

$ export AWS_ACCESS_KEY_ID="<YOUR_AWS_ACCESS_KEY_ID>"
Enter fullscreen mode Exit fullscreen mode
$ export AWS_SECRET_ACCESS_KEY="<YOUR_AWS_SECRET_ACCESS_KEY>"
Enter fullscreen mode Exit fullscreen mode

After all these steps we can start using Terraform to manage our resources!

==============

Deploying our first infrastructure

We will start with a deploy of one EC2 instance. This will introduce you to the workflow of Terraform and basic structure of configuration files.

Enter these commands in the terminal to create a directory for our demo project and go into it:

# Create folder with the name tf-demo
$ mkdir tf-demo
Enter fullscreen mode Exit fullscreen mode
# Change into tf-demo folder
$ cd tf-demo
Enter fullscreen mode Exit fullscreen mode

Creating our first configuration

Now that we are in our project's folder we need to create a main.tf file that will hold our configuration:

$ touch main.tf
Enter fullscreen mode Exit fullscreen mode

Open main.tf file with you favourite editor and paste this code:

# This block stores terraform settings
terraform {
  # We can define providers such as AWS, Azure, etc.
  # in this block along with their version and
  # where terraform should download them from.
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.27"
    }
  }
  # This is the version of Terraform we will use
  required_version = ">= 0.14.9"
}

# These are provider-specific settings
provider "aws" {
  profile = "default"
  region  = "us-west-2"
}

resource "aws_instance" "app_server" {
  ami           = "ami-830c94e3"
  instance_type = "t2.micro"

  tags = {
    Name = "PathToTerraformCertInstance"
  }
}
Enter fullscreen mode Exit fullscreen mode

Next we will initialize Terraform in our folder:

$ terraform init
# the output should contain this
Terraform has been successfully initialized!
Enter fullscreen mode Exit fullscreen mode

If the code indentation is wrong, do not worry! Terraform provides a convenient command fmt that formats the code all nice and tidy. Let's try it:

$ terraform fmt
Enter fullscreen mode Exit fullscreen mode

Next step would be to check our configuration for syntactical errors. This is done using a validate command:

$ terraform validate
# You should get this output
Success! The configuration is valid.
Enter fullscreen mode Exit fullscreen mode

Before we deploy any resources it is a good idea to see what Terraform intends to do with the configuration we wrote. We will do this using plan command:

$ terraform plan
# output
Terraform used the selected providers to generate the following
execution plan. Resource actions are indicated with the following
symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.app_server will be created
  + resource "aws_instance" "app_server" {
      + ami                                  = "ami-830c94e3"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = (known after apply)
      + availability_zone                    = (known after apply)
      + cpu_core_count                       = (known after apply)
      + cpu_threads_per_core                 = (known after apply)
      + disable_api_termination              = (known after apply)
      + ebs_optimized                        = (known after apply)
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = (known after apply)
      + instance_state                       = (known after apply)
      + instance_type                        = "t2.micro"
      + ipv6_address_count                   = (known after apply)
      + ipv6_addresses                       = (known after apply)
      + key_name                             = (known after apply)
      + monitoring                           = (known after apply)
      + outpost_arn                          = (known after apply)
      + password_data                        = (known after apply)
      + placement_group                      = (known after apply)
      + placement_partition_number           = (known after apply)
      + primary_network_interface_id         = (known after apply)
      + private_dns                          = (known after apply)
      + private_ip                           = (known after apply)
      + public_dns                           = (known after apply)
      + public_ip                            = (known after apply)
      + secondary_private_ips                = (known after apply)
      + security_groups                      = (known after apply)
      + source_dest_check                    = true
      + subnet_id                            = (known after apply)
      + tags                                 = {
          + "Name" = "PathToTerraformCertInstance"
        }
      + tags_all                             = {
          + "Name" = "PathToTerraformCertInstance"
        }
      + tenancy                              = (known after apply)
      + user_data                            = (known after apply)
      + user_data_base64                     = (known after apply)
      + vpc_security_group_ids               = (known after apply)

      + capacity_reservation_specification {
          + capacity_reservation_preference = (known after apply)

          + capacity_reservation_target {
              + capacity_reservation_id = (known after apply)
            }
        }

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + enclave_options {
          + enabled = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
          + instance_metadata_tags      = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

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

─────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so
Terraform can't guarantee to take exactly these actions if you
run "terraform apply" now.
Enter fullscreen mode Exit fullscreen mode

As you can see the output is pretty long, a thing to note is that we can output all this data to a file for analysis.

Everything looks good. Let's deploy our first resource using Terraform!
*You will be asked to approve deployment, you'd need to type yes to begin deployment process.

$ terraform apply
# output
Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_instance.app_server: Creating...
aws_instance.app_server: Still creating... [10s elapsed]
aws_instance.app_server: Still creating... [20s elapsed]
aws_instance.app_server: Still creating... [30s elapsed]
aws_instance.app_server: Still creating... [40s elapsed]
aws_instance.app_server: Creation complete after 45s [id=i-02bb0dc43fdd214d3]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Enter fullscreen mode Exit fullscreen mode

Congratulations! You have just deployed your first resource using Infrastructure as Code!

We can check the state of Terraform-managed resources:

$ terraform state list
# output
aws_instance.app_server
Enter fullscreen mode Exit fullscreen mode

Great! Now it is time to take our instance down. It is done using aptly-named command destroy, you will need to approve this command with yes when prompted:

$ terraform destroy
# output
Destroy complete! Resources: 1 destroyed.
Enter fullscreen mode Exit fullscreen mode

Conclusion

This was a brief introduction to Terraform. As you went through the commands you also learned about Terraform's workflow.

This workflow is as follows: write -> plan -> create. We write our configuration, we verify that Terraform will do what we want it to do, and lastly, we create our resources.

You did great following through all the way to the end! I understand that deploying a single instance may not seem all that exciting. Some may argue that it would be faster to go to AWS console and deploy this instance manually. Maybe it's true. The point of this article was to introduce reader to Terraform basics.

Later on we will be deploying a much more complicated infrastructure with several EC2 instances, public and private subnets, S3 buckets, and bucket policies. We will see how we can leverage Terraform to automatically select active AZs (Availability Zones), search for the latest AMIs, name resources based on their location and purpose, create users and policies, and run bootstrap scripts on our EC2 instances. There is so much more that I want to write about.

Thank you for reading! See you in the next article where we will learn about HashiCorp Configuration Language (HCL)!

Top comments (0)