diff --git a/content/posts/infrastructure-as-code-with-opentofu.md b/content/posts/infrastructure-as-code-with-opentofu.md index 738f720..1f3c720 100644 --- a/content/posts/infrastructure-as-code-with-opentofu.md +++ b/content/posts/infrastructure-as-code-with-opentofu.md @@ -1,8 +1,186 @@ +++ date = '2026-05-14T05:17:27-05:00' -draft = true -title = 'Infrastructure as Code With Opentofu' +draft = false +title = 'Infrastructure as Code With OpenTofu' +++ + +## What is infrastructure as code? +Simply put, infrastructure as code (IaC) means using declarative configurations +represented in source code to manage infrastructure rather than doing each step manually. +One benefit of managing infrastructure this way is idempotency. Idempotency in this context +means that the user can apply an OpenTofu plan as many times as needed and changes +will only occur when the target systems don't match the described state. + +Another benefit to managing +infrastructure this way is that it gives us the ability to store our configurations in a version control +system such as Git. Version control gives us the ability to audit our source code, roll back changes +when needed, and can enhance reproducibility and collaboration. + + +## Installation + +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. +Refer to the [official OpenTofu documentation](https://opentofu.org/docs/intro/install/) for installation instructions before continuing. + +## A word on OpenTofu providers and hosting + +OpenTofu uses something called providers to access APIs for various cloud hosting providers. +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/) +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 +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. + +## Defining Your Infrastructure +To define our server we're going to use three configuration files: + +- `main.tf` — the primary configuration file where our infrastructure is defined +- `variables.tf` — holds the input variables we'll set for our deployment +- `outputs.tf` — retrieves useful information after provisioning, such as our server's IP address + +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. +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. +### `main.tf` +This is our main configuration file for defining our infrastructure. It will tell OpenTofu what to configure and where. Mine looks like: +```hcl +terraform { + required_providers { + hcloud = { + source = "hetznercloud/hcloud" + version = "~> 1.50" + } + } +} + +provider "hcloud" { + token = var.hcloud_token +} + +resource "hcloud_server" "main" { + name = "learningunix" + server_type = "cx23" + image = "debian-13" + location = var.location + ssh_keys = [var.ssh_key_name] +} +``` + +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. + +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. + +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. + +- `name` — the hostname assigned to the server in Hetzner +- `server_type` — the VM size; `cx23` is a shared-CPU instance with 2 vCPU and 4 GB RAM +- `image` — the operating system image to install +- `location` — the Hetzner datacenter region +- `ssh_keys` — a list of SSH key names registered in your Hetzner account to install on the server at creation time + +### `variables.tf` + +`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. + +```hcl +variable "hcloud_token" { + sensitive = true +} + +variable "ssh_key_name" { + description = "Name of the SSH key in your Hetzner account" +} + +variable "location" { + default = "nbg1" +} +``` + +- `hcloud_token` — the Hetzner API key. Marked `sensitive = true`, which tells OpenTofu to redact its value from plan and apply output. +- `ssh_key_name` — the name of the SSH key registered in your Hetzner account that will be installed on the server at creation time. +- `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. + +### `terraform.tfvars` + +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: + +```hcl +hcloud_token = "your-api-key-here" +ssh_key_name = "your-ssh-key-name" +``` + +**Keep `terraform.tfvars` out of version control.** It contains potentially sensitive data and should never be committed to a repository. + + +### `outputs.tf` + +`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. + +```hcl +output "server_ip" { + value = hcloud_server.main.ipv4_address +} +``` + +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. + +## Applying the Configuration + +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: + +``` +tofu fmt +``` + +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: + +``` +tofu init +``` + +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: + +``` +tofu validate +``` + +Run a plan to preview what OpenTofu intends to do before making any changes: + +``` +tofu plan +``` + +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: + +``` +tofu apply +``` + +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: + +``` +Apply complete! Resources: 1 added, 0 changed, 0 destroyed. + +Outputs: + +server_ip = "1.2.3.4" +``` + +You can now use that IP address to connect to your server. Hetzner Debian images use `root` as the default user: + +``` +ssh root@ +``` + +If your SSH key is not your default key, specify it explicitly with the `-i` flag: + +``` +ssh -i ~/.ssh/your_key root@ +``` + + +## Conclusion + +At this point we have a running Debian server on Hetzner, provisioned entirely from code. The configuration is reproducible. +If you ever need to rebuild the server, running `tofu apply` again will produce an identical result. +The sensitive values stay out of your source code, and the infrastructure itself is described in a handful of readable text files.