Blog
January 15, 2017 Marie H.

Getting Started with Terraform

Getting Started with Terraform

Photo by <a href="https://unsplash.com/@growtika?utm_source=cloudista&utm_medium=referral" target="_blank" rel="noopener">Growtika</a> on <a href="https://unsplash.com/?utm_source=cloudista&utm_medium=referral" target="_blank" rel="noopener">Unsplash</a>

I kept hearing about Terraform at every meetup and finally sat down to actually use it. Turns out it's not that intimidating once you get past the initial "wait, where does all this state go" confusion.

Terraform is an infrastructure-as-code tool from HashiCorp. You describe what you want your infrastructure to look like in HCL (HashiCorp Configuration Language), run a plan to see what it's going to do, then apply it. It talks to cloud providers via plugins. Simple concept, surprisingly powerful in practice.

Installing

Download the binary from terraform.io, unzip it, drop it somewhere in your PATH. That's it. No package manager drama.

$ terraform --version
Terraform v0.9.11

Your first .tf file

Create a directory for your project and a main.tf file. Here's a minimal AWS setup that launches an EC2 instance:

provider "aws" {
  region = "${var.aws_region}"
}

variable "aws_region" {
  default = "us-east-1"
}

variable "instance_type" {
  default = "t2.micro"
}

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "${var.instance_type}"

  tags {
    Name = "terraform-example"
  }
}

The ${var.whatever} syntax is how you interpolate variables. The provider block tells Terraform which AWS region to use. Credentials are pulled from your environment (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) or ~/.aws/credentials — same as the AWS CLI.

terraform init

Before you can do anything, you need to initialize the directory. This downloads the provider plugins.

$ terraform init

Initializing provider plugins...
- Downloading plugin for provider "aws" (1.7.0)...

Terraform has been successfully initialized!

terraform plan

This is the dry run. It shows you exactly what Terraform is going to create, modify, or destroy before touching anything.

$ terraform plan

+ aws_instance.web
    ami:                         "ami-0c55b159cbfafe1f0"
    availability_zone:           "<computed>"
    ebs_optimized:               "false"
    instance_state:              "<computed>"
    instance_type:               "t2.micro"
    key_name:                    "<computed>"
    placement_group:             "<computed>"
    private_dns:                 "<computed>"
    private_ip:                  "<computed>"
    public_dns:                  "<computed>"
    public_ip:                   "<computed>"
    root_block_device.#:         "<computed>"
    security_groups.#:           "<computed>"
    source_dest_check:           "true"
    subnet_id:                   "<computed>"
    tags.%:                      "1"
    tags.Name:                   "terraform-example"
    tenancy:                     "<computed>"
    vpc_security_group_ids.#:    "<computed>"

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

The + means it's adding a resource. You'll also see ~ for changes and - for destroys. Get very familiar with reading this output before you apply anything in production.

terraform apply

When the plan looks right, apply it:

$ terraform apply

aws_instance.web: Creating...
  ami:           "" => "ami-0c55b159cbfafe1f0"
  instance_type: "" => "t2.micro"
  tags.Name:     "" => "terraform-example"
aws_instance.web: Still creating... (10s elapsed)
aws_instance.web: Creation complete (ID: i-0a1b2c3d4e5f67890)

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

The state file

After apply, Terraform creates terraform.tfstate. This is how it tracks what it manages. Don't delete it, don't edit it by hand, and if you're working with a team, put it in S3 with a remote backend so everyone's working from the same state:

terraform {
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "project/terraform.tfstate"
    region = "us-east-1"
  }
}

Using .tfvars

Instead of editing defaults in the .tf file, use a .tfvars file for environment-specific values:

# prod.tfvars
aws_region    = "us-west-2"
instance_type = "t2.medium"

Then pass it at plan/apply time:

$ terraform plan -var-file=prod.tfvars

I keep a dev.tfvars and prod.tfvars in each project and just pass the right one. Way cleaner than environment variables or editing files.

Cleaning up

$ terraform destroy

It will show you a plan and ask for confirmation. Everything Terraform created gets torn down cleanly. This alone makes it worth using for spinning up test environments — no more orphaned resources running up your AWS bill.

That's the basics. Next step is breaking things into modules once your main.tf starts getting unwieldy.