Terraform essentials: 50 cards on HCL, state, modules and workflows
Terraform flashcards: HCL, providers, resources, variables and outputs, state and remote backends, plan and apply, modules, count, for_each, drift and import.
Practice now
Study without an account
Scheduled with the default Askesis timings. Progress is saved in this browser.
All 50 cards
- 1What is Terraform?An infrastructure-as-code tool: you declare the desired infrastructure in configuration files, and Terraform works out and makes the API calls needed to reach that stateIt is declarative: you describe the end result, not the steps to get there.
- 2HCL block syntaxhcl
<BLOCK TYPE> "<LABEL>" "<LABEL>" { <ARGUMENT> = <EXPRESSION> }HCL is the HashiCorp Configuration Language. Terraform loads every.tffile in a directory as one configuration. - 3The core Terraform workflowbash
terraform init # download providers and modules, set up the backend terraform plan # preview the changes terraform apply # make the changesterraform destroyremoves everything the configuration manages. - 4When do you need to run
terraform init?In a new or freshly cloned directory, and again after adding or changing providers, modules or the backend configuration. It is safe to run repeatedly - 5What is
.terraform.lock.hclfor?It records the exact provider versions and checksums chosen byterraform init, so everyone gets the same providers. Commit it to version controlRunterraform init -upgradeto move to newer versions allowed by your constraints. Module versions are not locked. - 6Declare and configure the AWS providerhcl
terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 6.0" } } } provider "aws" { region = "eu-central-1" }A provider is a plugin that talks to one API (AWS, Azure, Google Cloud, Kubernetes, GitHub…) and supplies its resource types. - 7What does the version constraint
~> 6.0allow?Any version from 6.0 up to, but not including, 7.0: only the rightmost given component may increase~> 6.0.1allows 6.0.1 up to, but not including, 6.1.0. - 8Use a second provider configuration for another regionhcl
provider "aws" { alias = "us_east_1" region = "us-east-1" } resource "aws_acm_certificate" "cdn" { provider = aws.us_east_1 domain_name = "example.com" validation_method = "DNS" }A real use: certificates for CloudFront must be created inus-east-1. - 9Resource blockhcl
resource "aws_s3_bucket" "logs" { bucket = "acme-app-logs" }The first label is the resource type, the second a local name; together they form the addressaws_s3_bucket.logs. - 10Reference another resource’s attributeUse
<TYPE>.<NAME>.<ATTRIBUTE>, such asaws_instance.web.public_ip. The reference also tells Terraform to create the referenced resource first - 11When do you need
depends_on?Only for hidden dependencies that Terraform can’t infer from references, such as an app that needs an IAM policy to exist before it startsPrefer real references where possible;depends_onmakes plans more conservative. - 12Declare an input variable with a defaulthcl
variable "instance_type" { type = string default = "t3.micro" description = "EC2 instance size" }Use it asvar.instance_type. A variable without a default must be given a value. - 13Variable value sources, from lowest to highest precedenceDefault value, then
TF_VAR_nameenvironment variables,terraform.tfvars,terraform.tfvars.json,*.auto.tfvarsfiles (in alphabetical order), and finally-varand-var-fileon the command line - 14Restrict a variable to dev, staging or prodhcl
variable "env" { type = string validation { condition = contains(["dev", "staging", "prod"], var.env) error_message = "env must be dev, staging or prod." } } - 15Does
sensitive = truekeep a secret out of the state file?No. It only hides the value in plan and apply output; the value is still stored in plain text in state and saved plan filesProtect state with an encrypted backend and tight access. Recent versions add ephemeral values and write-only arguments that are never stored. - 16Output valuehcl
output "web_ip" { value = aws_instance.web.public_ip }Outputs are printed afterapply, shown byterraform outputand exposed to a parent module asmodule.<NAME>.web_ip. - 17Local valueshcl
locals { common_tags = { team = "platform" project = var.project } } # referenced as local.common_tagsThe block islocals(plural), but references uselocal.(singular). - 18String interpolation and conditional expressionshcl
bucket = "${var.project}-${var.env}-assets" instance_type = var.env == "prod" ? "m7i.large" : "t3.micro" - 19Terraform’s type constraintsPrimitive:
string,number,bool. Collections:list(...),set(...),map(...). Structural:object({...})andtuple([...]).anyaccepts anything - 20What does
cidrsubnet("10.0.0.0/16", 8, 1)return?"10.0.1.0/24": it adds 8 bits to the prefix (/16 becomes /24) and picks subnet number 1Try expressions like this interactively interraform console. - 21What is Terraform state?A JSON file (
terraform.tfstate) that maps each resource in your configuration to the real object it manages, with its ID and attributes; Terraform compares it with the config and reality to plan changes - 22Why shouldn’t state live in Git or on a laptop?It can contain secrets in plain text, and local files can’t be locked or shared safely, so concurrent runs or stale copies corrupt it. Use a remote backendA typical
.gitignorecovers.terraform/,*.tfstateand*.tfstate.*, but not.terraform.lock.hcl. - 23Remote backendWhere Terraform stores state outside your machine, such as S3, Azure Blob Storage, Google Cloud Storage or HCP Terraform, giving shared access, locking, encryption and versioning
- 24Store state in S3 with lockinghcl
terraform { backend "s3" { bucket = "acme-tf-state" key = "prod/network.tfstate" region = "eu-central-1" encrypt = true use_lockfile = true } }use_lockfileuses S3’s own locking (Terraform 1.11+). Older setups used a DynamoDB table, which is now deprecated. Enable versioning on the bucket. - 25What is state locking for?It stops two runs from writing the same state at once. If a crashed run leaves a stale lock, release it with
terraform force-unlock <LOCK_ID>Only force-unlock when you are sure no other run is in progress. - 26Can a backend block use variables?No. Backend settings can’t reference variables, locals or data sources; pass environment-specific values at init time with partial configurationbash
terraform init -backend-config=prod.s3.tfbackend - 27Plan symbols
+,-,~and-/++create,-destroy,~update in place,-/+destroy and then create a replacement+/-means create the replacement first, then destroy the old one (create_before_destroy). - 28Apply exactly the plan that was reviewedbash
terraform plan -out=tfplan terraform apply tfplanApplying a saved plan skips the approval prompt. The plan file can contain secrets, so treat it like state. - 29What does “forces replacement” mean in a plan?You changed an argument the provider can’t update in place, such as an instance’s AMI, so Terraform will destroy the resource and create a new one
- 30Protect a database from being destroyedhcl
resource "aws_db_instance" "main" { # ... lifecycle { prevent_destroy = true } }Otherlifecycleoptions:create_before_destroy,ignore_changesandreplace_triggered_by. - 31Call a modulehcl
module "web" { source = "./modules/web-server" instance_type = "t3.small" }A module is a directory of.tffiles with its own variables and outputs. Runterraform initafter adding one. - 32Where can a module’s
sourcepoint?A local path (./modules/vpc), the Terraform Registry (<NAMESPACE>/<NAME>/<PROVIDER>plus aversion), a Git repository (git::https://…?ref=v1.2.0), or an HTTP or S3 URLTheversionargument only works for registry modules; pin Git sources withref. - 33Read a value from a child moduleUse
module.<NAME>.<OUTPUT>, such asmodule.vpc.private_subnet_ids; only values the module declares as outputs are visible - 34Look up the latest Ubuntu 24.04 AMI with a data sourcehcl
data "aws_ami" "ubuntu" { most_recent = true owners = ["099720109477"] # Canonical filter { name = "name" values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"] } }Data sources read existing information without managing it. Use the result asdata.aws_ami.ubuntu.id. - 35Create three similar instances with
counthclresource "aws_instance" "web" { count = 3 ami = data.aws_ami.ubuntu.id instance_type = "t3.micro" tags = { Name = "web-${count.index}" } }Refer to one asaws_instance.web[0].count = var.enabled ? 1 : 0makes a resource optional. - 36Create one IAM user per name with
for_eachhclresource "aws_iam_user" "team" { for_each = toset(["alice", "bob"]) name = each.key }Instances are addressed by key, such asaws_iam_user.team["alice"]. With a map,each.valueholds the value. - 37Why prefer
for_eachovercountfor a list of distinct things?Withcount, removing an item from the middle shifts every later index, so Terraform replaces those resources;for_eachaddresses instances by stable keys - 38Why does
for_each = var.namesfail whennamesis a list?for_eachaccepts only a map or a set of strings. Convert the list withtoset(var.names)A block can’t usecountandfor_eachtogether, and their values must be known at plan time. - 39
forexpressionshclupper_names = [for n in var.names : upper(n)] roles = { for u in var.users : u.name => u.role } - 40Generate repeated nested blocks from a listhcl
dynamic "ingress" { for_each = var.ports content { from_port = ingress.value to_port = ingress.value protocol = "tcp" cidr_blocks = ["10.0.0.0/16"] } }Current AWS provider guidance prefers separateaws_vpc_security_group_ingress_ruleresources over inline rules. - 41Terraform CLI workspacesSeparate state files for the same configuration and backend, managed with
terraform workspace new,selectandlist; the current name is available asterraform.workspaceFine for similar, short-lived copies. For environments that need different credentials or access controls, such as prod, HashiCorp recommends separate configurations or backends. - 42DriftReal infrastructure has changed outside Terraform, for example in the console, so it no longer matches the configuration and state.
terraform planshows it as changes to undo - 43Accept out-of-band changes into state without changing infrastructurebash
terraform plan -refresh-only # review the drift terraform apply -refresh-only # write it to stateThis replaces the deprecatedterraform refresh. Update the configuration too, or the next plan will try to revert the change. - 44Bring an existing resource under Terraform managementhcl
import { to = aws_s3_bucket.logs id = "acme-app-logs" }Then runterraform plan -generate-config-out=generated.tfto draft the matching resource block. The older CLI form isterraform import aws_s3_bucket.logs acme-app-logs. - 45Rename a resource without destroying ithcl
moved { from = aws_instance.web to = aws_instance.app }Without amovedblock, Terraform sees one resource deleted and another created.terraform state mvdoes the same imperatively. - 46Stop managing a resource but keep it runninghcl
removed { from = aws_instance.legacy lifecycle { destroy = false } }The older imperative equivalent isterraform state rm aws_instance.legacy. - 47List and inspect resources in statebash
terraform state list terraform state show aws_instance.web - 48Force Terraform to recreate one resourcebash
terraform apply -replace="aws_instance.web"This replaces the deprecatedterraform taint. - 49
terraform fmtvsterraform validatefmtrewrites files in the canonical style (fmt -checkfails CI on unformatted code);validatechecks syntax, types and references without contacting any remote APIs - 50OpenTofuAn open-source fork of Terraform under the MPL 2.0 license, created after HashiCorp moved Terraform to the Business Source License in 2023; it is run by the Linux Foundation and uses the
tofucommandIts HCL and workflow are largely compatible with Terraform.
Frequently asked questions
Which Terraform version does the deck assume?
A recent 1.x release (1.11 or newer), which has import, moved and removed blocks and native S3 state locking. Older versions differ mainly in those areas.
Does it apply to OpenTofu?
Almost entirely. OpenTofu is a fork of Terraform with the same HCL language and workflow; you run tofu instead of terraform.
Do I need to know AWS?
It helps but isn’t required. Examples use AWS resources because they are the most common, but the concepts apply to every provider. The AWS essentials deck covers the services themselves.
More in IT & DevOps
Linux command line
Linux commands and files for sysadmins and LPIC-1 candidates: find and grep, permissions and special bits, fstab, systemd targets, package managers, users, cron and more.
50 cardsLinux filesystem basics
Learn the Linux directory tree: what /etc, /var, /usr, /proc and /dev are for, plus passwd, shadow, fstab and hosts. Free flashcards, no signup.
50 cardsIntermediate Linux filesystem
Linux paths admins use daily: /usr/local, /var/lib, /run, /sys, /etc/skel, sudoers, Bash startup files, SSH keys, resolv.conf and /proc. Free flashcards.
50 cardsAdvanced Linux filesystem
Deeper Linux files for LPIC-1: profile.d, sudoers.d, systemd units, GRUB config, nsswitch.conf, ld.so.conf, distro logs and /lib/modules. Free flashcards.
50 cardsBash scripting
Bash scripting flashcards: shebangs, quoting, exit codes, tests, loops, functions, arrays, parameter expansion, redirection, set -euo pipefail and traps.
50 cardsVim
Learn Vim from the keyboard up: modes, motions, operators and text objects, search and :s substitutions, registers, macros, visual mode and windows.
50 cardsNetworking
Computer networking fundamentals: OSI and TCP/IP layers, TCP vs UDP, common port numbers, subnetting and CIDR, DNS, DHCP and ARP, and IPv6 basics.
50 cardsCyber security
Cyber security flashcards: the CIA triad, phishing, XSS, CSRF, SQL injection, MITM, hashing vs encryption, TLS, MFA, least privilege, OWASP and zero trust.
50 cardsDocker
Learn Docker from images and layers to multi-stage builds, volumes, networks and Compose, with the commands and best practices you use daily.
50 cardsKubernetes
Kubernetes fundamentals for developers and CKA or CKAD candidates: Pods, Deployments, Services, storage, probes, resources and scheduling, plus the kubectl commands you use daily.
50 cardsAWS
AWS flashcards: regions and AZs, EC2, S3 storage classes, IAM users, roles and policies, VPC networking, RDS, Lambda, CloudWatch and Route 53.
50 cards