Section 1: Infrastructure as code basics
Your first configuration
Let us write a small configuration and run the full cycle. Create a folder with three files.
# versions.tf
terraform {
required_version = ">= 1.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.region
default_tags {
tags = { project = "iac-course", managed_by = "terraform" }
}
}
# variables.tf
variable "region" {
type = string
default = "eu-central-1"
}
variable "env" {
type = string
validation {
condition = contains(["dev", "prod"], var.env)
error_message = "env must be dev or prod."
}
}
# main.tf
resource "aws_s3_bucket" "logs" {
bucket_prefix = "iac-course-${var.env}-logs-"
}
resource "aws_s3_bucket_versioning" "logs" {
bucket = aws_s3_bucket.logs.id
versioning_configuration {
status = "Enabled"
}
}
output "logs_bucket" {
value = aws_s3_bucket.logs.bucket
}
Things to notice
- Version constraints:
~> 5.0allows any 5.x provider but not 6.0. Pin providers so an upgrade never surprises you. Check the registry for the current major version when you start a real project. - References:
aws_s3_bucket.logs.idrefers to an attribute of another resource. Terraform uses these references to build a dependency graph, so the versioning resource is created after the bucket. - Validation on variables catches mistakes before any API call.
- default_tags applies tags to every resource the provider creates, which helps with cost tracking.
Run it
terraform init
terraform fmt
terraform validate
terraform plan -var env=dev -out=dev.tfplan
terraform apply dev.tfplan
Saving the plan with -out and applying that exact file guarantees you apply what you reviewed, not a new plan computed a minute later.
init also creates .terraform.lock.hcl, which records exact provider versions and checksums. Commit it, so everyone uses identical providers.
Finish with terraform destroy -var env=dev to remove the bucket.