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.
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
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"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-1Note for us-east-1: When the region is
us-east-1, omit the--create-bucket-configurationflag — 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.
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-1Tip: Make sure you have run
terraform destroyfirst 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.
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 applyThe 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 stterraformstate123Once these exist, update resource_group_name, storage_account_name and container_name in azure-vm-demo/providers.tf to match, then run terraform init.
Tip: Run
terraform destroyfirst 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 --yesProvisions 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"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 \
--versioningOnce the bucket exists, update bucket in gcp-vm-demo/providers.tf to match, then run terraform init.
Tip: Run
terraform destroyfirst 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-bucketDemonstrates 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- All
.gitignorefiles exclude.terraform/,*.tfstate*and*.tfplan— do not remove those entries. - Backend bucket and table/container names in
providers.tfare placeholders. Replace them with your own before runningterraform 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.