Post

Getting started with Terraform

Getting started with Terraform

At this point I have never had any real hands-on experience with Terraform - I have been aware of it and what it does, but never actually used it. Now, however I feel like I want to automate my homelab setup one step further, and I believe starting from the infrastructure is as good place to start as any. Plus, I like the idea of having my homelab setup in declarative form for the future reference.

As I already use Semaphore UI for running some of my Ansible playbooks, I will be using it to run Terraform/OpenTofu as well.

Preparing for Terraform

To keep my Terraform files separate from everything else, I created a new repository in my self-hosted Gitea instance, and added the details into the Semaphore UI.

Starting with Terraform

providers.tf

To get the variables from Semaphore UI Variable Groups, the vars should include TF_VAR_ prefix. For instance in the provider.tf file, a variable is referenced as pm_api_url and in Semaphore UI, the same variable is TF_VAR_pm_api_url.

Create a new file provider.tf. This file will include the provider details and the Proxmox instance details.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
terraform {
  required_providers {
    proxmox = {
      source = "Telmate/proxmox"
      version = ">= 2.9.0"
    }
  }
}

variable "pm_api_url" {
  type = string
}

variable "pm_api_id" {
  type = string
}

variable "pm_api_token" {
  type = string
  sensitive = true
}

provider "proxmox" {
  pm_api_url = var.pm_api_url
  pm_api_token_id = var.pm_api_id
  pm_api_token_secret = var.pm_api_token

  # Set this true only when Proxmox host is using self-signed certificates.
  pm_tls_insecure = true
}

Module for LXC containers

I wanted as little repetition as possible, so I opted for using a module to define the logic for LXC container setup. This way I can have simpler files in the root folder defining the containers themselves.

Create a folder called modules and inside it a folder called lxc. Add four files (main.tf, outputs.tf, variables.tf, and versions.tf) into the module folder.

Details about available variables for the module can be found in the provider registry page.

main.tf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
resource "proxmox_lxc" "container" {
  features {
      nesting = var.nesting
  }

  unprivileged = var.unprivileged
  onboot = var.onboot
  start = var.start

  rootfs {
    storage = var.disk_storage
    size    = var.disk_size
  }

  network {
    name = var.network_name
    bridge = var.network_bridge
    ip = var.network_ip
    gw = var.network_gw
    tag = var.network_tag
  }

  nameserver = var.network_ns
  
  ssh_public_keys = var.ssh_keys
  password = var.lxc_password
  target_node = var.target_node
  hostname = var.hostname
  ostemplate = var.ostemplate
  vmid = var.vmid
  cores = var.cores
  memory = var.memory
  swap = var.swap
}

outputs.tf

1
2
3
output "lxc_ip" {
  value = proxmox_lxc.container.network[0].ip
}

variables.tf

In this file, default values can be set. The variables without default values would need to be defined in the file for the actual container definition. E.g. 100-my-important-ct.tf.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
variable "nesting" {
  default = true
}

variable "unprivileged" {
  default = true
}
variable "onboot" {
  default = false
}
variable "start" {
  default = true
}

variable "disk_storage" {
  default = "local"
}
variable "disk_size" {
  default = "8G"
}

variable "network_name" {
  default = "eth0"
}
variable "network_bridge" {
  default = "vmbr0"
}
variable "network_ip" {
  default = "dhcp"
}
variable "network_gw" {
  default = ""
}
variable "network_tag" {
  default = null
}

variable "network_ns" {
  default = ""
}

variable "ssh_keys" {
  default = ""
}
variable "lxc_password" {}
variable "target_node" {}
variable "hostname" {}
variable "ostemplate" {
  default = "local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst"
}
variable "vmid" {
  default = 0
}
variable "cores" {
  default = 1
}
variable "memory" {
  default = 512
}
variable "swap" {
  default = 512
}

versions.tf

Initially I tried without versions.tf file, however when running the task in Semaphore UI, OpenTofu was trying to pull a wrong provider and I kept getting errors. After some trial and error as well as research and consultation with an LLM, I found a solution that worked for me, even if it is a bit of code repetition.

1
2
3
4
5
6
7
8
terraform {
  required_providers {
    proxmox = {
      source = "Telmate/proxmox"
      version = ">= 2.9.0"
    }
  }
}

Defining LXC containers

To keep things better organised for myself and to avoid having a massive file to include all the lxc containers I want Terraform/OpenTofu to create, I will have a separate file in the root folder with the name of the container. E.g. 100-my-important-ct.tf.

Variables that don’t have defaults defined in ./modules/lxc/variables.tf will have to be defined regardless, others with defaults only when override is needed. E.g. more memory or cores etc.

1
2
3
4
5
6
7
8
9
10
module "lxc" {
  source = "./modules/lxc"
  target_node = "proxmox"
  hostname = "my-important-ct"
  vmid = 100
  cores = 2
  memory = 2048
  disk_size = "16G"
  lxc_password = "very-secure-root-password"
}

Voilà!


Buy Me A Coffee
This post is licensed under CC BY 4.0 by the author.