HCL & Terraform compatibility

Providers, resources, and shared syntax.

Beginner12 min · lesson 3 of 12

OpenTofu speaks the same configuration language as Terraform — HCL — with the same building blocks: a provider configures a target platform, a resource declares a piece of infrastructure, and references wire them together into an implicit dependency graph. If you have written Terraform, none of this is new; if you have not, this is the same core you would learn there.

main.tofu
terraform { # yes, still the "terraform" block
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}
provider "aws" { region = "us-east-1" }
resource "aws_s3_bucket" "logs" {
bucket = "acme-logs-prod"
}
resource "aws_s3_bucket_versioning" "logs" {
bucket = aws_s3_bucket.logs.id # reference => dependency edge
versioning_configuration { status = "Enabled" }
}

File extensions and the settings block

OpenTofu reads .tf files for full Terraform compatibility, and also supports a .tofu extension for files that should apply only under OpenTofu — a .tofu file overrides a same-named .tf file when running tofu, which is how you keep one repo that behaves one way under each tool. For maximum compatibility the top settings block is still named terraform { }, not tofu { } — a pragmatic choice so shared modules parse under both.

terminal
# providers resolve through the same protocol => the ecosystem just works
$ tofu providers
Providers required by configuration:
.
└── provider[registry.opentofu.org/hashicorp/aws] ~> 5.0
Providers resolve from a different registry by default
tofu resolves providers from registry.opentofu.org rather than registry.terraform.io. It mirrors the same providers, but if your network allowlists a registry or you run a private mirror, add the OpenTofu registry host — otherwise init fails to download providers even though the config is identical.