HCL Basics

Sign Up to Build

About this Architecture

Here is some information about this architecture.

How to Build This Solution

Here are the steps you can follow to build this solution on your own.

In this lesson you’ll learn the basics of Terraform’s primary user interface: Hashicorp Configuration Language (HCL), or Configuration Language (CL) for short. This configuration language is how infrastructure resources are defined.

Here are the key concepts covered in this lesson:

  • Files & Directories

  • Language Syntax

  • Resources

  • Providers

  • Lab: Single Resource Deployment

Files & Directories

You create Terraform projects by writing configuration files full of resource definitions, and then using the CLI to manage their deployment to a cloud provider.

These configuration files are typically grouped together by a specific project. If you were deploying a web application, you would create a Terraform project directory to define all of the cloud resources for that one project.

The files themselves end in .tf or .tf.json and are typically organized like this:

├─ variables.tf
├─ outputs.tf
├─ main.tf

You’ll learn more about which each of the file types are used for in later lessons.

In the structure above, the my-project/ directory would be considered the root module. It is possible to have additional directories in the root module. If those directories also have .tf files in them, they become child modules. You’ll learn more about modules in the HCL Advanced Concepts module.

Language Syntax

Terraform has a specific language syntax. This syntax was designed to be easy for humans to read and write. One glance at it and you should be able to understand what the resource is, and how it is configured.

The configure language has two basic concepts: Blocks and Arguments.


A block has a container for content. In the context of a cloud architecture, there can be blocks for things like infrastructure resources, variable inputs, and resource outputs. They are the fundamental building block of the Configuration Language.

Here’s what a block looks like:

resource "aws_instance" "web_server" {
  // block content

In the above code, the block type is declared using the resource name. The resource block type has two label requirements: kind, and name. In the example above, the kind is aws_instance, and the name is web_server.


Arguments assign values to names. They are like settings of blocks. In the context of a resource like an EC2 instance, the argument would set things like the instance type, the AMI ID, etc.

Here’s what arguments look like. Note that they are defined within the block.

resource "aws_instance" "web_server" {
  ami           = "ami-0cf6f5c8a62fa5da6"
  instance_type = "t2.micro"


In the previous section you learned about blocks. Resources are the most important kind of block you’ll use, as they define the actual cloud infrastructure that will be deployed and managed.

You’ve already seen an example of the EC2 resource block above. Here’s another example showing a S3 bucket resource block.

resource "aws_s3_bucket" "my_bucket" {
  bucket = "smx123yzap"

The above code defines:

  • That this is a resource block.

  • The resource block type is an aws_s3_bucket.

  • The local name of the bucket is my_bucket. Note that this is not the bucket name that will be created on AWS S3.

  • Lastly, the bucket argument name is smx123yzap. This is the actual bucket name on AWS S3.

The types of resources available in Terraform are dictated by providers. Providers are plugins for Terraform that you’ll learn about more next.


How does Terraform know what resources are on AWS or GCP? It’s not magic, that we know for sure! It’s through plugins called providers. Provider plugins define exactly what resource types and data sources are available. For each resource type, the plugin defines what arguments are required, and which are optional.

Terraform is responsible for managing providers. Each provider has its own documentation that details exactly what resources are available and how to configure them. Once you get the hang of Terraform, you’ll mostly just need to reference the provider documentation to learn how to deploy a new resource.

Check out the Terraform Registry for a complete list of providers.

Setting Providers

Providers need to be explicitly set within a Terraform project configuration file. Provider configurations should be set in the root module (directory) of a Terraform project.

Providers are set using the provider block, like below:

provider "aws" {
  region  = "us-west-2"

The above code creates the provider configuration, and sets the region.

However, you will also need to set this as a required provider within the terraform project block. The following code will make sure that the right aws provider version is downloaded when a new project is initialized.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.42"

Lab Time!

It’s time to get hands-on experience with Terraform. In this lab you will launch your first EC2 instance.

Get Your AWS Credentials

If you're using the Skillmix Labs feature, open the lab settings (the beaker icon) on the right side of the code editor. Then, click the Start Lab button to start hte lab environment.

Wait for the credentials to load. Then run this in the terminal.

Be sure to enter in your own access key and secret key and name your profile 'smx-lab'.

$ aws configure --profile smx-lab
AWS Access Key ID [None]: 
AWS Secret Access Key [None]: 
Default region name [None]: us-west-2
Default output format [None]: 

Note: If you're using your own AWS account you'll need to ensure that you've created and configured a named AWS CLI profile named smx-lab.

Get the Subnet and Security Group IDs

Further in this lab you will create an EC2 instance resource. You will need two resources IDs from the AWS account in order to do so; the subnet and security group IDs.

First, open the VPC Security Group Console. Make sure you’re in the us-west-2 region.

Find the security group named default, and copy the ID. The ID will be in the format of sg-xxxxxxxxxx.

Next, open the VPC Subnet Console. Find the subnet named Skillmix Lab Public Subnet (AZ2) and copy the ID. It will be in the format of subnet-xxxxxxxxxx.

Create a Project Directory

You need a directory to create the Terraform files in. Find a good place on your computer to create this directory in. Use your CLI or Command Prompt to navigate to it.

# create the diretory
$ mkdir terraform-lab 

$ cd terraform-lab

Create the main.tf File

Terraform uses the .tf file extension for defining resources and other configuration settings. When Terraform runs, it will concatenate all .tf files in the directory tree together. It’s best practice to start with a file named main.tf in the root of your directory.

In your working directory, create a file called main.tf.

# create the file
$ touch main.tf

Add the Terraform & Provider Block

In this lab we will use the AWS provider. As mentioned, there are many providers you can choose from. You can look at the Terraform Provider Registry to see what’s available.

Open the main.tf file and add the provider block as follows:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.42"
  required_version = ">= 0.15.3"

provider "aws" {
  profile = "skillmix-lab"
  region  = "us-west-2"

Code Review

  • The terraform block defines two main things. First, it defines the required_providers. We are using the AWS provider, defining the source path, and we are specifying the provider version to use. Second, we are defining the Terraform required_version to use

  • The second config is the provider block. Here, we define that we’re using the aws provider. We define two settings here. First, we specify what AWS CLI profile to use; we are going to use the one you just created in this lab. Second, we define the region

Add the EC2 Instance Resource Block

Next, we will add the EC2 instance resource. Append the following code to the main.tf file.

Note: You must replace the vpc_security_group_ids and subnet_id values with those you collected earlier.

# ...terraform and provider block above

resource "aws_instance" "web_server" {
  ami           = "ami-0cf6f5c8a62fa5da6"
  instance_type = "t2.micro"
  vpc_security_group_ids = ["<your_sg_id>"]
  subnet_id              = "<your_subnet_id>"

  tags = {
    Name = "skillmix-lab-instance"

Code Review

  • The resource block follows a specific structure. The first value in quotes is "aws_instance"; this is the resource name according to the provider specification. The second value in quotes is "web_server" is a name you give to the resource; this can be referenced later on

  • The resource block configuration parameters define what the AMI ID, instance type, security group, and subnet to use. We also give the instance a name tag

Reviewing the main.tf File

Just so we’re on the same page, here’s the full main.tf file.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.42"
  required_version = ">= 0.15.3"

provider "aws" {
  profile = "skillmix-lab"
  region  = "us-west-2"

resource "aws_instance" "web_server" {
  ami           = "ami-0cf6f5c8a62fa5da6"
  instance_type = "t2.micro"
  vpc_security_group_ids = ["<your_sg_id>"]
  subnet_id              = "<your_subnet_id>"

  tags = {
    Name = "skillmix-lab-instance"

Run Terraform Init

Now we get to one of the fun parts! We can start working with the Terraform CLI commands. There are a set of commands you’ll need to get very familiar with. We start with terraform init.

The terraform init command will download and install the providers defined in the configuration. In our case, it will download the aws provider. It also sets up a hidden subdirectory named .terraform. This subdirectory contains files that Terraform needs to do its job.

Run this command.

$ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 3.42"...
- Installing hashicorp/aws v3.42.0...
- Installed hashicorp/aws v3.42.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

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.

If you look in your main directory, you’ll also see a subdirectory called .terraform now.

Run Terraform Plan

Next we need to run terraform plan to create an execution plan. Creating an execution plan consist of the following steps:

  • Gets the current state of any existing remote items. For example, if there’s a running EC2 instance, the details of that instance will be read

  • The plan will compare what’s running, to what’s in the current state of the project, and noting any differences

  • It will then propose a set of change actions that will be applied

$ terraform plan

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.web_server will be created
  + resource "aws_instance" "web_server" {
      + ami                                  = "ami-0cf6f5c8a62fa5da6"
      + 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)
      + 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)
      + outpost_arn                          = (known after apply)
      + password_data                        = (known after apply)
      + placement_group                      = (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                            = "subnet-0c2d1e42f315784f8"
      + tags                                 = {
          + "Name" = "skillmix-lab-instance"

# additional output...

Run Terraform Apply

Now we can run terraform apply. This command will deploy the resources (or apply changes) defined in our configuration files.

You will be prompted to continue; type ‘yes’.

$ terraform apply

# additional output...

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: yes

aws_instance.web_server: Creating...
aws_instance.web_server: Still creating... [10s elapsed]
aws_instance.web_server: Still creating... [20s elapsed]
aws_instance.web_server: Still creating... [30s elapsed]
aws_instance.web_server: Creation complete after 32s [id=i-0b428c12917f18248]

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

Run Terraform Show

Terraform keeps its state in a file called terraform.tfstate. This file will show up in your root directory. You can run terraform show to inspect its contents.

This command will show you things like the instance ID, ARN, IPs, etc.

$ terraform show

# aws_instance.web_server:
resource "aws_instance" "web_server" {
    ami                                  = "ami-0cf6f5c8a62fa5da6"
    arn                                  = "arn:aws:ec2:us-west-2:013084617604:instance/i-0b428c12917f18248"
    associate_public_ip_address          = true
    availability_zone                    = "us-west-2b"
    cpu_core_count                       = 1
    cpu_threads_per_core                 = 1
    disable_api_termination              = false
    ebs_optimized                        = false
    get_password_data                    = false
    hibernation                          = false
    id                                   = "i-0b428c12917f18248"
    instance_initiated_shutdown_behavior = "stop"
    instance_state                       = "running"
    instance_type                        = "t2.micro"
    ipv6_address_count                   = 0
    ipv6_addresses                       = []
    monitoring                           = false
    primary_network_interface_id         = "eni-0089fd346335d31cb"
    private_dns                          = "ip-10-192-11-47.us-west-2.compute.internal"
    private_ip                           = ""
    public_ip                            = ""
    secondary_private_ips                = []
    security_groups                      = []
    source_dest_check                    = true
    subnet_id                            = "subnet-0c2d1e42f315784f8"
    tags                                 = {
        "Name" = "skillmix-lab-instance"

# additional output...

Run Terraform Destroy

Now it’s time to complete the lifecycle with terraform destroy. This command will destroy all of the resources created by the project. In this case, it will destroy the EC2 instance.

$ terraform destroy

# additional output...

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

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_instance.web_server: Destroying... [id=i-0b428c12917f18248]