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.