Creating virtual machines through the web interface is perfectly fine for first experiments. After a few months, though, nobody remembers why one VM has 6 GB of RAM instead of 4, or which options were enabled on the reverse proxy's VM. Infrastructure as code solves this: machines are described in text files, and a tool creates, changes or deletes resources until reality matches the description.
Terraform and OpenTofu
Terraform is the tool that made this approach popular. In 2023 its developer changed its licence to a non-open-source one; in response OpenTofu was born, a fork that stayed open source and is hosted by the Linux Foundation. The two tools share the configuration language, called HCL, and largely the providers as well. For a homelab, OpenTofu is the natural choice if you want to stay with free software; the concepts below apply to both.
The fundamental concepts
- Provider: the plugin that knows how to talk to a specific system, such as the Proxmox VE API. Community-maintained providers exist for Proxmox, and it is worth choosing an actively developed one after checking its documentation.
- Resource: an object to manage, for example a virtual machine or a container.
- State: a file where the tool records which resources it created and with which attributes. That is how it knows what to change next time.
- Plan and apply:
tofu planshows the changes that would be made;tofu applycarries them out after confirmation.
An example workflow
A minimal configuration has three parts: the provider, variables and resources. In simplified form, a virtual machine cloned from a cloud-init template might be described like this:
resource "proxmox_virtual_environment_vm" "monitor" {
name = "monitor"
node_name = "pve1"
clone { vm_id = 9000 }
cpu { cores = 2 }
memory { dedicated = 4096 }
initialization {
ip_config {
ipv4 {
address = "192.168.20.30/24"
gateway = "192.168.20.1"
}
}
}
}
The exact block names depend on the provider you choose, so adapt the example to its documentation. The workflow is always the same:
tofu initdownloads the provider;tofu planshows that a VM will be created;tofu applycreates it;- you change the memory in the file,
tofu planshows a single change,tofu applyapplies it.
Credentials and secrets
The tool connects to the hypervisor API with an API token. Some rules:
- create a dedicated user with only the permissions needed, rather than the main admin account;
- do not write tokens and passwords in versioned files: pass them through environment variables or variable files excluded from the repository;
- remember that the state file can contain sensitive values in plain text; protect it like a secret and never publish it.
OpenTofu also offers state file encryption, a feature Terraform does not include in its free edition. For a homelab whose repository lives on an external service, that is one more reason to prefer it.
Where to keep state
At home the simplest solution is local state, in a folder that is backed up. If you work from several computers, you can use an S3-compatible remote backend, such as a self-hosted object storage service. What matters is that there is a single authoritative copy: two diverging states lead the tool to create duplicates or delete resources unexpectedly.
Splitting the work with configuration management
OpenTofu creates the infrastructure: machines, disks, networks. Configuring what is inside the machines, meaning packages, files and services, is the job of configuration management tools such as Ansible, or of cloud-init itself. A clear split works well:
| Layer | Tool |
|---|---|
| Creating VMs and containers | OpenTofu or Terraform |
| First boot, users, SSH keys | cloud-init |
| Packages, configuration, services | Ansible |
| Applications | versioned Compose files |
When it pays off, and when it does not
In my work I find infrastructure as code repays the effort when there are more than a handful of machines, when they are rebuilt often or when you want to be able to recreate everything after a failure. For a homelab with three VMs that never change, the gain is mostly educational, and that has value too, because these are the same skills used in the cloud.
A practical tip: do not import all your existing infrastructure at once. Start by managing only new machines with code, and bring the rest over one piece at a time.
In short
- Machines are described in HCL files; plan shows the changes, apply carries them out.
- OpenTofu is the open source fork of Terraform, with compatible language and providers.
- Use API tokens with minimal permissions and keep secrets out of the repository.
- The state file is sensitive and must exist as a single authoritative copy.
- OpenTofu creates the machines; cloud-init and Ansible configure what is inside.