├── .gitignore ├── .gitlab-ci.yml ├── README.md ├── cloudinit.tf ├── lxc ├── ansible.yaml └── snmpmonitor.yaml ├── main.tf ├── modules ├── lxc │ ├── main.tf │ ├── output.tf │ └── variables.tf └── vm │ ├── main.tf │ ├── output.tf │ └── variables.tf └── vm ├── amp-go.yaml ├── graylog.yaml └── testvm.yaml.example /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | crash.*.log 11 | 12 | # Exclude all .tfvars files, which are likely to contain sentitive data, such as 13 | # password, private keys, and other secrets. These should not be part of version 14 | # control as they are data points which are potentially sensitive and subject 15 | # to change depending on the environment. 16 | # 17 | *.tfvars 18 | 19 | # Ignore override files as they are usually used to override resources locally and so 20 | # are not checked in 21 | override.tf 22 | override.tf.json 23 | *_override.tf 24 | *_override.tf.json 25 | 26 | # Include override files you do wish to add to version control using negated pattern 27 | # 28 | # !example_override.tf 29 | 30 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 31 | # example: *tfplan* 32 | 33 | # Ignore CLI configuration files 34 | .terraformrc 35 | terraform.rc 36 | *.lock.hcl 37 | kubeconfig/* 38 | **/unencrypted/* 39 | **/.idea/* 40 | **/.vscode/* -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # This file is a template, and might need editing before it works on your project. 2 | # To contribute improvements to CI/CD templates, please follow the Development guide at: 3 | # https://docs.gitlab.com/ee/development/cicd/templates.html 4 | # This specific template is located at: 5 | # https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml 6 | 7 | include: 8 | - template: Terraform/Base.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Terraform/Base.gitlab-ci.yml 9 | # - template: Jobs/SAST-IaC.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/SAST-IaC.gitlab-ci.yml 10 | 11 | 12 | #before_script: 13 | # - wget https://github.com/mozilla/sops/releases/download/v3.7.3/sops-v3.7.3.linux.amd64 -O /usr/bin/sops 14 | # - chmod +x /usr/bin/sops 15 | # - sops --decrypt --in-place proxmox-gitlab.tfbackend 16 | 17 | stages: 18 | - validate 19 | # - test 20 | - build 21 | - deploy 22 | - cleanup 23 | 24 | fmt: 25 | extends: .terraform:fmt 26 | needs: [] 27 | tags: 28 | - dev 29 | 30 | validate: 31 | extends: .terraform:validate 32 | needs: [] 33 | tags: 34 | - dev 35 | 36 | build: 37 | extends: .terraform:build 38 | tags: 39 | - staging 40 | 41 | deploy: 42 | extends: .terraform:deploy 43 | tags: 44 | - prod 45 | dependencies: 46 | - build 47 | environment: 48 | name: $TF_STATE_NAME 49 | 50 | cleanup: 51 | extends: .terraform:destroy 52 | tags: 53 | - prod 54 | dependencies: 55 | - deploy 56 | environment: 57 | name: $TF_STATE_NAME 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # proxmox-terraform 2 | 3 | ## Description 4 | 5 | This repository contains a set of Terraform modules to deploy a Proxmox resources. 6 | 7 | ## Requirements 8 | 9 | - [Terraform](https://www.terraform.io/downloads.html) >= 0.13 10 | - [Proxmox](https://www.proxmox.com/en/downloads) >= 7.0 11 | - [Proxmox Provider requirement](https://registry.terraform.io/providers/Telmate/proxmox/latest/docs) 12 | - bind9 or any dns provider that support dynamic dns 13 | - [cloud-init](https://cloudinit.readthedocs.io/en/latest/) (optional) 14 | - [Hashicorp Vault](https://www.vaultproject.io/) 15 | - [Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/) 16 | 17 | ## Usage 18 | 19 | ### LXC 20 | 21 | ```yaml 22 | hostname: "mylxc" 23 | description: "a description" 24 | template: "local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst" # image path on proxmox 25 | unprivileged: true # can be true or false 26 | size: "small" # size of the container. Can be "small", "medium", "large" or "xlarge" 27 | onboot: true # start on boot can be true or false 28 | start: true # start after creation can be true or false 29 | ssh_public_keys: 30 | MySSHPublicKey 31 | ip_address: "10.0.0.105" # static ip address 32 | tags: "lxc,ubuntu,monitoring" # comma separated list of tags 33 | ``` 34 | 35 | ### VM 36 | 37 | ```yaml 38 | hostname: "testvm" 39 | description: "testvm" 40 | os: "rocky" # can be "rocky", "debian" or "rhel" 41 | size: "small" # size of the container. Can be "small", "medium", "large" or "xlarge" 42 | ip_address: "10.0.0.122" # static ip address 43 | tags: "testvm,test" # comma separated list of tags 44 | ``` 45 | 46 | ## Modules specifications 47 | 48 | size: 49 | - small: 50 | - lxc: 1 CPU, 1GB RAM, 10GB Disk 51 | - vm: 2 sockets, 2 Core, 1GB RAM, 10GB Disk 52 | - medium: 53 | - lxc: 2 CPU, 2GB RAM, 20GB Disk 54 | - vm: 2 sockets, 2 Core, 2GB RAM, 20GB Disk 55 | - large: 56 | - lxc: 4 CPU, 4GB RAM, 30GB Disk 57 | - vm: 2 sockets, 4 Core, 4GB RAM, 40GB Disk 58 | - xlarge: 59 | - lxc: 8 CPU, 8GB RAM, 40GB Disk 60 | - vm: 2 sockets, 8 Core, 8GB RAM, 80GB Disk 61 | 62 | ## LXC specifications 63 | 64 | Vault and Azure Key Vault are supported to store the generated password from terraform. -------------------------------------------------------------------------------- /cloudinit.tf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deltxprt/proxmox-terraform/1296c49957c65da4c29281ac73c5e54934bdbd69/cloudinit.tf -------------------------------------------------------------------------------- /lxc/ansible.yaml: -------------------------------------------------------------------------------- 1 | hostname: "ansible" 2 | description: "ansible management endpoint" 3 | template: "local:vztmpl/ubuntu-23.04-standard_23.04-1_amd64.tar.zst" 4 | unprivileged: true 5 | size: "medium" 6 | onboot: true 7 | start: true 8 | ssh_public_keys: 9 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO8fI+pCmr7L46GUm2GkqVxIRYGXtZNH7R71pTviN7Up Generated By Termius 10 | ip_address: "10.0.0.112" 11 | tags: "lxc,ubuntu,configuration" -------------------------------------------------------------------------------- /lxc/snmpmonitor.yaml: -------------------------------------------------------------------------------- 1 | hostname: "snmp" 2 | description: "snmp monitor" 3 | template: "local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst" 4 | unprivileged: true 5 | size: "medium" 6 | onboot: true 7 | start: true 8 | ssh_public_keys: 9 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO8fI+pCmr7L46GUm2GkqVxIRYGXtZNH7R71pTviN7Up Generated By Termius 10 | ip_address: "10.0.0.105" 11 | tags: "lxc,ubuntu,monitoring" -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | cloud { 3 | organization = "markaplay" 4 | workspaces { 5 | name = "proxmox-terraform" 6 | } 7 | } 8 | required_providers { 9 | proxmox = { 10 | source = "Telmate/proxmox" 11 | version = "2.9.14" 12 | } 13 | azurerm = { 14 | source = "hashicorp/azurerm" 15 | version = "3.58.0" 16 | } 17 | random = { 18 | source = "hashicorp/random" 19 | version = "3.5.1" 20 | } 21 | vault = { 22 | source = "hashicorp/vault" 23 | version = "3.15.2" 24 | } 25 | dns = { 26 | source = "hashicorp/dns" 27 | version = "3.3.2" 28 | } 29 | } 30 | } 31 | 32 | variable "address" {} 33 | variable "roleid" {} 34 | variable "secretid" {} 35 | 36 | provider "vault" { 37 | address = var.address 38 | auth_login { 39 | path = "auth/approle/login" 40 | 41 | parameters = { 42 | role_id = var.roleid 43 | secret_id = var.secretid 44 | } 45 | } 46 | } 47 | 48 | data "vault_generic_secret" "azure_secrets" { 49 | path = "proxmox/azure" 50 | } 51 | 52 | provider "azurerm" { 53 | client_id = data.vault_generic_secret.azure_secrets.data["client_id"] 54 | client_secret = data.vault_generic_secret.azure_secrets.data["client_secret"] 55 | tenant_id = data.vault_generic_secret.azure_secrets.data["tenant_id"] 56 | subscription_id = data.vault_generic_secret.azure_secrets.data["subscription_id"] 57 | features { 58 | key_vault { 59 | purge_soft_deleted_secrets_on_destroy = true 60 | recover_soft_deleted_secrets = true 61 | } 62 | } 63 | } 64 | 65 | data "vault_generic_secret" "dns-key" { 66 | path = "bind-dns/ns1" 67 | } 68 | 69 | #data.vault_generic_secret.dns-key.data["tsig"] 70 | provider "dns" { 71 | update { 72 | server = "10.0.0.111" 73 | key_name = "tsig-key." 74 | key_algorithm = "hmac-sha256" 75 | key_secret = data.vault_generic_secret.dns-key.data["tsig"] 76 | } 77 | } 78 | 79 | data "vault_generic_secret" "proxmox_secrets" { 80 | path = "proxmox/terraform" 81 | } 82 | 83 | provider "proxmox" { 84 | pm_api_url = data.vault_generic_secret.proxmox_secrets.data["url"] 85 | pm_api_token_id = data.vault_generic_secret.proxmox_secrets.data["user"] 86 | pm_api_token_secret = data.vault_generic_secret.proxmox_secrets.data["key"] 87 | } 88 | 89 | 90 | 91 | locals { 92 | lxc_files = fileset(".", "lxc/*.yaml") 93 | lxc = { for file in local.lxc_files : basename(file) => yamldecode(file(file)) } 94 | vm_files = fileset(".", "vm/*.yaml") 95 | vm = { for file in local.vm_files : basename(file) => yamldecode(file(file)) } 96 | } 97 | 98 | module "lxc_resource" { 99 | source = "./modules/lxc" 100 | for_each = local.lxc 101 | hostname = each.value.hostname 102 | description = each.value.description 103 | template = each.value.template 104 | unprivileged = each.value.unprivileged 105 | size = each.value.size 106 | onboot = each.value.onboot 107 | start = each.value.start 108 | ssh_public_keys = each.value.ssh_public_keys 109 | ip_address = each.value.ip_address 110 | tags = each.value.tags 111 | } 112 | module "vm_resource" { 113 | source = "./modules/vm" 114 | for_each = local.vm 115 | hostname = each.value.hostname 116 | description = each.value.description 117 | os = each.value.os 118 | size = each.value.size 119 | ip_address = each.value.ip_address 120 | tags = each.value.tags 121 | } 122 | 123 | #output "test" { 124 | # value = module.lxc_resource 125 | #} -------------------------------------------------------------------------------- /modules/lxc/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | proxmox = { 4 | source = "Telmate/proxmox" 5 | version = "2.9.14" 6 | } 7 | azurerm = { 8 | source = "hashicorp/azurerm" 9 | version = "3.58.0" 10 | } 11 | random = { 12 | source = "hashicorp/random" 13 | version = "3.5.1" 14 | } 15 | vault = { 16 | source = "hashicorp/vault" 17 | version = "3.15.2" 18 | } 19 | dns = { 20 | source = "hashicorp/dns" 21 | version = "3.3.2" 22 | } 23 | } 24 | } 25 | 26 | resource "random_password" "lxcpassword" { 27 | length = 25 28 | special = true 29 | } 30 | 31 | locals { 32 | lxc_size = { 33 | "small" = { 34 | cores = 1 35 | memory = 1024 36 | size = "10G" 37 | } 38 | "medium" = { 39 | cores = 2 40 | memory = 2048 41 | size = "20G" 42 | } 43 | "large" = { 44 | cores = 4 45 | memory = 4096 46 | size = "30G" 47 | } 48 | "xlarge" = { 49 | cores = 8 50 | memory = 8192 51 | size = "40G" 52 | } 53 | } 54 | } 55 | 56 | 57 | resource "proxmox_lxc" "lxc-servers" { 58 | target_node = "epyc" 59 | hostname = var.hostname 60 | description = var.description 61 | ostemplate = var.template 62 | password = random_password.lxcpassword.result 63 | unprivileged = var.unprivileged 64 | cores = local.lxc_size[var.size].cores 65 | memory = local.lxc_size[var.size].memory 66 | onboot = var.onboot 67 | start = var.start 68 | ssh_public_keys = var.ssh_public_keys 69 | 70 | // Terraform will crash without rootfs defined 71 | rootfs { 72 | storage = "vmpool" 73 | size = local.lxc_size[var.size].size 74 | } 75 | 76 | network { 77 | name = "eth0" 78 | bridge = "vmbr0" 79 | ip = format("%s/24", var.ip_address) 80 | gw = "10.0.0.1" 81 | } 82 | 83 | lifecycle { 84 | ignore_changes = [ 85 | description, 86 | ] 87 | } 88 | 89 | } 90 | 91 | resource "azurerm_key_vault_secret" "lxcpassword" { 92 | name = proxmox_lxc.lxc-servers.hostname 93 | value = random_password.lxcpassword.result 94 | key_vault_id = "/subscriptions/433a5766-0b1a-475e-aa9b-9556b6dab416/resourceGroups/Lab/providers/Microsoft.KeyVault/vaults/map-Vault-lab" 95 | } 96 | 97 | resource "vault_generic_secret" "lxclocalpassword" { 98 | path = "proxmox/${var.hostname}" 99 | data_json = jsonencode({ 100 | password = random_password.lxcpassword.result 101 | }) 102 | } 103 | 104 | resource "dns_a_record_set" "lxc_lab" { 105 | zone = "lab.markaplay.net." 106 | name = format("%s", var.hostname) 107 | addresses = [var.ip_address] 108 | ttl = 3600 109 | } 110 | 111 | resource "dns_ptr_record" "lxc_reverse_lab" { 112 | zone = "0.0.10.in-addr.arpa." 113 | name = split(".", var.ip_address)[3] 114 | ptr = format("%s.lab.markaplay.net.", var.hostname) 115 | ttl = 3600 116 | } 117 | 118 | #output "lxc_resource" { 119 | # value = var.lxc_data 120 | #} -------------------------------------------------------------------------------- /modules/lxc/output.tf: -------------------------------------------------------------------------------- 1 | output "id" { 2 | value = proxmox_lxc.lxc-servers.id 3 | } 4 | 5 | output "hostname" { 6 | value = proxmox_lxc.lxc-servers.hostname 7 | } 8 | 9 | output "ip" { 10 | value = proxmox_lxc.lxc-servers.network.0.ip 11 | } 12 | 13 | -------------------------------------------------------------------------------- /modules/lxc/variables.tf: -------------------------------------------------------------------------------- 1 | variable "hostname" { 2 | } 3 | variable "description" { 4 | } 5 | variable "template" { 6 | } 7 | variable "unprivileged" { 8 | default = true 9 | } 10 | variable "size" { 11 | default = "small" 12 | } 13 | variable "onboot" { 14 | default = true 15 | } 16 | variable "start" { 17 | default = true 18 | } 19 | variable "ssh_public_keys" { 20 | } 21 | variable "ip_address" { 22 | } 23 | variable "tags" { 24 | type = string 25 | } -------------------------------------------------------------------------------- /modules/vm/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | proxmox = { 4 | source = "Telmate/proxmox" 5 | version = "2.9.14" 6 | } 7 | azurerm = { 8 | source = "hashicorp/azurerm" 9 | version = "3.58.0" 10 | } 11 | random = { 12 | source = "hashicorp/random" 13 | version = "3.5.1" 14 | } 15 | vault = { 16 | source = "hashicorp/vault" 17 | version = "3.15.2" 18 | } 19 | dns = { 20 | source = "hashicorp/dns" 21 | version = "3.3.2" 22 | } 23 | } 24 | } 25 | locals { 26 | vm_size = { 27 | "small" = { 28 | sockets = 2 29 | cores = 1 30 | memory = 1024 31 | disk = "10G" 32 | } 33 | "medium" = { 34 | sockets = 2 35 | cores = 2 36 | memory = 2048 37 | disk = "20G" 38 | } 39 | "large" = { 40 | sockets = 2 41 | cores = 4 42 | memory = 4096 43 | disk = "40G" 44 | } 45 | "xlarge" = { 46 | sockets = 2 47 | cores = 8 48 | memory = 8192 49 | disk = "80G" 50 | } 51 | } 52 | operating_system = { 53 | "debian" = { 54 | os = "debian11" 55 | type = "l26" 56 | } 57 | 58 | "rhel" = { 59 | os = "rhel9" 60 | type = "l26" 61 | } 62 | "rocky" = { 63 | os = "rocky9" 64 | type = "l26" 65 | } 66 | "windows" = { 67 | os = "Win22" 68 | type = "win11" 69 | } 70 | } 71 | } 72 | 73 | resource "proxmox_vm_qemu" "vm-server" { 74 | target_node = "epyc" 75 | name = var.hostname 76 | desc = var.description 77 | agent = 1 78 | full_clone = true 79 | clone = local.operating_system[var.os].os 80 | cpu = "host" 81 | numa = true 82 | sockets = local.vm_size[var.size].sockets 83 | cores = local.vm_size[var.size].cores 84 | memory = local.vm_size[var.size].memory 85 | onboot = true 86 | os_type = "cloud-init" 87 | qemu_os = local.operating_system[var.os].type 88 | 89 | disk { 90 | type = "virtio" 91 | storage = "vmpool" 92 | size = local.vm_size[var.size].disk 93 | backup = true 94 | } 95 | 96 | network { 97 | model = "virtio" 98 | bridge = "vmbr0" 99 | firewall = false 100 | } 101 | ipconfig0 = format("ip=%s/24,gw=10.0.0.1", var.ip_address) 102 | 103 | tags = var.tags 104 | 105 | lifecycle { 106 | ignore_changes = [desc,tags,network,disk,cicustom] 107 | } 108 | 109 | } 110 | 111 | resource "dns_a_record_set" "vm_lab" { 112 | zone = "lab.markaplay.net." 113 | name = format("%s", var.hostname) 114 | addresses = [var.ip_address] 115 | ttl = 3600 116 | } 117 | 118 | resource "dns_ptr_record" "vm_reverse_lab" { 119 | zone = "0.0.10.in-addr.arpa." 120 | name = split(".", var.ip_address)[3] 121 | ptr = format("%s.lab.markaplay.net.", var.hostname) 122 | ttl = 3600 123 | } -------------------------------------------------------------------------------- /modules/vm/output.tf: -------------------------------------------------------------------------------- 1 | output "id" { 2 | value = proxmox_vm_qemu.vm-server.id 3 | } 4 | 5 | output "hostname" { 6 | value =proxmox_vm_qemu.vm-server.name 7 | } 8 | 9 | output "ip" { 10 | value = proxmox_vm_qemu.vm-server.ipconfig0 11 | } 12 | 13 | -------------------------------------------------------------------------------- /modules/vm/variables.tf: -------------------------------------------------------------------------------- 1 | variable "hostname" { 2 | type = string 3 | } 4 | variable "description" { 5 | } 6 | variable "os" { 7 | type = string 8 | } 9 | variable "size" { 10 | default = "small" 11 | } 12 | variable "ip_address" { 13 | type = string 14 | } 15 | variable "tags" { 16 | type = string 17 | } -------------------------------------------------------------------------------- /vm/amp-go.yaml: -------------------------------------------------------------------------------- 1 | hostname: "go-amp" 2 | description: "go amp dev machine" 3 | os: "rocky" 4 | size: "small" 5 | ip_address: "10.0.0.123" 6 | tags: "qemu,rocky,app" 7 | -------------------------------------------------------------------------------- /vm/graylog.yaml: -------------------------------------------------------------------------------- 1 | hostname: "greylog" 2 | description: "greylog log server" 3 | os: "rocky" 4 | size: "xlarge" 5 | ip_address: "10.0.0.107" 6 | tags: "qemu,rocky,monitoring" 7 | -------------------------------------------------------------------------------- /vm/testvm.yaml.example: -------------------------------------------------------------------------------- 1 | hostname: "testvm" 2 | description: "testvm" 3 | os: "rocky" 4 | size: "small" 5 | ip_address: "10.0.0.122" 6 | tags: "testvm,test" 7 | --------------------------------------------------------------------------------