Terraforming on Linode, part i

Posted on Feb 8, 2026
tl;dr:

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

  • This guide uses Linux examples; workflow is similar on other platforms.
  • Your user may need sudo to 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/

  1. Create a working directory:
mkdir -p ~/opentofu/linode-demo
cd ~/opentofu/linode-demo
  1. Export your Linode token (recommended). The provider supports LINODE_TOKEN.
export LINODE_TOKEN="YOUR_LINODE_API_TOKEN"
  1. Create main.tf with a single Linode instance. Update region, type, and image as 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"
}
  1. Initialize:
tofu init
  1. Review the plan:
tofu plan
  1. Apply:
tofu apply

Type yes when prompted.

  1. Verify OpenTofu-Web-Example appears 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.

  1. Change the type for a db instance (e.g., g6-standard-4).
  2. 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.