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:
- An Ubuntu 24.04 on dedicated server or KVM VPS.
- Basic Linux Command Line Knowledge.
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.
