Skip to content

granite-stack/terraform-first-steps

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

terraform-first-steps — Code examples

Leer en español

Companion code for the tutorial Terraform para humanos: de "click-ops" descontrolado a IaC (sin perder la dignidad por el camino).

Each subdirectory is a self-contained Terraform project that maps directly to a section of the tutorial. You can use them as-is, adapt them to your own infrastructure, or just read through them while following the article.


Directory structure

terraform-first-steps/
├── aws-ec2-demo/          # Example 1 — EC2 instance on AWS
├── azure-vm-demo/         # Example 2 — Linux VM on Azure
├── gcp-vm-demo/           # Example 3 — Compute Engine VM on GCP
└── modules-example/       # Modules section — reusable AWS VM module

aws-ec2-demo

Provisions a Ubuntu 22.04 EC2 instance on AWS using a remote S3 + DynamoDB backend for state.

File Purpose
providers.tf AWS provider and S3 remote backend configuration
variables.tf Region, instance name, type and root volume size
main.tf AMI data source and EC2 instance resource
outputs.tf Instance ID and public IP
dev.tfvars Variable values for a development environment
prod.tfvars Variable values for a production environment

Prerequisites: AWS CLI installed and configured (aws configure). Credentials must have EC2 permissions. The S3 bucket and DynamoDB table referenced in providers.tf must exist before running terraform init.

cd aws-ec2-demo
terraform init
terraform plan -var-file="dev.tfvars"
terraform apply -var-file="dev.tfvars"

Creating the S3 backend for Terraform state

The aws-ec2-demo project (and any AWS project using a remote backend) requires an S3 bucket for storing the Terraform state file and a DynamoDB table for state locking. These are one-time resources that must exist before running terraform init.

Replace tutorial-2026-terraform-state and eu-west-1 with your own values. Bucket names are globally unique — pick something specific to your project and account.

# Create the S3 bucket
aws s3api create-bucket \
  --bucket tutorial-2026-terraform-state \
  --region eu-west-1 \
  --create-bucket-configuration LocationConstraint=eu-west-1

# Enable versioning (lets you recover previous state files)
aws s3api put-bucket-versioning \
  --bucket tutorial-2026-terraform-state \
  --versioning-configuration Status=Enabled

# Enable server-side encryption
aws s3api put-bucket-encryption \
  --bucket tutorial-2026-terraform-state \
  --server-side-encryption-configuration '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'

# Block all public access
aws s3api put-public-access-block \
  --bucket tutorial-2026-terraform-state \
  --public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

# Create the DynamoDB table for state locking
aws dynamodb create-table \
  --table-name terraform-locks \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST \
  --region eu-west-1

Note for us-east-1: When the region is us-east-1, omit the --create-bucket-configuration flag — it is not accepted for that region.

Once the bucket and table exist, update the bucket, dynamodb_table and region values in aws-ec2-demo/providers.tf to match, then run terraform init.

Cleaning up the backend resources

When you no longer need the backend, delete the resources in this order: first empty and delete the S3 bucket, then delete the DynamoDB table.

# Delete all object versions (skipped if the bucket is empty)
VERSIONS=$(aws s3api list-object-versions \
  --bucket tutorial-2026-terraform-state \
  --query '{Objects: Versions[].{Key:Key,VersionId:VersionId}}' \
  --output json)
if echo "$VERSIONS" | grep -q '"Key"'; then
  aws s3api delete-objects \
    --bucket tutorial-2026-terraform-state \
    --delete "$VERSIONS"
fi

# Delete all delete markers (skipped if none exist)
MARKERS=$(aws s3api list-object-versions \
  --bucket tutorial-2026-terraform-state \
  --query '{Objects: DeleteMarkers[].{Key:Key,VersionId:VersionId}}' \
  --output json)
if echo "$MARKERS" | grep -q '"Key"'; then
  aws s3api delete-objects \
    --bucket tutorial-2026-terraform-state \
    --delete "$MARKERS"
fi

# Delete the now-empty bucket
aws s3api delete-bucket \
  --bucket tutorial-2026-terraform-state \
  --region eu-west-1

# Delete the DynamoDB table
aws dynamodb delete-table \
  --table-name terraform-locks \
  --region eu-west-1

Tip: Make sure you have run terraform destroy first to remove the managed infrastructure before deleting the backend. Otherwise the state file is lost and Terraform will no longer be able to track those resources.


azure-vm-demo

Provisions a Ubuntu 22.04 Linux VM on Azure, including resource group, virtual network, subnet, public IP, and network interface.

File Purpose
providers.tf AzureRM provider and Azure Blob remote backend configuration
variables.tf Location, VM name, size and OS disk size
main.tf Full networking stack and Linux VM resource
outputs.tf VM resource ID and public IP

Prerequisites: Azure CLI installed and authenticated (az login). An active subscription. The storage account and container referenced in providers.tf must exist before running terraform init.

cd azure-vm-demo
terraform init
terraform plan
terraform apply

Creating the Azure backend for Terraform state

The azure-vm-demo project requires an Azure Storage Account and a blob container to store the state file. Azure does not have a built-in locking mechanism separate from the backend — the AzureRM backend handles locking automatically via blob leases.

Replace stterraformstate123 with a globally unique name (3–24 characters, lowercase letters and numbers only). Adjust --location to your preferred region.

# Create the resource group
az group create \
  --name rg-terraform-state \
  --location westeurope

# Create the storage account
az storage account create \
  --name stterraformstate123 \
  --resource-group rg-terraform-state \
  --location westeurope \
  --sku Standard_LRS \
  --encryption-services blob

# Create the blob container
az storage container create \
  --name tfstate \
  --account-name stterraformstate123

Once these exist, update resource_group_name, storage_account_name and container_name in azure-vm-demo/providers.tf to match, then run terraform init.

Cleaning up the Azure backend resources

Tip: Run terraform destroy first to remove the managed infrastructure before deleting the backend.

# Deleting the resource group removes the storage account and container with it
az group delete --name rg-terraform-state --yes

gcp-vm-demo

Provisions a Ubuntu 22.04 Compute Engine instance on GCP, including a VPC network and a firewall rule allowing SSH.

File Purpose
providers.tf Google provider and GCS remote backend configuration
variables.tf Project ID, region, zone, instance name, machine type and boot disk size
main.tf VPC network, firewall rule and Compute Engine instance
outputs.tf Instance ID and public IP

Prerequisites: Google Cloud CLI installed and initialised (gcloud init). A GCP project with billing enabled and Compute Engine API activated. The GCS bucket referenced in providers.tf must exist before running terraform init.

cd gcp-vm-demo
terraform init
terraform plan -var="project_id=your-project-id"
terraform apply -var="project_id=your-project-id"

Creating the GCS backend for Terraform state

The gcp-vm-demo project requires a GCS bucket to store the state file. GCS backends handle locking natively via object locking — no separate resource is needed.

Replace mi-terraform-state-bucket with a globally unique bucket name and adjust the --location as needed.

# Create the GCS bucket with uniform access control
gcloud storage buckets create gs://mi-terraform-state-bucket \
  --location=EU \
  --uniform-bucket-level-access

# Enable versioning (lets you recover previous state files)
gcloud storage buckets update gs://mi-terraform-state-bucket \
  --versioning

Once the bucket exists, update bucket in gcp-vm-demo/providers.tf to match, then run terraform init.

Cleaning up the GCS backend resources

Tip: Run terraform destroy first to remove the managed infrastructure before deleting the backend.

# Remove all objects and versions inside the bucket
gcloud storage rm -r gs://mi-terraform-state-bucket/**

# Delete the bucket
gcloud storage buckets delete gs://mi-terraform-state-bucket

modules-example

Demonstrates how to extract a reusable module for an AWS EC2 instance and consume it from a root configuration to provision multiple environments without copy-pasting.

modules-example/
├── main.tf                     # Root config — invokes web_dev and web_prod modules
└── modules/
    └── aws_vm/
        ├── variables.tf        # Module input variables
        ├── main.tf             # AMI data source and EC2 instance
        └── outputs.tf          # Instance ID and public IP

Prerequisites: Same as aws-ec2-demo. The module uses local state by default (no backend block); add one to modules-example/main.tf before using this in a team.

cd modules-example
terraform init
terraform plan
terraform apply

Notes

  • All .gitignore files exclude .terraform/, *.tfstate* and *.tfplan — do not remove those entries.
  • Backend bucket and table/container names in providers.tf are placeholders. Replace them with your own before running terraform init.
  • The SSH public key path (~/.ssh/id_rsa.pub) used in the Azure and GCP examples assumes a default key location. Adjust as needed.

About

Companion code for the tutorial "Terraform para humanos: de click-ops descontrolado a IaC (sin perder la dignidad por el camino)".

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages