67 hands-on labs to practise for the HashiCorp Certified: Terraform Authoring and Operations Professional, roughly 63 hours of work, each one graded on the state of your machine rather than on the commands you typed. They run on your own hardware with the `dsoxlab` command line, and cost nothing.
The level above, and this one is hands-on: writing reusable modules, managing shared state, and operating Terraform inside a delivery pipeline.
- 67labs
- 63hours of practice
Levels 43 intermediate 24 advanced
Official exam objectives published by HashiCorp
These are not exam questions. They are hands-on exercises on the skills the exam measures, graded on the state of your machine.
Terraform, Associate and Professional
Add this catalogue dsoxlab catalog add https://github.com/stephrobert/terraform-dsoxlab-training
Writing Terraform code
-
Explicit source and provider alias
Declare providers correctly: an explicit source address that resolves to registry.terraform.io/hashicorp/random, a second configuration of the same provider distinguished by an alias, and a resource wired to it with provider =. Proven by version -json and the plan JSON provider_config. Associate 5b.
dsoxlab start write-code-providers -
Read a resource's lifecycle in the plan
Build four resources ordered only by references, then prove in the plan JSON the four lifecycle operations: update in place, replace, create before destroy, and destroy. Pro objective 1c.
dsoxlab start write-code-declare-resources -
Variables typing, validation and precedence
Type variables correctly and master their traps: a map of objects with optional() defaults, a validation rule that rejects before any provider, an explicit null that falls back to the default with nullable=false, the real source precedence (auto.tfvars over TF_VAR_, -var above all), and sensitive as a display mask, not a state protection. Associate 2e plus 2f.
dsoxlab start write-code-variables -
The secret the output does not hide
Master the real output block: a type constraint (object), sensitivity propagation (an output referencing a secret must be sensitive, and sensitive is only a display mask, cleartext stays in state, -json and -raw), nonsensitive() to expose a hash deliberately, and a precondition that fails the plan. Associate 2e plus 2f.
dsoxlab start write-code-outputs -
The local that is not resolved at plan time
Centralize expressions in locals blocks: normalize with HCL functions, keep a ternary typed, generate a list with a for expression, derive from a resource attribute (known after apply), and inherit a variable's sensitivity. Pro objective 2c.
dsoxlab start write-code-locals -
When exactly does Terraform read a data source?
Build three data sources whose read timing is deliberate: one read during the plan, one deferred to the apply because the resource it depends on is changing, and one carrying an explicit depends_on that does NOT defer it. Prove each case in the plan JSON. Pro objective 2b.
dsoxlab start write-code-data-sources -
What expressions really compute
Write correct Terraform expressions: reference a managed resource with no prefix, know that == does not convert types (so 3 is not "3") while arithmetic does, respect operator precedence, and use null to omit an argument rather than an empty string. Associate 2e.
dsoxlab start write-code-expressions -
Compose values with HCL functions
Deduplicate a collection for for_each, handle out-of-range element(), lookup() fallback, ceil() rounding, template escaping and a provider-defined function. Pro objective 2c.
dsoxlab start write-code-functions -
Provider-defined functions
Use functions exposed by a provider with the provider:: syntax. Pro objectives 2c and 5a.
dsoxlab start write-code-provider-defined-functions -
The configuration that refuses absurd values
Compute values with conditional expressions, then place each guard at the right level: validation, precondition, postcondition and check block. Pro objective 2a.
dsoxlab start write-code-conditionals -
Custom conditions: precondition, postcondition and check blocks
Validate a configuration with the language features designed for it. Pro objective 2a.
dsoxlab start write-code-validation-check-preconditions -
count indexes by position, and the position lies
Choose count or for_each per resource and prove it: index a set by name with for_each, migrate a count resource to for_each with no destroy using moved blocks, keep count for interchangeable copies, and expose a conditional resource with one(). Associate objective 4b plus the Professional moved migration.
dsoxlab start write-code-count -
Add an instance without destroying the others
Migrate count to for_each with moved blocks on an already applied configuration, then add a service proving zero destruction. Pro objective 2d.
dsoxlab start write-code-for-each -
Transform a catalog with for expressions
Derive four collections from a single map of servers with for expressions: a filtered tuple, an object grouped by role with the ellipsis, a flattened two-level cross, and a filtered for_each. Avoid the splat-on-a-map trap. Pro objective 2c.
dsoxlab start write-code-for-loops -
Generate blocks, and know when not to
Generate cloudinit parts with a dynamic block filtered in for_each and named from the key, keep the fixed part literal, and hit the wall: a lifecycle meta-argument block cannot be generated by dynamic. Pro objective 2d.
dsoxlab start write-code-dynamic-blocks -
depends_on belongs only where a reference cannot go
Remove a redundant depends_on that a reference already covers, replace a hardcoded path with a reference, keep the one depends_on nothing else can express, and prove the graph in the plan JSON. Pro objective 2d.
dsoxlab start write-code-depends-on -
The lifecycle block decides the order, not you
Set create_before_destroy and watch its propagation go down to dependencies, hit the documented limit of prevent_destroy, scope ignore_changes to a single attribute, trigger a replacement from a bare value through terraform_data, and reject an invalid input at plan time. Pro objective 2d.
dsoxlab start write-code-lifecycle -
The typo that breaks nothing
Two tfvars traps: an undeclared variable in a .tfvars is only a warning, so the real variable silently stays at its default (fix the typo), and terraform.tfvars.json outranks terraform.tfvars (a distinct precedence level). Associate 3c.
dsoxlab start write-code-tfvars-files -
Version constraints and the lock file
Write correct version constraints: a literal required_version (the terraform block accepts no variable), an exact provider pin that resolves to that version, and the pessimistic ~> that allows 3.x but not 4.0. Proven by version -json and the h1 hashes of the lock file. Associate 3a.
dsoxlab start write-code-version-constraints -
The config that works but no CI accepts
Take a working but non-conforming config and make it CI-ready: pass terraform fmt, fix the orphan reference that breaks validate, rename resources to snake_case, type and describe variables and outputs, mark the token sensitive, type the number output (1.15), and write a .gitignore that excludes state but keeps the lock file. Associate 2a plus 2e.
dsoxlab start write-code-style-guide -
When sensitivity breaks for_each
A side effect of sensitive rarely taught: a sensitive value cannot be a for_each key (Invalid for_each argument), so you must iterate over a non-sensitive set. The contaminated resource attribute is flagged in sensitive_values, and a hash is exposed with nonsensitive(). Professional 2f.
dsoxlab start write-code-sensitive-data-sensitive-values -
The value that never touches the state
Declare an ephemeral value that is generated during the run but never written to the state or plan: the ephemeral block (random 3.7+), the contrast with a persisted random_password, exposing it safely at the root with ephemeralasnull(), and the two errors it triggers when leaked. Professional 2f.
dsoxlab start write-code-sensitive-data-ephemeral-values -
Write-only arguments: a secret that never lands in the state
Send a secret to the provider without ever persisting it in the Terraform state, using write-only arguments against a local AWS emulator.
dsoxlab start write-code-sensitive-data-write-only-arguments -
Read secrets from Vault
Fetch secrets from HashiCorp Vault with the vault provider and consume them without freezing them in clear text in the state. Pro objective 2f.
dsoxlab start write-code-sensitive-data-vault-secrets
Terraform state
-
Take over a secret already in service, without regenerating it
A plain `apply` would draw a new password. Attach the existing one through an `import` block, then observe that the secret sits in clear text in the state while being marked `sensitive_attributes`: masking is a display, not encryption.
dsoxlab start state-understand-state -
Migrate the state without breaking its lineage
A backend block accepts no named value. Move from an implicit local backend to a parameterised one through partial configuration and `-backend-config`, and prove the state migrated rather than being recreated: same `lineage`, both resources still managed.
dsoxlab start state-backends -
State locking, what it really blocks
Measure the exact scope of the state lock on a local backend, and state what enables it on S3.
dsoxlab start state-state-locking -
terraform state list, the address is the identity
Find the address of an instance when all you know is its real identifier, and count managed resources without being caught by a module.
dsoxlab start state-terraform-state-list -
terraform state show, and what the summary hides
Extraire du state une valeur sensible et des attributs nuls, que la sortie humaine de state show caviarde ou omet.
dsoxlab start state-terraform-state-show -
terraform state mv and the moved block, refactor without destroying
Reattach three existing objects to their new addresses, imperatively then declaratively, and prove nothing was recreated.
dsoxlab start state-terraform-state-mv -
terraform state rm and the removed block, stop managing without destroying
Take two resources out of the state without deleting the files, and learn why a removed block destroys unless you write destroy = false.
dsoxlab start state-terraform-state-rm -
The removed block: bequeath an infrastructure without destroying it
Take resources out of the state deciding for each whether the real object survives, and find where the removed block gives up. Pro objectives 1e and 4c.
dsoxlab start state-removed-block -
Restore an amputated state: pick the right backup, prove nothing was recreated
A state rm went wrong and the backup was overwritten. Three candidate backups, one restorable, compared by lineage and serial, then pushed without touching the real files.
dsoxlab start state-backup-restore-state -
Diagnose a drift and adopt an orphan resource, without losing its value
Prove a drift with a saved refresh-only plan, reconcile it, then adopt a pre-existing token with an import block, without Terraform regenerating it.
dsoxlab start state-diagnose-state
Terraform modules
-
A reusable module configures no provider of its own
Empty a child module of its provider configuration, have it declare the aliased configuration it expects, pass that configuration from the root, and instantiate the module three times with a single for_each.
dsoxlab start modules-create-modules -
Refactor a monolith into the standard module structure
Split a single .tf file into the official layout, add a nested module called by relative path, a standalone example, and prove that override.tf is the one file name with a functional effect.
dsoxlab start modules-module-structure -
A module interface is a contract, not just types
Make an object attribute optional, refuse an explicit null, reject an out-of-range value, gate an output behind a precondition, and re-export a secret without breaking the plan.
dsoxlab start modules-module-variables-outputs -
A local module is read in place, not installed
Wire two root projects onto one shared local module, and prove from modules.json which folder each call really reads: relative path versus absolute path.
dsoxlab start modules-module-local -
A registry module is downloaded, versioned, and never locked
Pin a registry module, resolve a flexible constraint, and prove from modules.json and the plan JSON which version got installed: the lock file never covers a module.
dsoxlab start modules-module-registry -
Publish and consume module versions with Git tags
Publish three versions of a shared module, then consume them from three projects: an immutable reference, a minor tag and a major tag, with no version argument in sight.
dsoxlab start modules-version-modules -
Prove a module with terraform test, mutations included
Write the .tftest.hcl suite of a module, covering its default, its options and its refusal of an invalid input: every behaviour is checked by mutating the module.
dsoxlab start modules-test-module -
Make a module composable, and prove it from the plan JSON
Strip a legacy module of its own provider configuration, invert its dependency and document it, then read the proof in provider_config and module_calls.
dsoxlab start modules-module-best-practices -
Refactor a copy-pasted project without destroying anything
Extract two copy-pasted resources into a typed module called once, and prove from the state identifiers that nothing was destroyed on the way.
dsoxlab start modules-module-anti-patterns
Environments
-
Split a monolithic configuration, and prove the plan did not move
Split an eighty-line main.tf into the official file layout, then prove by comparing two plans that nothing changed: a green validate proves nothing here.
dsoxlab start environments-organize-terraform-repo -
Two roots, two states, one shared module
Wire two root configurations onto one shared module with a partial backend, then prove by destroying dev that prod never moved.
dsoxlab start environments-separate-environments -
Workspaces or separate configurations, and what the split costs
Decide which parts still belong in workspaces, split the rest into two root configurations, and wire their dependency through terraform_remote_state without duplicating a value.
dsoxlab start environments-when-to-use-workspaces -
Split a monorepo into two stacks that talk to each other
Re-export a nested module output at the root, consume it from a downstream stack through terraform_remote_state, and keep the platform secret out of the shared state.
dsoxlab start environments-monorepo-vs-repo-per-stack -
Run Terraform in automation (CI/CD)
Build a non-interactive Terraform chain judged on exit codes alone, then record what a saved plan really freezes, what a lock does, and where a secret leaks.
dsoxlab start environments-terraform-in-automation
Terraform on AWS, via Floci
-
Compose the IAM chain, and name its two policies correctly
`aws_iam_policy_document` is a LOCAL data source: it queries nothing, it builds JSON. Compose the policy, role, instance profile and instance chain, and prove in the structured state that the trust policy and the permissions take two distinct paths to the role.
dsoxlab start aws-iam-role-policy-instance-profile -
A remote state, locked, and read by another stack
Repair a trapped backend block, make the S3 lock actually effective, and have a second configuration consume the outputs without copying a single value.
dsoxlab start aws-backend-s3-remote-state -
Read a replacement in the plan, before it happens
An ASG referencing `version = "$Latest"` never triggers an instance refresh, and a `create_before_destroy` placed on the launch template protects nothing. Read both truths in the plan converted to JSON, without ever calling AWS.
dsoxlab start aws-launch-template-autoscaling -
Import, moved and drift: the three traps a tutorial never shows
Bring an EC2 instance created outside Terraform under its control, change its logical address without the real object being recreated, then reconcile a drift by ACCEPTING reality rather than overwriting it.
dsoxlab start aws-import-moved-drift
HCP Terraform
-
The run workflow: played in two steps, then qualified
A run is a plan, then an apply of THAT plan. Reproduce it locally with saved plans, see what a stale plan and a frozen variable refuse, then qualify six described runs and put the eleven stages back in the order the documentation gives.
dsoxlab start hcp-terraform-hcp-terraform-overview -
Workspaces: one word, two meanings, two attachment strategies
A CLI workspace is one more state in the same directory; an HCP Terraform workspace is a unit of execution with its own variables, permissions and history. Repair two `cloud` blocks, attach one by name and the other by tags, and establish what `terraform validate` does not catch.
dsoxlab start hcp-terraform-hcp-workspaces -
The stream a run sends back, and the three ways to start one
What a CLI receives from a remote run is the structured stream `terraform apply -json` also produces locally. Record it, analyse it in HCL , two `change_summary` messages, only one of which says what happened , and establish what each of the three workflows allows.
dsoxlab start hcp-terraform-remote-runs -
Fifteen precedence levels, and one inversion
Among priority variable sets the broadest scope wins, whereas among normal sets it is the narrowest. Build the complete fifteen-level table, then resolve cases the tests generate and that no case-by-case answer can handle.
dsoxlab start hcp-terraform-variable-sets -
Credentials: neither in the code, nor in the state
`sensitive` hides a value from the screen, and the state holds it in clear text all the same. Take the provider's keys out of the configuration, replace a token carried in a tag with its fingerprint, and establish how dynamic credentials work.
dsoxlab start hcp-terraform-shared-credentials -
Permissions add up, they do not override
A permission set at the organization level can beat one set on the workspace, and the reverse is also true: the most permissive wins, never the most specific. Write the rule that computes six teams' effective access, and establish two scales that are not the same.
dsoxlab start hcp-terraform-projects-teams -
Policy as code: what blocks a run, and who can override it
A `hard-mandatory` is not unbypassable: the policy set setting decides, crossed with the user's permission. Qualify seven runs, establish the levels of the three frameworks, and write a rule that refuses a non-compliant plan and accepts the compliant one.
dsoxlab start hcp-terraform-policy-as-code -
The first remote run, for real (optional, needs an account)
The only lab in the catalog that needs an HCP Terraform account. Provision the platform with the `tfe` provider , project, workspace, settings, a sensitive variable , attach a configuration with a `cloud` block, and watch a run execute on HashiCorp's infrastructure.
dsoxlab start hcp-terraform-premier-run-distant
Professional certification
-
Pro · Objective 1: resource lifecycle, import and drift reconciliation
Import into state an EC2 instance created outside Terraform (aws cli on Floci), then detect and reconcile a drift. Exam objective 1.
dsoxlab start certifications-professional-capstone1-resource-lifecycle -
Pro · Objective 2: dynamic configuration and troubleshooting
Data sources, HCL functions, meta-arguments (count/for_each/dynamic), complex types, sensitive data and Vault. Exam objective 2.
dsoxlab start certifications-professional-capstone2-dynamic-config -
Pro · Objective 3: collaborative workflows
Remote state (S3 backend on Floci), version constraints, workflow in automation and data sharing via terraform_remote_state. Exam objective 3.
dsoxlab start certifications-professional-capstone3-collaborative-workflows -
Pro · Objective 4: create, maintain and use modules
Create a module, consume it, refactor and version it; refactor a flat configuration into modules. Exam objective 4.
dsoxlab start certifications-professional-capstone4-modules -
Pro · Objective 5: configure and use providers
Plugin architecture, aliasing, versioning/sourcing/upgrades, authentication and provider error troubleshooting (on Floci). Exam objective 5.
dsoxlab start certifications-professional-capstone5-providers -
Pro · Objective 6: HCP Terraform, where the sub-objectives cross
Objective 6 is assessed by multiple choice, and needs no account. Six situations that each cross two sub-objectives, a `cloud` block to write, and a secret that must not reach the state. Six situations, six different outcomes: no constant answer passes.
dsoxlab start certifications-professional-capstone6-hcp -
Pro · Integrative mock exam: the six objectives in one sitting
A rehearsal, not a lesson: six tasks, one per objective, to be played in one go once the six capstones are done. Drift reconciliation, dynamic configuration, two states that talk, a module that recreates nothing, two configurations of one provider, and a twelve-question HCP paper scored per sub-objective.
dsoxlab start certifications-professional-mock-pro