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.

Terraform Infrastructure Code in Practice: The Code and Commands That Really Matter

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.

tl;dr
  • Introduction to Terraform and IaC
  • Installation and First Steps
  • HCL Language Providers Variables and Resources
  • Modules and Code Organization
  • Remote State Backend and Workspaces
~$ cat ./parcours.md # Terraform Infrastructure Code — 10 chapters
01
Introduction to Terraform and IaC
→ Chapter 00 – Lesson 1 : Why IaC ? Why Terraform ?→ Chapter 00 – Lesson 2 : The Terraform Lifecycle — init, plan, apply, destroy+ 1 more lessons
02
Installation and First Steps
→ Chapter 01 – Lesson 1 : Installing Terraform on Windows, Linux and macOS→ Chapter 01 – Lesson 2 : First Project — Deploying an EC2 Instance on AWS+ 1 more lessons
03
HCL Language Providers Variables and Resources
→ Chapter 02 – Lesson 1a : HCL Syntax and Fundamental Blocks→ Chapter 02 – Lesson 1b : Types, Expressions and Local Variables+ 4 more lessons
04
Modules and Code Organization
→ Chapter 03 – Lesson 1 : Creating and Using Local Modules→ Chapter 03 – Lesson 2 : Terraform Registry, Public Modules and Versioning+ 2 more lessons
05
Remote State Backend and Workspaces
→ Chapter 04 – Lesson 1 : Managing Terraform State — terraform.tfstate→ Chapter 04 – Lesson 2 : Remote Backend — S3 + DynamoDB and Terraform Cloud+ 1 more lessons
06
CICD Security and Best Practices
→ Chapter 05 – Lesson 1a : Terraform Pipeline with GitHub Actions and OIDC→ Chapter 05 – Lesson 1b : Terraform Pipeline with Jenkins+ 3 more lessons
07
AWS Labs and Practical Guides
→ Chapter 06 – Lab 01 : Static S3 Site + CloudFront (Step by Step)→ Chapter 06 – Lab 02 : Reusable Multi-Environment VPC Module+ 2 more lessons
08
Terraform with Microsoft Azure
→ Chapter 07 – Lesson 1 : Azure Fundamentals and Terraform Authentication→ Chapter 07 – Lesson 2 : First Azure Project — VNet, NSG and Linux VM+ 2 more lessons
🏁
Final project (+ 2 chapters along the way)
→ You leave with a concrete and demonstrable project

Chapter 08 – Lesson 2: First GCP Project — VPC, Firewall and Compute Engine

NOTEObjective — Deploy a basic GCP infrastructure: a custom-mode VPC with regional subnet, firewall rules (SSH + HTTP), and a Compute Engine instance with Nginx server provisioned via startup script.

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:

ItemHow to obtain itExample
Project ID (short string)gcloud config get-value projectmon-projet-tf-prod
Project Number (12 digits)gcloud projects describe PROJECT_ID --format="value(projectNumber)"123456789012
Organization ID (optional)gcloud organizations list987654321000
Billing Account IDgcloud billing accounts list0X0X0X-0Y0Y0Y-0Z0Z0Z
Service Account emailStandard Terraform SA formattf-sa@PROJECT_ID.iam.gserviceaccount.com
WARNINGCritical security — NEVER commit the Service Account JSON key to Git. In production, prefer Workload Identity Federation (OIDC) which completely removes the need for static keys. For local dev, 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.

bash
# 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 project

1.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:

bash
gcloud services enable \
    compute.googleapis.com \
    cloudresourcemanager.googleapis.com \
    iam.googleapis.com \
    iap.googleapis.com

# Verify enabled APIs
gcloud services list --enabled

1.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:

bash
# 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_vm
WARNINGSA JSON key ≠ SSH key — Do not confuse the Service Account JSON key (Terraform auth to GCP APIs) with the SSH key (user auth to the VM). The first is extremely sensitive: its leak grants access to all project resources. Always encrypt it at rest, rotate it regularly and revoke it as soon as it is no longer needed.

2. 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.

bash
# 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

FileRoleRead by Terraform?
versions.tfterraform { required_providers } block + provider "google" blockYes
variables.tfInput variables with type, default, descriptionYes
main.tfAll GCP resource and data sourcesYes
outputs.tfOutputs exposed after apply (IP, URL, IAP SSH command)Yes
startup-script.shBash script injected into the VM via the file() functionRead by file()
terraform.tfvarsConcrete variable values (project_id = "...")Auto-loaded
.gitignoreExclude *.tfstate, *.tfvars, .terraform/, key.jsonNo (Git)

2.2 How Terraform loads files

Chapter 08 – Lesson 1: GCP Fundamentals and Terraform Authentication

NOTEObjective — Understand the GCP hierarchy (Organization, Folder, Project), install and configure the gcloud CLI, create a Service Account, enable required APIs, and configure Terraform's google provider.
TIPPrerequisites — Completed Chapters 00 to 04. Have a GCP account (300 USD free for 90 days: cloud.google.com/free).

1. GCP Hierarchy vs AWS and Azure

LevelAWSAzureGCP
Top-levelOrganizationTenantOrganization
Intermediate containerOUManagement GroupFolder
Billing accountAccountSubscriptionProject (linked to Billing Account)
RegionRegionLocationRegion (us-central1, europe-west1, northamerica-northeast1...)
ZoneAZAvailability ZoneZone (us-central1-a, -b, -c)
Service identityIAM RoleService Principal / MIService Account
WARNINGMajor difference — On GCP, every resource belongs to a Project. The Project is the fundamental unit of billing, IAM, and quotas. The vast majority of GCP resources require an explicit 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.

bash
(New-Object Net.WebClient).DownloadFile("https://dl.google.com/dl/cloudsdk/channels/rapid/GoogleCloudSDKInstaller.exe", "$env:Temp\GoogleCloudSDKInstaller.exe")
& $env:Temp\GoogleCloudSDKInstaller.exe

macOS

Install gcloud via Homebrew (the most widely used macOS package manager). The cask installs the full SDK and automatically adds it to PATH.

bash
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.

bash
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-cli

Quick 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.

bash
# 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:

bash
# 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

WARNINGGCP requires explicit API activation — Unlike AWS, where services are available immediately, on GCP you must enable each API before use. The first Terraform errors are almost always related to an unenabled API.

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.

bash
# 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_ID

Chapter 03 – Lesson 4: Serverless Project — Lambda, API Gateway and VPC with Modules

NOTEObjective — Deploy a complete serverless architecture on AWS with Terraform: a VPC, a Lambda function, and an API Gateway — all organized into reusable modules. This project illustrates how Terraform modules assemble to form production infrastructure.

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:

ItemWhere to find itExample
Account ID (12 digits)AWS Console → top right, click your name → My Account123456789012
Access Key IDIAM → Users → your user → Security credentials → Create access keyAKIAIOSFODNN7EXAMPLE
Secret Access KeyDisplayed ONLY ONCE during creation (otherwise recreate a new key)wJalrXUt...EXAMPLEKEY
WARNINGCritical security — NEVER commit these keys to Git. Always configure them via 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).

bash
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 policyWhy
AWSLambda_FullAccessCreate/modify the Lambda function and aws_lambda_permission
AmazonAPIGatewayAdministratorCreate the REST API, methods, integrations and deployment
IAMFullAccessCreate the Lambda execution role and attach policies to it
AmazonVPCFullAccessCreate the VPC, subnets and Security Group of the vpc module
CloudWatchLogsFullAccessCreate the Lambda aws_cloudwatch_log_group
TIPIn production — Restrict these permissions according to the least privilege principle (see Chapter 05). For a learning account, 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 »:

bash
# 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
NOTEDifference with EC2 — Unlike the EC2 project in Chapter 01, this lab requires no SSH key pair. Lambda is a managed service: you deploy code, AWS handles the server.

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.

bash
# 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, stage

2.1 Role of each file

FileRoleLevel
providers.tfTerraform versions + providers (aws, archive)Root
variables.tfGlobal variables shared across all modulesRoot
main.tf (root)Instantiates modules and passes outputs from one as inputs to anotherRoot
outputs.tf (root)Re-exposes module outputs at project level (e.g. api_url)Root
modules/<m>/main.tfModule business logic (AWS resources)Module
modules/<m>/variables.tfModule public interface — what a consumer can configureModule
modules/<m>/outputs.tfValues exposed by the module (consumable by root or another module)Module
src/lambda/index.pyPython application code — automatically zipped by data "archive_file"App
environments/*.tfvarsConcrete values per environment (passed via -var-file)Config

2.2 How Terraform loads files — there is NO "entry point"

NOTEKey concept — Terraform does not read 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.
va-plus-loin

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 Code

FAQ

How long does it take to learn Terraform Infrastructure Code?
With a structured progression (10 chapters, 38 short and practical lessons), you reach an operational level in a few weeks at 30 to 60 minutes per day. The key is to practice each concept immediately.
Are there any prerequisites?
It is best to be comfortable with the domain fundamentals: this content goes in depth, with real-world cases.
Where to start concretely?
Reproduce the commands in this article, then follow the complete Terraform Infrastructure Code course: it chains the 38 lessons in order, with exercises and a final project.

📬 Want to receive this type of guide every week? Subscribe for free — real code, zero fluff.