This is a step-by-step guide for provisioning AWS resources using Terraform Cloud. This tutorial covers organizations and workspace setup, credential management, and automated deployments via version control.
- Overview
- Prerequisites
- Project Structure
- Getting Started
- Configuration Reference
- Outputs
- Destroying Resources
- Troubleshooting
- Contributing
- License
This tutorial walks you through deploying an AWS EC2 instance using Terraform Cloud as the remote state backend and CI/CD execution environment. Instead of running terraform apply locally, Terraform Cloud handles plan and apply runs automatically when you push code to your connected Git repository.
What you'll learn:
- Creating and configuring a Terraform Cloud workspace
- Storing AWS credentials securely as environment variables
- Writing modular Terraform configuration for EC2
- Triggering remote plan/apply runs via VCS integration
- Reading outputs (instance ID, public IP) from Terraform Cloud
Before you begin, ensure you have the following:
| Requirement | Details |
|---|---|
| AWS Account | With an IAM user that has EC2 permissions |
| AWS IAM Credentials | AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY |
| Terraform Cloud Account | Free at app.terraform.io |
| Terraform CLI | v1.5+ β Install Guide |
| Git | Connected to GitHub, GitLab, or Bitbucket |
.
βββ main.tf
βββ variables.tf
βββ backend.tf
βββ provider.tf
βββ outputs.tf
βββ terraform.tfvars
βββ README.md
- Log in to app.terraform.io and create an Organization (if you don't have one).
- Click New Workspace β select Version Control Workflow.
- Connect your VCS provider (GitHub, GitLab, or Bitbucket) and choose this repository.
- Name your workspace (e.g.,
aws-ec2-prod) and click Create workspace.
Instead of storing long-lived AWS access keys, this tutorial uses Terraform Cloud Dynamic Provider Credentials β short-lived tokens issued via OpenID Connect (OIDC). This is the recommended, more secure approach.
In the AWS Console β IAM β Identity Providers, create a new OIDC provider:
| Field | Value |
|---|---|
| Provider URL | https://app.terraform.io |
| Audience | aws.workload.identity |
Create a new IAM role with the following Trust Policy, replacing the placeholders with your values:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:oidc-provider/app.terraform.io"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"app.terraform.io:aud": "aws.workload.identity"
},
"StringLike": {
"app.terraform.io:sub": "organization:<YOUR_TFC_ORG>:project:*:workspace:<YOUR_WORKSPACE_NAME>:run_phase:*"
}
}
}
]
}Attach the AmazonEC2FullAccess policy (or a least-privilege custom policy) to this role and note the Role ARN.
In your workspace, go to Settings β Variables and add the following as Environment Variables:
| Variable | Value | Sensitive |
|---|---|---|
TFC_AWS_PROVIDER_AUTH |
true |
No |
TFC_AWS_RUN_ROLE_ARN |
arn:aws:iam::<account-id>:role/<role-name> |
No |
TFC_AWS_WORKLOAD_IDENTITY_AUDIENCE |
aws.workload.identity |
No |
β No static keys required. Terraform Cloud will automatically assume the IAM role using short-lived OIDC tokens for every run !.
main.tf
data "aws_ami" "amazon_linux_2023" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
}
resource "aws_instance" "web-server" {
ami = data.aws_ami.amazon_linux_2023.id
instance_type = var.instance_type
tags = {
Name = "Terraform-Lab-Instance-${var.environment}"
}
}
backend.tf
terraform {
cloud {
organization = "your-org-name"
workspaces {
name = "aws-ec2-prod"
}
}
}provider.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}variables.tf
variable "instance_type" {
description = "The type of instance to create"
type = string
default = "t2.micro"
}
variable "environment" {
type = string
default = "dev"
description = "The environment for the instance"
}
variable "aws_region" {
description = "AWS region to deploy resources"
type = string
default = "us-east-1"
}
variable "ami_id" {
description = "Id for AMI"
type = string
}outputs.tf
output "instance_id" {
description = "The EC2 instance ID"
value = aws_instance.web.id
}
output "public_ip" {
description = "The public IP address of the EC2 instance"
value = aws_instance.web.public_ip
}Run the following to link your local CLI to Terraform Cloud:
terraform loginA browser window will open. Generate an API token and paste it into your terminal when prompted.
Initialize Terraform locally to link the workspace:
terraform initCommit and push your configuration files:
git add main.tf variables.tf outputs.tf
git commit -m "feat: add EC2 terraform configuration"
git push origin mainPushing to the connected branch automatically triggers a plan run in Terraform Cloud.
- Open your workspace in Terraform Cloud.
- Navigate to the Runs tab β you should see a new run triggered by your push.
- Review the plan output to confirm the EC2 instance will be created.
- Click Confirm & Apply to provision the instance.
After the apply completes, the Outputs section will show:
instance_idβ the AWS instance IDpublic_ipβ the public IP address of your EC2 instance
Verify in the AWS Console β EC2 β Instances.
| Variable | Type | Default | Description |
|---|---|---|---|
aws_region |
string |
us-east-1 |
AWS region to deploy into |
ami_id |
string |
Amazon Linux 2 AMI | AMI ID (must match region) |
instance_type |
string |
t2.micro |
EC2 instance type |
Note: AMI IDs are region-specific. Find the correct AMI for your region in the AWS AMI Catalog.
| Output | Description |
|---|---|
instance_id |
AWS-assigned ID of the EC2 instance (e.g., i-0abcd1234ef567890) |
public_ip |
Public IPv4 address assigned to the instance |
To avoid ongoing AWS charges, destroy the resources when you're done:
- In Terraform Cloud, go to your workspace β Settings β Destruction and Deletion.
- Click Queue destroy plan.
- Review and confirm the destroy run.
Alternatively, from the CLI:
terraform destroyError: No valid credential sources found
β Ensure AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are set as Environment Variables (not Terraform Variables) in your Terraform Cloud workspace.
InvalidAMIID.NotFound
β The ami_id default is for us-east-1. Update ami_id in variables.tf to match your target region.
terraform init fails with workspace not found
β Double-check the organization and workspaces.name values in the cloud {} block in main.tf.
Run not triggered after git push
β Confirm the VCS connection in Workspace Settings β Version Control and ensure you're pushing to the correct branch.
Contributions are welcome! To contribute:
- Fork this repository
- Create a feature branch:
git checkout -b feature/my-improvement - Commit your changes:
git commit -m 'feat: add my improvement' - Push to the branch:
git push origin feature/my-improvement - Open a Pull Request
Please keep examples beginner-friendly and tested.
This project is licensed under the MIT License.
Found this helpful? Give the repo a β and share it with others learning Terraform!