Module 00 · Orientation · 20 min · You are: Platform and product
Orientation: what a platform team keeps in Terraform
In this module
- You learn: What Terraform does (you describe what should exist, it works out the calls that get you there and remembers what it made), what a platform team keeps in it, and how this course is laid out.
- You do: Tell the course your GitHub name and which command you use, check your tools, create your repo
tf-platform, create a one-node cluster, and runterraform initon a configuration that has no resources yet. - You end with: A cluster
kind-tfwith oneReadynode. Your repo on GitHub holdscluster/kind.yaml,.gitignoreandenvs/dev/versions.tf.terraform initsucceeds inenvs/dev. Nothing is managed yet.
A platform team gives every product team the same starting kit: a namespace, a budget, permissions. Done by hand, the fourth team's kit differs from the first, nobody remembers why, and nobody dares to change any of them. Terraform turns the kit into code with a record of what it created, so a change is a diff a colleague can read before anything happens. You start with the smallest possible configuration, so the first thing you see is what init does on its own. You play the platform team, and now and then team-checkout, a product team.
Open this module in the interactive course to track progress and switch tracks.
Concepts
- Desired state in files
.tffiles say what should exist, not how to get there. Terraform compares them with what it already manages and proposes the steps.- Provider
- A plugin that knows one API. The
kubernetesprovider turns aresourceblock into calls to the cluster's API server. Terraform itself knows no API at all. - State
- Terraform's record of what it created, and which block each object belongs to. Without it, Terraform cannot tell "mine" from "someone else's".
- Root module
- One directory of
.tffiles that you run Terraform in. It has one state. Your first one isenvs/dev. - Terraform and OpenTofu
- Two programs that read the same language. This course works with both. You pick one command and keep it for the whole repo.
Who owns what in this repo
| Path | What it is | Owner | Module |
|---|---|---|---|
modules/team-namespace/ | The kit every team gets | Platform team | 04 |
envs/dev/ | Which teams exist in dev, and their sizes | Platform team; product teams propose changes by pull request | 01 |
envs/prod/ | The same for prod | Platform team only | 05 |
state/ | The store that holds the state | Platform team | 03 |
.github/ | What runs on a pull request, and who must review | Platform team | 05, 06 |
Terraform and OpenTofu, where they differ in this course
| Terraform | OpenTofu | |
|---|---|---|
| Command | terraform | tofu |
| Provider address in the lock file | registry.terraform.io/hashicorp/kubernetes | registry.opentofu.org/hashicorp/kubernetes |
| CI | hashicorp/setup-terraform | opentofu/setup-opentofu |
Pick one per repo: switching rewrites the lock file.
Files to know
Where you are heading, in two minutes. These files from the reference repo describe the finished setup. Click Quick look on each: it opens the real file right here, with numbered circles on the lines that matter.
Open them in your editor or on GitHub; the bullets say what to look at.
README.md(reference, your repo): The map of the reference repo. Read the tables, skip the prose.cluster/kind.yaml(reference, your repo): The course cluster..gitignore(reference, your repo): Everything Terraform writes that must never be committed. You meet each line in the module that needs it.- Downloads, state, saved plans, local overrides, credentials L2–15
Steps
1. Tell the course who you are
Your GitHub user (or organisation) and a folder for your repo and the reference clone. Every command and link uses them. They stay in your browser.
Set your GitHub user and workspace folder in the interactive course or the form at the top of the notes index; these notes then show your values.
2. Pick your command
Terraform or OpenTofu. Every step is the same; only the word you type changes. If you have neither, install Terraform.
These notes show Terraform commands, with the OpenTofu variants folded underneath.
3. Check your tools
Paste this into a terminal. Each tool reports ok (with its version), MISSING (with where to get it) or PROBLEM (installed but not usable yet). Install what is missing your usual way, then run it again until it says All set. Versions: Terraform or OpenTofu 1.11 or newer (the course is written on Terraform 1.17.2 and OpenTofu 1.13.9), kind 0.33 or newer, Docker with 4 GB of memory.
check() { # tool, works-check, version, problem, where-to-get-it
if ! command -v "${1%% *}" >/dev/null 2>&1; then printf ' MISSING %-22s install it: %s\n' "$1" "$5"; bad=$((bad+1))
elif ! eval "$2" >/dev/null 2>&1; then printf ' PROBLEM %-22s %s\n' "$1" "$4"; bad=$((bad+1))
else printf ' ok %-22s %s\n' "$1" "$(eval "$3" 2>&1 | head -n 1)"; fi
}
bad=0
check 'git' 'git --version' 'git --version' 'installed, but it does not run' 'https://git-scm.com/downloads'
check 'docker' 'docker info' "docker version --format '{{.Server.Version}}'" 'installed, but the Docker daemon is not running: start Docker Desktop (or the docker service)' 'https://docs.docker.com/get-docker/'
check 'docker compose' 'docker compose version' 'docker compose version' 'the Compose plugin is missing: install Docker Compose v2' 'https://docs.docker.com/compose/install/'
check 'kind' 'kind version' 'kind version' 'installed, but it does not run' 'https://kind.sigs.k8s.io/docs/user/quick-start/#installation'
check 'kubectl' 'kubectl version --client' 'kubectl version --client' 'installed, but it does not run' 'https://kubernetes.io/docs/tasks/tools/'
check 'gh' 'gh auth status' 'gh --version' 'installed, but not logged in: run gh auth login' 'https://cli.github.com/'
check 'terraform' "terraform version | head -n 1 | grep -Eq 'v1\\.(1[1-9]|[2-9][0-9])\\.'" 'terraform version' 'installed, but older than 1.11: install a newer Terraform' 'https://developer.hashicorp.com/terraform/install'
if [ "$bad" -eq 0 ]; then echo "All set: every tool is ready."; else echo "$bad tool(s) need attention. Install or fix them, then run this check again."; fi
On openTofu
check() { # tool, works-check, version, problem, where-to-get-it
if ! command -v "${1%% *}" >/dev/null 2>&1; then printf ' MISSING %-22s install it: %s\n' "$1" "$5"; bad=$((bad+1))
elif ! eval "$2" >/dev/null 2>&1; then printf ' PROBLEM %-22s %s\n' "$1" "$4"; bad=$((bad+1))
else printf ' ok %-22s %s\n' "$1" "$(eval "$3" 2>&1 | head -n 1)"; fi
}
bad=0
check 'git' 'git --version' 'git --version' 'installed, but it does not run' 'https://git-scm.com/downloads'
check 'docker' 'docker info' "docker version --format '{{.Server.Version}}'" 'installed, but the Docker daemon is not running: start Docker Desktop (or the docker service)' 'https://docs.docker.com/get-docker/'
check 'docker compose' 'docker compose version' 'docker compose version' 'the Compose plugin is missing: install Docker Compose v2' 'https://docs.docker.com/compose/install/'
check 'kind' 'kind version' 'kind version' 'installed, but it does not run' 'https://kind.sigs.k8s.io/docs/user/quick-start/#installation'
check 'kubectl' 'kubectl version --client' 'kubectl version --client' 'installed, but it does not run' 'https://kubernetes.io/docs/tasks/tools/'
check 'gh' 'gh auth status' 'gh --version' 'installed, but not logged in: run gh auth login' 'https://cli.github.com/'
check 'tofu' "tofu version | head -n 1 | grep -Eq 'v1\\.(1[1-9]|[2-9][0-9])\\.'" 'tofu version' 'installed, but older than 1.11: install a newer OpenTofu' 'https://opentofu.org/docs/intro/install/'
if [ "$bad" -eq 0 ]; then echo "All set: every tool is ready."; else echo "$bad tool(s) need attention. Install or fix them, then run this check again."; fi
4. Clone the reference repo
The finished implementation. You copy a few files from it and compare with it; you never push to it.
mkdir -p ~/platformengr && git clone -q https://github.com/platformengr/terraform-platform.git ~/platformengr/terraform-platform
Check that it arrived. You should see cluster, docs, envs, labs, modules, policy and state:
ls ~/platformengr/terraform-platform
5. Create your repo
Your repo: the platform team's Terraform. Public, so the lab checks on this site can read it.
cd ~/platformengr && gh repo create YOUR_GITHUB_USER/tf-platform --public --clone
Go into it, make the first folders, and copy two files in: the cluster definition and the list of files Git must ignore.
cd ~/platformengr/tf-platform && git switch -C main && mkdir -p cluster envs/dev && cp ~/platformengr/terraform-platform/cluster/kind.yaml cluster/ && cp ~/platformengr/terraform-platform/.gitignore .
6. Create the cluster
One node is enough: Terraform creates namespaces, quotas and permissions, and nothing has to be scheduled. It is a separate cluster from the other courses on purpose: Terraform must be the only owner of the namespaces it manages.
Create the cluster. It takes about a minute, and pulls the node image the first time.
kind create cluster --config ~/platformengr/tf-platform/cluster/kind.yaml
kind pointed kubectl at the new cluster. Expect kind-tf:
kubectl config current-context
Expect one row, tf-control-plane, STATUS Ready:
kubectl get nodes
Expect five namespaces: the four Kubernetes made for itself, default, kube-node-lease, kube-public and kube-system, and local-path-storage, which kind adds. None of them is yours. Terraform will never touch them, because they are not in its state.
kubectl get namespaces
7. Write the smallest configuration
A configuration with no resources: only the versions of Terraform it accepts. A range, so Terraform and OpenTofu both pass.
Create ~/platformengr/tf-platform/envs/dev/versions.tf
terraform {
required_version = ">= 1.11.0, < 2.0.0"
}
init prepares a directory: it reads the configuration and downloads what it needs. There is nothing to download yet. Look for has been successfully initialized!
cd ~/platformengr/tf-platform/envs/dev
terraform init
Ask for a plan. Expect No changes. Your infrastructure matches the configuration. An empty configuration and an empty state are equal. Every plan you read from now on is a difference between those two, plus the real world.
terraform plan
8. Commit
Push it, so it is on GitHub. The course page offers Connect your repo after this step: it adds a small workflow so this site can read your progress.
cd ~/platformengr/tf-platform && git add -A && git commit -qm "Cluster and an empty dev configuration" && git push -qu origin main
Simplified for the course
The "infrastructure" in this course is Kubernetes objects on a laptop cluster, because that needs no account and costs nothing. The language, the state, the modules and the workflow are the same when the provider is a cloud.
docs/cloud.mdin the reference repo shows the same setup on real S3.You are the platform team and every product team at once. In a company these are different people, and the pull request is where they meet.
The CLI version is a range in the code and an exact version in CI. Teams that need everyone on the same build add a version manager (
tenv,mise) with a version file in the repo.
Break it on purpose: The configuration says it does not want your Terraform
You copy versions.tf from an older internal project to save typing. Its author pinned the CLI the way it was right at the time.
You practise: reading a version constraint, and deciding whether to change the tool or the constraint.
1. Break it
Change in ~/platformengr/tf-platform/envs/dev/versions.tf
- required_version = ">= 1.11.0, < 2.0.0"
+ required_version = "~> 1.5.0"
cd ~/platformengr/tf-platform/envs/dev
terraform init
2. What you see
Expected: has been successfully initialized! You get: Error: Unsupported Terraform Core version (OpenTofu says Unsupported OpenTofu Core version) and, below it, This configuration does not support your version. Nothing was downloaded and nothing was changed. Read the line the error points at.
3. Fix it yourself
Make terraform init succeed with the version you have installed, without allowing a future 2.0. Done when init succeeds and the constraint in versions.tf is one you can explain.
Hint 1
~> lets only the last number you wrote grow. What is the highest version ~> 1.5.0 accepts?
Hint 2
~> 1.5.0 means 1.5.0 or newer, but below 1.6.0. You want 1.11 or newer, below 2.0.
The fix
Change in ~/platformengr/tf-platform/envs/dev/versions.tf
- required_version = "~> 1.5.0"
+ required_version = ">= 1.11.0, < 2.0.0"
terraform init
After you fixed it: the five questions
- What failed?
- Nothing in the infrastructure. The configuration refused the tool before reading anything else.
- How was it detected?
- By Terraform itself, on the first command, with the file and line.
- Who responds?
- Whoever owns the root module: here the platform team.
- How is it corrected?
- Widen the constraint to what the configuration really needs. Do not install an old CLI to satisfy an old constraint unless the code needs it.
- How is it prevented?
- One constraint for the whole repo, and CI that runs the pinned version on every pull request (module 06), so the constraint is tested and not guessed.
Quiz
- What does
terraform initdo?- It prepares the directory: reads the configuration, downloads the providers and modules it names, and sets up where state is kept
- It creates the resources in the configuration for the first time, then records them in a new state file
- It checks the cluster for objects that are not in the configuration yet and lists them for import
- Which versions does
required_version = "~> 1.5.0"accept?- 1.5.0 and everything newer, including 2.0 and later major versions
- 1.5.0 and later patch releases such as 1.5.7, but not 1.6.0
- Only exactly 1.5.0, so every colleague runs the same build
- Why does Terraform need a state at all?
- To cache the provider downloads between runs, so
initis faster the second time - To keep a backup copy of your
.tffiles in case the Git history is lost - To know which real objects belong to which block, so it can change or remove exactly those
- To cache the provider downloads between runs, so
Answers
- It prepares the directory: reads the configuration, downloads the providers and modules it names, and sets up where state is kept
initnever touches infrastructure; it only prepares the working directory. - 1.5.0 and later patch releases such as 1.5.7, but not 1.6.0
~>lets only the rightmost number you wrote increase. - To know which real objects belong to which block, so it can change or remove exactly those Without state, Terraform could not tell its own namespace from one that Kubernetes or a colleague created.
Field note
When I join a team that already uses Terraform, I read versions.tf and the backend block before any resource. They tell me how old the setup is, who else can write to it, and how careful I have to be with the first plan.