Terraform Infrastructure Code in Practice: The Code and Commands That Really Matter
Terraform Infrastructure Code: The Essentials in One Article — Real Code, Diagrams and Concrete Steps, Excerpts from a 38-Lesson Course.
No endless theory here: open the terminal and practice. Here's the essentials of Terraform Infrastructure Code, extracted directly from a complete 38-lesson course — with real code you can copy-paste right now.
- Introduction to Terraform and IaC
- Installation and First Steps
- HCL Language Providers Variables and Resources
- Modules and Code Organization
- Remote State Backend and Workspaces
Chapter 08 – Lesson 2: First GCP Project — VPC, Firewall and Compute Engine
1. Prerequisites
Before starting, make sure all the elements below are in place. Without these prerequisites, terraform apply will fail with authentication or unenabled API errors.
1.1 Accounts and tools
1.2 GCP credentials to retrieve
You will need this information to configure the Terraform provider and authenticate API calls:
| Item | How to obtain it | Example |
|---|---|---|
| Project ID (short string) | gcloud config get-value project | mon-projet-tf-prod |
| Project Number (12 digits) | gcloud projects describe PROJECT_ID --format="value(projectNumber)" | 123456789012 |
| Organization ID (optional) | gcloud organizations list | 987654321000 |
| Billing Account ID | gcloud billing accounts list | 0X0X0X-0Y0Y0Y-0Z0Z0Z |
| Service Account email | Standard Terraform SA format | tf-sa@PROJECT_ID.iam.gserviceaccount.com |
gcloud auth application-default login is sufficient.1.3 Configure gcloud + ADC authentication
One time on your machine: authenticate with your Google account, set the project, create a Service Account dedicated to Terraform and configure Application Default Credentials (ADC) that Terraform reads automatically.
# 1. User login (opens the browser)
gcloud auth login
# 2. Select the project
gcloud config set project mon-projet-tf-prod
# 3. Create a Service Account dedicated to Terraform
gcloud iam service-accounts create tf-sa \
--display-name="Terraform Service Account"
# 4. Assign the role (Editor for exercises, more restricted in prod)
gcloud projects add-iam-policy-binding mon-projet-tf-prod \
--member="serviceAccount:tf-sa@mon-projet-tf-prod.iam.gserviceaccount.com" \
--role="roles/editor"
# 5. ADC for local dev (recommended method for Terraform)
gcloud auth application-default login
# 6. (CI/CD alternative) Create a JSON key — sensitive, protect it!
gcloud iam service-accounts keys create key.json \
--iam-account=tf-sa@mon-projet-tf-prod.iam.gserviceaccount.com
# export GOOGLE_APPLICATION_CREDENTIALS=$PWD/key.json
# 7. Verify
gcloud auth list
gcloud config list project1.4 Enable required GCP APIs
GCP requires you to explicitly enable each service before you can use it. For this lesson, only Compute Engine is strictly necessary, but it is worth enabling common APIs at once:
gcloud services enable \
compute.googleapis.com \
cloudresourcemanager.googleapis.com \
iam.googleapis.com \
iap.googleapis.com
# Verify enabled APIs
gcloud services list --enabled1.5 Minimal IAM permissions
For this exercise, the Terraform Service Account must be able to create Compute Engine resources and read the project. The roles/editor role (used in 1.3) largely covers the need. In production, prefer targeted roles such as roles/compute.admin + roles/iam.serviceAccountUser (least privilege).
1.6 SSH key for Compute Engine
To connect via SSH to the VM (outside of IAP), GCP supports two modes: OS Login (recommended, auth via IAM — used in this lesson) or classic SSH keys via project metadata. Here is how to generate a dedicated GCP key:
# Generate a 4096-bit RSA key pair dedicated to GCP
ssh-keygen -t rsa -b 4096 -f ~/.ssh/gcp_vm -C "votre-user@gcp"
# Option A: OS Login enabled on the VM (used in this lesson)
# → the public key is managed by IAM, nothing to push to metadata
# Option B: Add via project metadata (classic SSH key)
gcloud compute project-info add-metadata \
--metadata-from-file ssh-keys=~/.ssh/gcp_vm.pub
# Restrict private key permissions (mandatory)
chmod 400 ~/.ssh/gcp_vm2. Project Structure
Here is the typical directory tree for this lesson. The code is organized into several .tf files according to their responsibility, plus an external Bash script injected into the VM via metadata_startup_script.
# Recommended structure for this project premier-projet-gcp/ ├── versions.tf # Terraform configuration + google provider ├── variables.tf # Input variables (project_id, region, zone, ...) ├── main.tf # VPC + subnet + firewall rules + Compute Engine VM ├── outputs.tf # Public IP, IAP SSH command, HTTP URL ├── startup-script.sh # Bash script executed at VM boot (Nginx) ├── terraform.tfvars # Concrete values (project_id, ...) — NOT COMMITTED └── .gitignore # Exclude .tfstate, .tfvars, .terraform/, key.json
2.1 Role of each file
| File | Role | Read by Terraform? |
|---|---|---|
versions.tf | terraform { required_providers } block + provider "google" block | Yes |
variables.tf | Input variables with type, default, description | Yes |
main.tf | All GCP resource and data sources | Yes |
outputs.tf | Outputs exposed after apply (IP, URL, IAP SSH command) | Yes |
startup-script.sh | Bash script injected into the VM via the file() function | Read by file() |
terraform.tfvars | Concrete variable values (project_id = "...") | Auto-loaded |
.gitignore | Exclude *.tfstate, *.tfvars, .terraform/, key.json | No (Git) |
2.2 How Terraform loads files
Chapter 08 – Lesson 1: GCP Fundamentals and Terraform Authentication
gcloud CLI, create a Service Account, enable required APIs, and configure Terraform's google provider.1. GCP Hierarchy vs AWS and Azure
| Level | AWS | Azure | GCP |
|---|---|---|---|
| Top-level | Organization | Tenant | Organization |
| Intermediate container | OU | Management Group | Folder |
| Billing account | Account | Subscription | Project (linked to Billing Account) |
| Region | Region | Location | Region (us-central1, europe-west1, northamerica-northeast1...) |
| Zone | AZ | Availability Zone | Zone (us-central1-a, -b, -c) |
| Service identity | IAM Role | Service Principal / MI | Service Account |
project.2. Install gcloud CLI
Windows (PowerShell)
Download and run the official Google Cloud SDK installer for Windows. Once launched, the installer configures gcloud, gsutil and bq, and offers to initialize the connection to your GCP account.
(New-Object Net.WebClient).DownloadFile("https://dl.google.com/dl/cloudsdk/channels/rapid/GoogleCloudSDKInstaller.exe", "$env:Temp\GoogleCloudSDKInstaller.exe")
& $env:Temp\GoogleCloudSDKInstaller.exemacOS
Install gcloud via Homebrew (the most widely used macOS package manager). The cask installs the full SDK and automatically adds it to PATH.
brew install --cask google-cloud-sdk
Linux (apt)
Official Debian/Ubuntu procedure: add the Google APT repository, import the signed GPG key, then install google-cloud-cli. This method allows benefiting from automatic updates via apt.
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" \
| sudo tee /etc/apt/sources.list.d/google-cloud-sdk.list
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg \
| sudo gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg
sudo apt update && sudo apt install -y google-cloud-cliQuick validation of the installation: the command displays the SDK versions, bq (BigQuery) and gsutil (Storage). An empty output indicates a PATH problem to fix before continuing.
# Verify gcloud --version # Google Cloud SDK 488.x.x # bq 2.x.x # gsutil 5.x.x
3. Create and Configure a GCP Project
Complete GCP project bootstrap in CLI:
# 1. Login (opens the browser) gcloud auth login # 2. Create a project (project_id must be globally unique) PROJECT_ID="monprojet-tf-$(date +%s)" gcloud projects create $PROJECT_ID --name="Mon Projet Terraform" # 3. Link to billing account (mandatory for most resources) BILLING_ID=$(gcloud billing accounts list --format="value(ACCOUNT_ID)" --limit=1) gcloud billing projects link $PROJECT_ID --billing-account=$BILLING_ID # 4. Set active project gcloud config set project $PROJECT_ID gcloud config set compute/region us-central1 gcloud config set compute/zone us-central1-a # 5. Verify gcloud config list # [compute] # region = us-central1 # zone = us-central1-a # [core] # project = monprojet-tf-1714234567
4. Enable Required APIs
Batch activation of the most common GCP APIs needed for Compute, Storage, IAM, Cloud SQL, Cloud Run and Secret Manager. The second command (gcloud services list --enabled) lets you check what is already enabled on the project.
# Enable common APIs
gcloud services enable \
compute.googleapis.com `# Compute Engine` \
storage.googleapis.com `# Cloud Storage` \
cloudresourcemanager.googleapis.com `# Resource Manager` \
iam.googleapis.com `# IAM` \
iamcredentials.googleapis.com `# IAM Credentials (for SA tokens)` \
sqladmin.googleapis.com `# Cloud SQL Admin` \
run.googleapis.com `# Cloud Run` \
secretmanager.googleapis.com `# Secret Manager` \
--project=$PROJECT_ID
# List enabled APIs
gcloud services list --enabled --project=$PROJECT_IDChapter 03 – Lesson 4: Serverless Project — Lambda, API Gateway and VPC with Modules
1. Prerequisites
This project deploys 3 Terraform modules that together orchestrate a VPC, a Lambda function and an API Gateway. Before running terraform apply, verify that all the elements below are in place — otherwise you will get IAM errors, Lambda invocation timeouts or 500 errors on the API.
1.1 Accounts and tools
1.2 AWS credentials to retrieve
You will need these 3 pieces of information from the AWS console to authenticate Terraform:
| Item | Where to find it | Example |
|---|---|---|
| Account ID (12 digits) | AWS Console → top right, click your name → My Account | 123456789012 |
| Access Key ID | IAM → Users → your user → Security credentials → Create access key | AKIAIOSFODNN7EXAMPLE |
| Secret Access Key | Displayed ONLY ONCE during creation (otherwise recreate a new key) | wJalrXUt...EXAMPLEKEY |
aws configure (stored in ~/.aws/credentials) or environment variables. Add *.tfvars, *.tfstate*, .terraform/ and dist/ to your .gitignore.1.3 Configure AWS CLI authentication
One time on your machine: run aws configure and paste your keys. The file ~/.aws/credentials is created, and Terraform will read it automatically (the AWS provider and the archive provider do not need any specific token).
aws configure
# AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
# AWS Secret Access Key [None]: wJalrXUt...EXAMPLEKEY
# Default region name [None]: ca-central-1
# Default output format [None]: json
# Verify it works
aws sts get-caller-identity
# {
# "UserId": "AIDA...EXAMPLE",
# "Account": "123456789012", ← Your Account ID
# "Arn": "arn:aws:iam::123456789012:user/votre-user"
# }1.4 Minimal IAM permissions
The user (or role) used must be able to create Lambda, API Gateway, IAM (execution roles), VPC, and CloudWatch Logs. For this exercise, attach the following managed policies to the IAM user:
| AWS managed policy | Why |
|---|---|
AWSLambda_FullAccess | Create/modify the Lambda function and aws_lambda_permission |
AmazonAPIGatewayAdministrator | Create the REST API, methods, integrations and deployment |
IAMFullAccess | Create the Lambda execution role and attach policies to it |
AmazonVPCFullAccess | Create the VPC, subnets and Security Group of the vpc module |
CloudWatchLogsFullAccess | Create the Lambda aws_cloudwatch_log_group |
PowerUserAccess + IAMFullAccess is also sufficient.1.5 Lambda source code
The data "archive_file" module (used in the root main.tf) automatically zips your Python code at each plan. You must create the source folder before the first terraform plan, otherwise Terraform will fail with « source_dir does not exist »:
# Create the structure expected by data "archive_file" mkdir -p src/lambda touch src/lambda/index.py # Content will be seen in section 8 # No SSH/.pem needed here — Lambda is invoked via API Gateway, not SSH
2. Project Structure
Here is the complete project directory tree, designed for modularity. The root main.tf acts as the orchestrator: it instantiates the 3 modules and connects their outputs/inputs. Each module in modules/ is self-contained (it has its own variables.tf and outputs.tf) and could be reused in another project.
# Complete serverless project structure
project-serverless/
├── providers.tf # Terraform versions + AWS + archive provider
├── variables.tf # Global variables (project_name, environment, region)
├── main.tf # Orchestration: module.vpc, module.lambda, module.api_gateway calls
├── outputs.tf # Root outputs (api_url, lambda_name, lambda_arn)
├── terraform.tfvars # Concrete values (NOT COMMITTED)
├── .gitignore # *.tfstate, *.tfvars, .terraform/, dist/
├── README.md
│
├── src/
│ └── lambda/
│ └── index.py # Lambda Python code
│
├── dist/ # ZIP generated by data "archive_file" (NOT COMMITTED)
│ └── lambda.zip
│
├── environments/ # Environment-specific values
│ ├── dev.tfvars
│ └── prod.tfvars
│
└── modules/
├── vpc/ # Reusable network module
│ ├── main.tf # aws_vpc, aws_subnet, aws_security_group
│ ├── variables.tf # prefix, cidr, environment
│ └── outputs.tf # vpc_id, private_subnets, lambda_sg_id
│
├── lambda/ # Lambda function + IAM module
│ ├── main.tf # aws_lambda_function, aws_cloudwatch_log_group
│ ├── iam.tf # Execution role + policy attachments
│ ├── variables.tf # function_name, runtime, handler, vpc_id...
│ └── outputs.tf # function_name, function_arn, invoke_arn
│
└── api_gateway/ # REST API module
├── main.tf # aws_api_gateway_rest_api, methods, deployment
├── variables.tf # api_name, lambda_arn, lambda_name
└── outputs.tf # api_id, api_url, stage2.1 Role of each file
| File | Role | Level |
|---|---|---|
providers.tf | Terraform versions + providers (aws, archive) | Root |
variables.tf | Global variables shared across all modules | Root |
main.tf (root) | Instantiates modules and passes outputs from one as inputs to another | Root |
outputs.tf (root) | Re-exposes module outputs at project level (e.g. api_url) | Root |
modules/<m>/main.tf | Module business logic (AWS resources) | Module |
modules/<m>/variables.tf | Module public interface — what a consumer can configure | Module |
modules/<m>/outputs.tf | Values exposed by the module (consumable by root or another module) | Module |
src/lambda/index.py | Python application code — automatically zipped by data "archive_file" | App |
environments/*.tfvars | Concrete values per environment (passed via -var-file) | Config |
2.2 How Terraform loads files — there is NO "entry point"
main.tf first. It automatically merges ALL .tf files in the current directory into a single set. The names main.tf, iam.tf, variables.tf are a convention, not a technical requirement. In the lambda/ module, separating iam.tf from main.tf improves readability — for Terraform, it is strictly equivalent to a single large file.This article covers the most useful excerpts — the complete Terraform Infrastructure Code course (10 chapters, 38 lessons, corrected exercises and final project) takes you all the way.
./access-the-complete-course free course: Mastering Claude CodeFAQ
How long does it take to learn Terraform Infrastructure Code?
Are there any prerequisites?
Where to start concretely?
📬 Want to receive this type of guide every week? Subscribe for free — real code, zero fluff.