askesis

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.

IT & DevOpsBeginner50 cardsFree

Practice now

Study without an account

0 of 50 seen0 graduated
What is Terraform?

Scheduled with the default Askesis timings. Progress is saved in this browser.

All 50 cards

  1. 1
    What 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 state
    It is declarative: you describe the end result, not the steps to get there.
  2. 2
    HCL block syntax
    hcl
    <BLOCK TYPE> "<LABEL>" "<LABEL>" {
      <ARGUMENT> = <EXPRESSION>
    }
    HCL is the HashiCorp Configuration Language. Terraform loads every .tf file in a directory as one configuration.
  3. 3
    The core Terraform workflow
    bash
    terraform init     # download providers and modules, set up the backend
    terraform plan     # preview the changes
    terraform apply    # make the changes
    terraform destroy removes everything the configuration manages.
  4. 4
    When 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
  5. 5
    What is .terraform.lock.hcl for?
    It records the exact provider versions and checksums chosen by terraform init, so everyone gets the same providers. Commit it to version control
    Run terraform init -upgrade to move to newer versions allowed by your constraints. Module versions are not locked.
  6. 6
    Declare and configure the AWS provider
    hcl
    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.
  7. 7
    What does the version constraint ~> 6.0 allow?
    Any version from 6.0 up to, but not including, 7.0: only the rightmost given component may increase
    ~> 6.0.1 allows 6.0.1 up to, but not including, 6.1.0.
  8. 8
    Use a second provider configuration for another region
    hcl
    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 in us-east-1.
  9. 9
    Resource block
    hcl
    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 address aws_s3_bucket.logs.
  10. 10
    Reference another resource’s attribute
    Use <TYPE>.<NAME>.<ATTRIBUTE>, such as aws_instance.web.public_ip. The reference also tells Terraform to create the referenced resource first
  11. 11
    When 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 starts
    Prefer real references where possible; depends_on makes plans more conservative.
  12. 12
    Declare an input variable with a default
    hcl
    variable "instance_type" {
      type        = string
      default     = "t3.micro"
      description = "EC2 instance size"
    }
    Use it as var.instance_type. A variable without a default must be given a value.
  13. 13
    Variable value sources, from lowest to highest precedence
    Default value, then TF_VAR_name environment variables, terraform.tfvars, terraform.tfvars.json, *.auto.tfvars files (in alphabetical order), and finally -var and -var-file on the command line
  14. 14
    Restrict a variable to dev, staging or prod
    hcl
    variable "env" {
      type = string
    
      validation {
        condition     = contains(["dev", "staging", "prod"], var.env)
        error_message = "env must be dev, staging or prod."
      }
    }
  15. 15
    Does sensitive = true keep 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 files
    Protect state with an encrypted backend and tight access. Recent versions add ephemeral values and write-only arguments that are never stored.
  16. 16
    Output value
    hcl
    output "web_ip" {
      value = aws_instance.web.public_ip
    }
    Outputs are printed after apply, shown by terraform output and exposed to a parent module as module.<NAME>.web_ip.
  17. 17
    Local values
    hcl
    locals {
      common_tags = {
        team    = "platform"
        project = var.project
      }
    }
    
    # referenced as local.common_tags
    The block is locals (plural), but references use local. (singular).
  18. 18
    String interpolation and conditional expressions
    hcl
    bucket        = "${var.project}-${var.env}-assets"
    instance_type = var.env == "prod" ? "m7i.large" : "t3.micro"
  19. 19
    Terraform’s type constraints
    Primitive: string, number, bool. Collections: list(...), set(...), map(...). Structural: object({...}) and tuple([...]). any accepts anything
  20. 20
    What 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 1
    Try expressions like this interactively in terraform console.
  21. 21
    What 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
  22. 22
    Why 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 backend
    A typical .gitignore covers .terraform/, *.tfstate and *.tfstate.*, but not .terraform.lock.hcl.
  23. 23
    Remote backend
    Where 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
  24. 24
    Store state in S3 with locking
    hcl
    terraform {
      backend "s3" {
        bucket       = "acme-tf-state"
        key          = "prod/network.tfstate"
        region       = "eu-central-1"
        encrypt      = true
        use_lockfile = true
      }
    }
    use_lockfile uses S3’s own locking (Terraform 1.11+). Older setups used a DynamoDB table, which is now deprecated. Enable versioning on the bucket.
  25. 25
    What 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.
  26. 26
    Can 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 configuration
    bash
    terraform init -backend-config=prod.s3.tfbackend
  27. 27
    Plan 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).
  28. 28
    Apply exactly the plan that was reviewed
    bash
    terraform plan -out=tfplan
    terraform apply tfplan
    Applying a saved plan skips the approval prompt. The plan file can contain secrets, so treat it like state.
  29. 29
    What 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
  30. 30
    Protect a database from being destroyed
    hcl
    resource "aws_db_instance" "main" {
      # ...
      lifecycle {
        prevent_destroy = true
      }
    }
    Other lifecycle options: create_before_destroy, ignore_changes and replace_triggered_by.
  31. 31
    Call a module
    hcl
    module "web" {
      source        = "./modules/web-server"
      instance_type = "t3.small"
    }
    A module is a directory of .tf files with its own variables and outputs. Run terraform init after adding one.
  32. 32
    Where can a module’s source point?
    A local path (./modules/vpc), the Terraform Registry (<NAMESPACE>/<NAME>/<PROVIDER> plus a version), a Git repository (git::https://…?ref=v1.2.0), or an HTTP or S3 URL
    The version argument only works for registry modules; pin Git sources with ref.
  33. 33
    Read a value from a child module
    Use module.<NAME>.<OUTPUT>, such as module.vpc.private_subnet_ids; only values the module declares as outputs are visible
  34. 34
    Look up the latest Ubuntu 24.04 AMI with a data source
    hcl
    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 as data.aws_ami.ubuntu.id.
  35. 35
    Create three similar instances with count
    hcl
    resource "aws_instance" "web" {
      count         = 3
      ami           = data.aws_ami.ubuntu.id
      instance_type = "t3.micro"
      tags = { Name = "web-${count.index}" }
    }
    Refer to one as aws_instance.web[0]. count = var.enabled ? 1 : 0 makes a resource optional.
  36. 36
    Create one IAM user per name with for_each
    hcl
    resource "aws_iam_user" "team" {
      for_each = toset(["alice", "bob"])
      name     = each.key
    }
    Instances are addressed by key, such as aws_iam_user.team["alice"]. With a map, each.value holds the value.
  37. 37
    Why prefer for_each over count for a list of distinct things?
    With count, removing an item from the middle shifts every later index, so Terraform replaces those resources; for_each addresses instances by stable keys
  38. 38
    Why does for_each = var.names fail when names is a list?
    for_each accepts only a map or a set of strings. Convert the list with toset(var.names)
    A block can’t use count and for_each together, and their values must be known at plan time.
  39. 39
    for expressions
    hcl
    upper_names = [for n in var.names : upper(n)]
    roles       = { for u in var.users : u.name => u.role }
  40. 40
    Generate repeated nested blocks from a list
    hcl
    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 separate aws_vpc_security_group_ingress_rule resources over inline rules.
  41. 41
    Terraform CLI workspaces
    Separate state files for the same configuration and backend, managed with terraform workspace new, select and list; the current name is available as terraform.workspace
    Fine for similar, short-lived copies. For environments that need different credentials or access controls, such as prod, HashiCorp recommends separate configurations or backends.
  42. 42
    Drift
    Real infrastructure has changed outside Terraform, for example in the console, so it no longer matches the configuration and state. terraform plan shows it as changes to undo
  43. 43
    Accept out-of-band changes into state without changing infrastructure
    bash
    terraform plan -refresh-only    # review the drift
    terraform apply -refresh-only   # write it to state
    This replaces the deprecated terraform refresh. Update the configuration too, or the next plan will try to revert the change.
  44. 44
    Bring an existing resource under Terraform management
    hcl
    import {
      to = aws_s3_bucket.logs
      id = "acme-app-logs"
    }
    Then run terraform plan -generate-config-out=generated.tf to draft the matching resource block. The older CLI form is terraform import aws_s3_bucket.logs acme-app-logs.
  45. 45
    Rename a resource without destroying it
    hcl
    moved {
      from = aws_instance.web
      to   = aws_instance.app
    }
    Without a moved block, Terraform sees one resource deleted and another created. terraform state mv does the same imperatively.
  46. 46
    Stop managing a resource but keep it running
    hcl
    removed {
      from = aws_instance.legacy
    
      lifecycle {
        destroy = false
      }
    }
    The older imperative equivalent is terraform state rm aws_instance.legacy.
  47. 47
    List and inspect resources in state
    bash
    terraform state list
    terraform state show aws_instance.web
  48. 48
    Force Terraform to recreate one resource
    bash
    terraform apply -replace="aws_instance.web"
    This replaces the deprecated terraform taint.
  49. 49
    terraform fmt vs terraform validate
    fmt rewrites files in the canonical style (fmt -check fails CI on unformatted code); validate checks syntax, types and references without contacting any remote APIs
  50. 50
    OpenTofu
    An 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 tofu command
    Its 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.

Intermediate

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 cards
Beginner

Linux 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 cards
Intermediate

Intermediate 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 cards
Advanced

Advanced 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 cards
Beginner

Bash scripting

Bash scripting flashcards: shebangs, quoting, exit codes, tests, loops, functions, arrays, parameter expansion, redirection, set -euo pipefail and traps.

50 cards
Beginner

Vim

Learn Vim from the keyboard up: modes, motions, operators and text objects, search and :s substitutions, registers, macros, visual mode and windows.

50 cards
Beginner

Networking

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 cards
Beginner

Cyber 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 cards
Beginner

Docker

Learn Docker from images and layers to multi-stage builds, volumes, networks and Compose, with the commands and best practices you use daily.

50 cards
Intermediate

Kubernetes

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 cards
Beginner

AWS

AWS flashcards: regions and AZs, EC2, S3 storage classes, IAM users, roles and policies, VPC networking, RDS, Lambda, CloudWatch and Route 53.

50 cards