How to Install and Use OpenTofu on Ubuntu 24.04

By Raman Kumar

Updated on Jan 30, 2026

In this tutorial, we'll learn how to install and use OpenTofu on Ubuntu 24.04.

Introduction

OpenTofu (a community-driven fork of Terraform) is an open-source infrastructure as code (IaC) tool that lets us define and manage cloud and on-prem resources declaratively. On Ubuntu 24.04, installing and using OpenTofu is straightforward if we follow the right steps. In this guide, we explain installation, setup, and how to run a basic project.

What Is OpenTofu

OpenTofu lets us define infrastructure using simple configuration files in HCL (HashiCorp Configuration Language). Once defined, we can plan and apply changes reliably, ensuring predictable environments across teams.

Prerequisites

Before we begin, ensure we have the following:

How to Install and Use OpenTofu on Ubuntu 24.04

Step 1. Update and Prepare Ubuntu 24.04

Before installing anything, make sure our system packages are up-to-date. This avoids dependency issues later.

sudo apt update
sudo apt upgrade -y

Keeping the system updated ensures OpenTofu runs on the latest libraries and security patches.

Step 2. Install Required Dependencies

OpenTofu itself is a single binary, but we install a few utilities that help with downloading and verifying it.

sudo apt install -y curl unzip gnupg

Step 3. Download the Latest OpenTofu Binary

Visit the official OpenTofu releases page (https://github.com/opentofu/opentofu/releases) and identify the latest stable version for Linux. Replace v1.11.4 with the actual version tag.

curl -Lo opentofu.zip https://github.com/opentofu/opentofu/releases/download/v1.11.4/tofu_1.11.4_linux_amd64.zip

We always fetch the latest release to benefit from bug fixes and new features.

Step 4. Extract and Install OpenTofu

Once downloaded, unzip and move the binary into a location in our system PATH:

unzip opentofu.zip
chmod +x tofu
sudo mv tofu /usr/local/bin/

Putting the executable in /usr/local/bin makes it available anywhere on the system.

Step 5. Verify the Installation

Check the installed version to make sure OpenTofu is correctly set up.

tofu version

We should see output like OpenTofu vX.Y.Z. This confirms the tool is installed and runnable.

Step 6. Initialize an OpenTofu Project

Create a directory for a new project and initialize it:

mkdir ~/opentofu-demo && cd ~/opentofu-demo
tofu init

Initialization scaffolds the folder and prepares the environment so we can start defining infrastructure.

Step 7. Write a Basic Configuration

Create a file named main.tf and add a simple provider block. For example, to use the local provider:

sudo nano main.tf

Add following:

terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.4"
    }
  }
}

provider "local" {}

This configuration tells OpenTofu what provider we plan to use. We can change providers (e.g., AWS, Azure, GCP) based on our needs.

Step 8. Initialize Providers and Modules

Run:

tofu init

This downloads provider plugins and sets up modules. This step must be done before planning or applying.

Step 9. Plan Changes

To preview what OpenTofu will do:

tofu plan

The plan shows actions that will be performed. We always inspect this before applying changes to avoid unexpected updates.

Step 10. Apply the Plan

After reviewing, execute:

tofu apply

We confirm with yes, and OpenTofu creates or updates resources based on the configuration.

Step 11. Clean Up

When we no longer need the resources managed by OpenTofu:

tofu destroy

This safely destroys resources defined in the configuration, avoiding lingering costs or clutter.

Advanced Configurations

1. Remote Backend with S3 + State Locking

Backend Configuration

terraform {
  backend "s3" {
    bucket         = "prod-opentofu-state-bucket"
    key            = "network/terraform.tfstate"
    region         = "ap-south-1"
    encrypt        = true
    dynamodb_table = "opentofu-lock-table"
  }
}

Explanation

  • bucket defines where the remote state file will be stored.
  • key specifies the logical path of the state file inside the bucket, helping separate environments or projects.
  • encrypt = true ensures state data is encrypted at rest.
  • dynamodb_table enables state locking to prevent concurrent updates from multiple team members.

Using a remote backend improves collaboration, consistency, and operational control in team environments.

2. Environment-Based Variable Management

variables.tf

variable "environment" {
  type        = string
  description = "Deployment environment"
}

variable "instance_type" {
  type        = string
  description = "EC2 instance size"
  default     = "t3.micro"
}

dev.tfvars

environment   = "dev"
instance_type = "t3.micro"

prod.tfvars

environment   = "prod"
instance_type = "t3.medium"

Usage

opentofu apply -var-file=prod.tfvars

Explanation

  • This structure allows us to define reusable configurations while adjusting values per environment.
  • variables.tf defines configurable inputs.
  • .tfvars files supply environment-specific values.
  • The -var-file flag ensures controlled deployment into the intended environment.

This approach improves clarity and reduces duplication across environments.

3. Using Modules

modules/vpc/main.tf

variable "cidr_block" {
  type = string
}

resource "aws_vpc" "this" {
  cidr_block = var.cidr_block
  tags = {
    Name = "main-vpc"
  }
}

Root main.tf

module "vpc" {
  source     = "./modules/vpc"
  cidr_block = "10.0.0.0/16"
}

Explanation

Modules allow us to group related resources into reusable components.

  • The module defines infrastructure logic.
  • The root configuration calls the module with specific inputs.

This promotes standardization, reuse, and maintainability across multiple deployments.

4. Dynamic Blocks

Example: Security Group with Multiple Ports

variable "allowed_ports" {
  type    = list(number)
  default = [22, 80, 443]
}

resource "aws_security_group" "web_sg" {
  name = "web-security-group"

  dynamic "ingress" {
    for_each = var.allowed_ports
    content {
      from_port   = ingress.value
      to_port     = ingress.value
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }
}

Explanation

Dynamic blocks allow us to generate repeated nested configurations based on input values.

  • for_each iterates over a list of ports.
  • Each iteration creates a corresponding ingress rule.

This keeps the configuration concise and scalable as requirements grow.

5. Lifecycle Management Rules

resource "aws_instance" "app" {
  ami           = "ami-123456"
  instance_type = "t3.micro"

  lifecycle {
    prevent_destroy = true
    create_before_destroy = true
    ignore_changes = [
      tags
    ]
  }
}

Explanation

The lifecycle block provides additional control over how resources are managed.

  • prevent_destroy blocks accidental deletion.
  • create_before_destroy ensures replacement occurs before removal, helping maintain availability.
  • ignore_changes allows selective drift tolerance when certain attributes are modified outside OpenTofu.

These controls enhance operational stability.

6. Workspaces for Multi-Environment Isolation

opentofu workspace new dev
opentofu workspace new prod

Usage inside configuration:

resource "aws_s3_bucket" "app_bucket" {
  bucket = "app-${terraform.workspace}-bucket"
}

Explanation

Workspaces allow us to maintain separate state files for different environments while using the same configuration code.

  • Each workspace maintains isolated state.
  • ${terraform.workspace} dynamically adjusts resource naming.

This supports structured environment separation within a single codebase.

7. Data Sources

data "aws_ami" "latest_amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }
}

resource "aws_instance" "app" {
  ami           = data.aws_ami.latest_amazon_linux.id
  instance_type = "t3.micro"
}

Explanation

Data sources allow us to retrieve existing infrastructure information dynamically.

  • most_recent = true ensures the latest compatible AMI is selected.
  • Filters narrow down the selection criteria.

This approach keeps infrastructure current without manually updating IDs.

8. Provisioners

resource "aws_instance" "app" {
  ami           = "ami-123456"
  instance_type = "t3.micro"

  provisioner "remote-exec" {
    inline = [
      "sudo apt update",
      "sudo apt install nginx -y"
    ]
  }
}

Explanation

Provisioners allow us to execute commands during resource creation.

They are useful for simple bootstrapping tasks but should be used thoughtfully. For complex configuration management, external tools such as Ansible or cloud-init are generally more appropriate.

9. Output Management

output "instance_public_ip" {
  value       = aws_instance.app.public_ip
  description = "Public IP of the application server"
}

Explanation

Outputs expose selected resource attributes after deployment.

They are useful for:

  • Integrating with CI/CD pipelines
  • Referencing values in other modules
  • Operational visibility

10. Provider Version Pinning

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  required_version = ">= 1.6.0"
}

Explanation

Version constraints ensure compatibility between OpenTofu and providers.

  • required_providers defines provider source and version.
  • required_version enforces the minimum OpenTofu version.

Pinning versions helps maintain consistent behavior across environments and prevents unexpected changes due to automatic upgrades.

Tips for Using OpenTofu on Ubuntu

1. Use Version Control

Store *.tf files in Git. This keeps changes traceable and collaborative.

2. Lock Provider Versions

Specify exact provider versions to ensure consistent behavior across systems.

3. Modularize Configurations

Break configurations into reusable modules for organization and reuse.

4. Automate with CI/CD

Integrate OpenTofu runs (plan/apply) into pipelines for repeatable and auditable deployments.

Conclusion

Installing and using OpenTofu on Ubuntu 24.04 is a reliable way to manage infrastructure declaratively. We install the binary, verify it, initialize projects, and go from planning to applying changes step by step. With best practices like version locking and automation, OpenTofu becomes a powerful part of our DevOps toolkit.