Terraforming on Linode, part i
The documentation on Linode Docs is out of date for Terraform and OpenTofu. This guide rewrites those instructions for OpenTofu.
Infrastructure as code (IaC) lets server deployments and configuration be represented as code. This reduces human error, makes complex systems more manageable, and eases collaboration.
OpenTofu focuses on creating, modifying, and destroying infrastructure. It uses the same declarative workflow you may know from Terraform: write configuration, run plan, then apply, and optionally destroy. See: https://opentofu.org/docs/cli/run/
Linodes created with OpenTofu can be configured further with containers (Docker) or configuration management tools (Salt, Puppet, Ansible, Chef).
Note: The commands in this guide can create Linodes that may incur charges. Monitor your Cloud Manager account.
Before you begin
Provider and OpenTofu versions
- OpenTofu: use a stable OpenTofu 1.x release. https://opentofu.org/docs/language/v1-compatibility-promises/
- Linode provider: at the time of writing (Feb 2026) the latest provider is v3.8.0. Verify before pinning. https://github.com/linode/terraform-provider-linode
- Lock file: OpenTofu records provider selections in
.terraform.lock.hcl. Commit it for reproducible installs. https://opentofu.org/docs/language/files/dependency-lock/- File extensions: OpenTofu works with
.tfand.tofufiles. This guide uses.tf.
- This guide uses Linux examples; workflow is similar on other platforms.
- Your user may need
sudoto install packages. - You need a Personal Access Token for the Linode API.
Install OpenTofu
Choose one installation method and stick to it.
Ubuntu / Debian
Use the OpenTofu Debian repository: https://opentofu.org/docs/intro/install/deb/
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://get.opentofu.org/opentofu.gpg | sudo tee /etc/apt/keyrings/opentofu.gpg >/dev/null
curl -fsSL https://packages.opentofu.org/opentofu/tofu/gpgkey | sudo gpg --no-tty --batch --dearmor -o /etc/apt/keyrings/opentofu-repo.gpg >/dev/null
sudo chmod a+r /etc/apt/keyrings/opentofu.gpg /etc/apt/keyrings/opentofu-repo.gpg
echo "deb [signed-by=/etc/apt/keyrings/opentofu.gpg,/etc/apt/keyrings/opentofu-repo.gpg] https://packages.opentofu.org/opentofu/tofu/any/ any main
deb-src [signed-by=/etc/apt/keyrings/opentofu.gpg,/etc/apt/keyrings/opentofu-repo.gpg] https://packages.opentofu.org/opentofu/tofu/any/ any main" | sudo tee /etc/apt/sources.list.d/opentofu.list > /dev/null
sudo chmod a+r /etc/apt/sources.list.d/opentofu.list
sudo apt-get update
sudo apt-get install -y tofu
RHEL / AlmaLinux / openSUSE and other RPM-based distros
Use the OpenTofu RPM repository: https://opentofu.org/docs/intro/install/rpm/
cat >/etc/yum.repos.d/opentofu.repo <<'EOF'
[opentofu]
name=opentofu
baseurl=https://packages.opentofu.org/opentofu/tofu/rpm_any/rpm_any/$basearch
repo_gpgcheck=0
gpgcheck=1
enabled=1
gpgkey=https://get.opentofu.org/opentofu.gpg
https://packages.opentofu.org/opentofu/tofu/gpgkey
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
metadata_expire=300
[opentofu-source]
name=opentofu-source
baseurl=https://packages.opentofu.org/opentofu/tofu/rpm_any/rpm_any/SRPMS
repo_gpgcheck=0
gpgcheck=1
enabled=1
gpgkey=https://get.opentofu.org/opentofu.gpg
https://packages.opentofu.org/opentofu/tofu/gpgkey
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
metadata_expire=300
EOF
sudo yum install -y tofu
Verify
tofu -version
Run tofu with no arguments to see available commands.
Building with the Linode provider
OpenTofu uses HCL and the same terraform { ... } block. Providers are installed from a registry (default: registry.opentofu.org). https://opentofu.org/docs/cli/private_registry/
- Create a working directory:
mkdir -p ~/opentofu/linode-demo
cd ~/opentofu/linode-demo
- Export your Linode token (recommended). The provider supports
LINODE_TOKEN.
export LINODE_TOKEN="YOUR_LINODE_API_TOKEN"
- Create
main.tfwith a single Linode instance. Updateregion,type, andimageas needed.
terraform {
required_providers {
linode = {
source = "linode/linode"
version = "~> 3.8"
}
}
}
provider "linode" {}
resource "linode_instance" "web" {
image = "linode/ubuntu24.04"
label = "OpenTofu-Web-Example"
group = "OpenTofu"
region = "us-east"
type = "g6-standard-1"
authorized_keys = ["YOUR_PUBLIC_SSH_KEY"]
root_pass = "YOUR_ROOT_PASSWORD"
}
- Initialize:
tofu init
- Review the plan:
tofu plan
- Apply:
tofu apply
Type yes when prompted.
- Verify
OpenTofu-Web-Exampleappears in Cloud Manager.
Provision additional servers
Create another .tf file; OpenTofu loads all .tf and .tofu files in the directory.
db.tf:
resource "linode_instance" "db" {
image = "linode/ubuntu24.04"
label = "OpenTofu-Db-Example"
group = "OpenTofu"
region = "us-east"
type = "g6-standard-1"
authorized_keys = ["YOUR_PUBLIC_SSH_KEY"]
root_pass = "YOUR_ROOT_PASSWORD"
}
Then:
tofu plan
tofu apply
Destroy servers
tofu destroy
Preview destruction:
tofu plan -destroy
After destroying you can remove config files:
rm -f *.tf
Use variables (avoid hardcoding secrets)
Define variables and supply values via terraform.tfvars or TF_VAR_* environment variables.
variables.tf:
variable "authorized_keys" {
type = list(string)
description = "SSH public keys allowed to access the Linode"
}
variable "root_pass" {
type = string
description = "Root password for the Linode"
sensitive = true
}
variable "region" {
type = string
description = "Linode region"
default = "us-east"
}
terraform.tfvars:
authorized_keys = ["YOUR_PUBLIC_SSH_KEY"]
root_pass = "YOUR_ROOT_PASSWORD"
region = "us-east"
Update main.tf to use variables:
resource "linode_instance" "web" {
image = "linode/ubuntu24.04"
label = "OpenTofu-Web-Example"
group = "OpenTofu"
region = var.region
type = "g6-standard-1"
authorized_keys = var.authorized_keys
root_pass = var.root_pass
}
Run:
tofu fmt
tofu plan
tofu apply
Modify live deployments
OpenTofu can update many attributes in-place when the provider supports it.
- Change the
typefor adbinstance (e.g.,g6-standard-4). - Review and apply:
tofu plan
tofu apply
OpenTofu modules
Modules package reusable infrastructure patterns.
Example structure:
modules
└── app-deployment
├── main.tf
└── variables.tf
client1
└── main.tf
Author modules using variables, then call them from client configs with per-client values.
Use Linode Object Storage for state
By default OpenTofu stores state locally in terraform.tfstate. For teams, use remote state; the S3 backend supports custom endpoints.
backend.tf example (fill bucket and endpoint):
terraform {
backend "s3" {
bucket = "YOUR-BUCKET-NAME"
key = "tf/tfstate"
region = "us-southeast-1"
endpoints = {
s3 = "https://us-southeast-1.linodeobjects.com"
}
skip_region_validation = true
skip_credentials_validation = true
skip_requesting_account_id = true
skip_s3_checksum = true
}
}
Export credentials (S3-compatible env vars):
export AWS_ACCESS_KEY_ID="OBJ-ACCESS-KEY"
export AWS_SECRET_ACCESS_KEY="OBJ-SECRET-KEY"
Re-initialize to migrate state:
tofu init
If prompted to migrate state, answer yes.