Search Posts on Binpipe Blog

Terraform: EC2 Instance Creation

Let's jump into creating an EC2 Instance with Terraform!

Creating the project directory

In a location of your choice, create a directory named 1-ec2-instance

Create the following directory structure (where the .tf files are blank text files):

1-ec2-instance/      -      -      -      -      - .gitignore  


This is not a required directory structure. Terraform will automatically read all `.tf` files within the directory and figure out what to do. This is a file structure that has proven effective in a production environment.


I would recommend creating a Git repository with these files. If you do so, you should start with this .gitignore content:

# Compiled files  *.tfstate  *.tfstate.backup    # Module directory  .terraform/    # Sensitive Files  /  

I recommend adding / to your .gitignore file because we're about to put some AWS secrets into it. Keeping the secrets out of Github can keep them more secure and resistent to accidents. In the future, your team may also want to use different secrets to manage permissions to different resources.

If working with a team, you can choose how you'd like these variables to be shared between each member of the team in a way that's right for you.

Here is where we'll set some variables to be re-used by the rest of the configuration. It will also serve as a handy place to keep our AWS secrets.

Let's start with these contents for the file:

# AWS Config
variable "aws_access_key" {
variable "aws_secret_key" {
variable "aws_region" {
default = "us-west-2"

Variables can have a default value that will be used if nothing is there to override them. In this case, we are utilizing default values to create reusable variables that will be utilized throughout the rest of our configuration files.

Explanation of Variables

aws_access_key - Access Key ID that allows your machine to make calls to the AWS API.

aws_secret_key - Secret Access Key that pairs with that Access Key ID

aws_region - The region in which our infrastructure is hosted (I'm using us-west-2 but you can change it if you'd like)

This file is pretty short:

provider "aws" {
access_key = "${var.aws_access_key}"
secret_key = "${var.aws_secret_key}"
region = "${var.aws_region}"
version = "~> 1.7"

In Terraform, Providers are interfaces to the services that maintain our Resources. For example - An EC2 Instance is a Resource provided by the Amazon Web Services Provider. A Git Repository is a Resource provided by the Github Provider.

Because Terraform is an open source tool, contributors can build custom providers to accomplish different tasks. For now, we will focus purely on the AWS provider and the resources it provides.

More specifically, we will be using version 1.7 of the AWS provider. This version will allow our configurations to work similarly on your machine - Even if the provider is updated after this chapter is written.

The AWS Provider requires an access_key (identifying the user Terraform should use) and a secret_key (authenticating the user Terraform should use). There is also an aws_region that identifies which region of the world Terraform should instantiate this infrastructure in.

This file is dedicated to finding the right Ubuntu AMI to install on our server. AMI IDs change from region to region and change over time as upgrades come out. We're going to create a data source to track down the right one.

The contents of the file are:

data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*"]
filter {
name = "virtualization-type"
values = ["hvm"]
owners = ["099720109477"] # Canonical

A data source is a read-only view into data stored outside of Terraform. The data sources available will change based on the provider. In this case, we are creating an aws_ami data source with the unique identifier of ubuntu.

The owners of the AMI that we're looking for (the official Ubuntu AMI), will always be Amazon. Therefore, the ID stored in owners is a constant.

We are using filter tags to filter all possible AMIs in the AWS AMI repository by nameand virtualization-type.

Lastly, there will likely be multiple results when we apply all of these filters. most_recent will select the most recent of the possible AMIs and return the attributes of that for later use in our Terraform configuration.

Here's the fun part. The part that initializes the server. It's also surprisingly short:

resource "aws_instance" "my-test-instance" {
ami = "${}"
instance_type = "t2.micro"
tags {
Name = "test-instance"

With all the work we've done in the other files, all we need to do here is describe the server we want.

Let's break down what this configuration is saying:

  • We are defining an aws_instance with the unique Terraform identifier of my-test-instance

  • That instance should use the AMI found in to initialize the server

  • That instance should be a t2.micro (the cheapest AWS instance type)

  • We've attached a Name tag to the instance, test-instance, for easy identification

This server won't do much - yet. As you'll notice, we haven't installed anything on it.

We could theoretically do that through a manual process, after we create the server. But that's not in the spirit of Infrastructure as Code!

We'll start adding things to this server in the next chapter. But before that happens, let's try out Terraform!

Creating the Infrastructure

Open up bash, navigate to the project's directory, and run the following:

$ terraform init  

This will download and install the proper version of the AWS provider for your project and place it in a directory called .terraform.

You should see a message like this in response:

Initializing provider plugins...  - Checking for available provider plugins on  - Downloading plugin for provider "aws" (1.7.0)...    Terraform has been successfully initialized!    You may now begin working with Terraform. Try running "terraform plan" to see  any changes that are required for your infrastructure. All Terraform commands  should now work.    If you ever set or change modules or backend configuration for Terraform,  rerun this command to reinitialize your working directory. If you forget, other  commands will detect it and remind you to do so if necessary.  

We'll now run the command that will take the configurations we've written and use the AWS API to build our servers. This command is one that you'll be using throughout most of your time with Terraform:

$ terraform apply  

You should see a message like this in response:

An execution plan has been generated and is shown below.  Resource actions are indicated with the following symbols:    + create    Terraform will perform the following actions:      +        id:                           <computed>        ami:                          "ami-1ee65166"        associate_public_ip_address:  <computed>        availability_zone:            <computed>        ebs_block_device.#:           <computed>        ephemeral_block_device.#:     <computed>        instance_state:               <computed>        instance_type:                "t2.micro"        ipv6_address_count:           <computed>        ipv6_addresses.#:             <computed>        key_name:                     <computed>        network_interface.#:          <computed>        network_interface_id:         <computed>        placement_group:              <computed>        primary_network_interface_id: <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:                    "test-instance"        tenancy:                      <computed>        volume_tags.%:                <computed>        vpc_security_group_ids.#:     <computed>      Plan: 1 to add, 0 to change, 0 to destroy.    Do you want to perform these actions?    Terraform will perform the actions described above.    Only 'yes' will be accepted to approve.      Enter a value:  

A message like this will always appear before changes are made to your infrastructure through apply. Terraform analyzes the existing resources in your AWS account and builds a plan of exactly what it will do and why. It outputs this plan and asks whether or not you'd like to make the changes.


Always read this plan carefully and take note of what's being created, modified or destroyed. This will prevent you from accidentally destroying infrastructure that does not need to be modified.

You can type yes and hit Enter to create the new server.

Congratulations! You've created your first piece of AWS infrastructure through Terraform. Welcome to the wonderful world of Infrastructure as Code!

Right now, this server doesn't do much. But we're going to start fixing that in the next chapter.

Destroying the Infrastructure

The idea of destroying infrastructure can sound a bit ominous. But, we're going to start getting rid of that ominous feeling right now. Let's destroy this server we've created!

With an established infrastructure, you are unlikely to use this next command. But destruction of resources will happen on a smaller scale, implicitly, through certain configuration changes.

Until we make this server do something in future chapters, let's destroy it to save a little money. Run the following command:

$ terraform destroy  

You should see a message like this in response: Refreshing state... (ID: i-0efdd3309c3a08f1e)    An execution plan has been generated and is shown below.  Resource actions are indicated with the following symbols:    - destroy    Terraform will perform the following actions:      -      Plan: 0 to add, 0 to change, 1 to destroy.    Do you really want to destroy?    Terraform will destroy all your managed infrastructure, as shown above.    There is no undo. Only 'yes' will be accepted to confirm.      Enter a value:  

Like with the apply command, this details exactly what is about to happen and asks you if it's what you are expecting.

You can type yes and hit Enter.