blog

Log | Files | Refs

infrastructure-as-code-with-opentofu.md (9132B)


      1 +++
      2 date = '2026-05-14T05:17:27-05:00'
      3 draft = false
      4 title = 'Infrastructure as Code With OpenTofu'
      5 +++
      6 
      7 
      8 
      9 
     10 ## What is infrastructure as code?
     11 Simply put, infrastructure as code (IaC) means using declarative configurations
     12 represented in source code to manage infrastructure rather than doing each step manually.
     13 One benefit of managing infrastructure this way is idempotency. Idempotency in this context 
     14 means that the user can apply an OpenTofu plan as many times as needed and changes
     15 will only occur when the target systems don't match the described state.  
     16 
     17 Another benefit to managing
     18 infrastructure this way is that it gives us the ability to store our configurations in a version control
     19 system such as Git.  Version control gives us the ability to audit our source code, roll back changes
     20 when needed, and can enhance reproducibility and collaboration.  
     21 
     22 
     23 ## Installation
     24 
     25 We'll be using OpenTofu to provision our server infrastructure. If you haven't installed it yet, OpenTofu has packages available for all major Linux distributions, as well as WSL, FreeBSD, and MacOS. 
     26 Refer to the [official OpenTofu documentation](https://opentofu.org/docs/intro/install/) for installation instructions before continuing.
     27 
     28 ## A word on OpenTofu providers and hosting
     29 
     30 OpenTofu uses something called providers to access APIs for various cloud hosting providers.
     31 We'll be using [Hetzner](https://www.hetzner.com/) to host our server.  A couple of other popular choices are [Vultr](www.vultr.com) and [DigitalOcean.](https://www.digitalocean.com/) 
     32 A searchable list of additional providers is available in the [OpenTofu provider registry.](https://search.opentofu.org/providers) OpenTofu uses an API key from your hosting provider 
     33 to configure your infrastructure. Remember to follow your provider's instructions for creating one.  It's also a good idea to save the key in a password vault or other protected location.
     34 
     35 ## Defining Your Infrastructure
     36 To define our server we're going to use three configuration files:
     37 
     38 - `main.tf` — the primary configuration file where our infrastructure is defined
     39 - `variables.tf` — holds the input variables we'll set for our deployment
     40 - `outputs.tf` — retrieves useful information after provisioning, such as our server's IP address
     41 
     42 OpenTofu comes with a couple of nice built-in tools for formatting its own files.  If you suspect a formatting issue you can run `tofu fmt` and OpenTofu will clean up any formatting issues that it finds.
     43 There is also `tofu validate` which can be used to check for syntax and logic errors.  Most modern text editors also have plugins available to assist with defining your infrastructure. Refer to your text editor's documentation for details about that.
     44 ### `main.tf`
     45 This is our main configuration file for defining our infrastructure.  It will tell OpenTofu what to configure and where. Mine looks like:
     46 ```hcl
     47 terraform {
     48   required_providers {
     49     hcloud = {
     50       source  = "hetznercloud/hcloud"
     51       version = "~> 1.50"
     52     }
     53   }
     54 }
     55 
     56 provider "hcloud" {
     57   token = var.hcloud_token
     58 }
     59 
     60 resource "hcloud_server" "main" {
     61   name        = "learningunix"
     62   server_type = "cx23"
     63   image       = "debian-13"
     64   location    = var.location
     65   ssh_keys    = [var.ssh_key_name]
     66 }
     67 ```
     68 
     69 The `terraform` block declares the providers your configuration depends on and where to download them from. The `required_providers` section names each provider — here `hcloud` is the local name we give the Hetzner provider. `source` tells OpenTofu where to find it in the provider registry, and `version` sets a constraint on which versions are acceptable. The `~>` operator means "pessimistic constraint" — `~> 1.50` allows any version `>= 1.50` and `< 2.0`, so you get bug fixes automatically but won't break on a major version change. See the [OpenTofu Settings documentation](https://opentofu.org/docs/language/settings/) for more details.
     70 
     71 The `provider` block configures the provider itself. For Hetzner, the only required setting is `token`, which is the API key OpenTofu will use to authenticate with Hetzner's API. Rather than hardcoding the key here, we reference `var.hcloud_token` — a variable defined in `variables.tf` — so the secret stays out of our source code. See the [OpenTofu Provider Configuration documentation](https://opentofu.org/docs/language/providers/configuration/) for more details.
     72 
     73 The `resource` block defines a piece of infrastructure to create. The block type is `resource`, followed by the resource type (`hcloud_server`) and a local name (`main`) used to reference this resource elsewhere in your config. Inside the block, each attribute maps to a setting on the server. See the [OpenTofu Resource Blocks documentation](https://opentofu.org/docs/language/resources/syntax/) for more details.
     74 
     75 - `name` — the hostname assigned to the server in Hetzner
     76 - `server_type` — the VM size; `cx23` is a shared-CPU instance with 2 vCPU and 4 GB RAM
     77 - `image` — the operating system image to install
     78 - `location` — the Hetzner datacenter region
     79 - `ssh_keys` — a list of SSH key names registered in your Hetzner account to install on the server at creation time
     80 
     81 ### `variables.tf`
     82 
     83 `variables.tf` declares the input variables that `main.tf` references. Declaring a variable here does not set its value — it defines the variable's name, an optional description, an optional default, and whether it should be treated as sensitive.
     84 
     85 ```hcl
     86 variable "hcloud_token" {
     87   sensitive = true
     88 }
     89 
     90 variable "ssh_key_name" {
     91   description = "Name of the SSH key in your Hetzner account"
     92 }
     93 
     94 variable "location" {
     95   default = "nbg1"
     96 }
     97 ```
     98 
     99 - `hcloud_token` — the Hetzner API key. Marked `sensitive = true`, which tells OpenTofu to redact its value from plan and apply output.
    100 - `ssh_key_name` — the name of the SSH key registered in your Hetzner account that will be installed on the server at creation time.
    101 - `location` — the Hetzner datacenter region. `nbg1` is Nuremberg, Germany. This variable has a default, so it does not need to be set explicitly unless you want a different region.
    102 
    103 ### `terraform.tfvars`
    104 
    105 Actual values are set in a separate file called `terraform.tfvars`. OpenTofu reads this file automatically when you run `tofu plan` or `tofu apply`. A `terraform.tfvars` file looks like this:
    106 
    107 ```hcl
    108 hcloud_token = "your-api-key-here"
    109 ssh_key_name = "your-ssh-key-name"
    110 ```
    111 
    112 **Keep `terraform.tfvars` out of version control.** It contains potentially sensitive data and should never be committed to a repository.
    113 
    114 
    115 ### `outputs.tf`
    116 
    117 `outputs.tf` defines values that OpenTofu will display after a successful `tofu apply`. This is useful for retrieving information about the infrastructure that was just created, such as an IP address you'll need to connect to your server.
    118 
    119 ```hcl
    120 output "server_ip" {
    121   value = hcloud_server.main.ipv4_address
    122 }
    123 ```
    124 
    125 The `output` block is named `server_ip`. The `value` attribute references the `ipv4_address` attribute of the `hcloud_server` resource we defined in `main.tf`. After applying, OpenTofu will print the server's public IP address to the terminal.
    126 
    127 ## Applying the Configuration
    128 
    129 Before applying the configuration, it's good practice to format your files. Run `tofu fmt`, which will clean up any formatting issues and ensure your files conform to HCL style conventions:
    130 
    131 ```
    132 tofu fmt
    133 ```
    134 
    135 Next, initialize the working directory. This downloads the provider plugins declared in your `terraform` block and only needs to be run once, or again if you add new providers:
    136 
    137 ```
    138 tofu init
    139 ```
    140 
    141 Now run `tofu validate` to check for syntax and logic errors before touching any infrastructure. Note that running `tofu validate` before `tofu init` will produce an error — the provider must be downloaded first so OpenTofu has the schema it needs to validate your resource definitions:
    142 
    143 ```
    144 tofu validate
    145 ```
    146 
    147 Run a plan to preview what OpenTofu intends to do before making any changes:
    148 
    149 ```
    150 tofu plan
    151 ```
    152 
    153 OpenTofu will display a list of resources it will create, modify, or destroy. Review it carefully — nothing has been changed yet at this point. When you're satisfied the plan looks correct, apply it:
    154 
    155 ```
    156 tofu apply
    157 ```
    158 
    159 OpenTofu will display the plan one more time and prompt you to confirm before proceeding. Type `yes` to continue. When it finishes, the output defined in `outputs.tf` will be printed to the terminal:
    160 
    161 ```
    162 Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
    163 
    164 Outputs:
    165 
    166 server_ip = "1.2.3.4"
    167 ```
    168 
    169 You can now use that IP address to connect to your server. Hetzner Debian images use `root` as the default user:
    170 
    171 ```
    172 ssh root@<server_ip>
    173 ```
    174 
    175 If your SSH key is not your default key, specify it explicitly with the `-i` flag:
    176 
    177 ```
    178 ssh -i ~/.ssh/your_key root@<server_ip>
    179 ```
    180 
    181 
    182 ## Conclusion
    183 
    184 At this point we have a running Debian server on Hetzner, provisioned entirely from code. The configuration is reproducible. 
    185 If you ever need to rebuild the server, running `tofu apply` again will produce an identical result. 
    186 The sensitive values stay out of your source code, and the infrastructure itself is described in a handful of readable text files.