Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Proxmox IaC Docker

Small IaC project focused on demonstrating the full automation flow: provisioning an Ubuntu 24.04 VM on Proxmox with Terraform, reading Proxmox credentials from Vault, and configuring the VM with Ansible to run Nginx in Docker.

Stack

  • Proxmox
  • Terraform
  • HashiCorp Vault
  • Ansible
  • Docker
  • Docker Compose
  • Nginx

Terraform Provider

This project uses the bpg/proxmox Terraform provider:

https://registry.terraform.io/providers/bpg/proxmox/latest/docs

Provider used in this project:

proxmox = {
  source  = "bpg/proxmox"
  version = "0.106.0"
}

Terraform File Structure

Terraform reads all .tf files in the same directory.

The main.tf file is kept as a short entrypoint note only.
The actual configuration is split into dedicated files:

providers.tf -> providers, Vault secret read, Proxmox provider config
variables.tf -> input variables
vm.tf        -> Proxmox VM resource
outputs.tf   -> Terraform outputs

This keeps the project structure simple and easy to read.

Terraform Variables

Variable Default Description
vault_mount secret Vault KV secrets engine name.
proxmox_secret proxmox Vault secret name that contains the Proxmox credentials.
proxmox_node pve Proxmox node where the VM will be created.
ubuntu_template_id 9000 Proxmox VM template ID used for cloning Ubuntu 24.04.
vm_name tf-vm-docker-01 Name of the VM that Terraform creates.
vm_id 200 Proxmox VM ID. Must be unique in Proxmox.
vm_ipv4_address 10.0.20.201/24 Static IPv4 address assigned to the VM through cloud-init.
vm_gateway 10.0.20.1 Default gateway for the VM.
vm_bridge vmbr0 Proxmox network bridge used by the VM.
vm_username ubuntu Linux user created by cloud-init.
ssh_public_key no default Public SSH key injected into the VM for Ansible access. Marked as sensitive.

The ssh_public_key value should usually be provided through terraform.tfvars or another local method.

Example:

ssh_public_key = "ssh-ed25519 AAAA..."

Do not commit terraform.tfvars to Git.

Proxmox Requirements

Before running Terraform, Proxmox should have:

1. Ubuntu 24.04 cloud-init template
2. Proxmox API token
3. Storage available for the VM
4. Network bridge available for the VM
5. SSH public key for cloud-init access

Template used in this project:

OS: Ubuntu 24.04
Template ID: 9000

Default VM values:

VM name: tf-vm-docker-01
VM ID: 200
VM IP: 10.0.20.201/24
Gateway: 10.0.20.1
Bridge: vmbr0
User: ubuntu

Proxmox User, Role and API Token

Terraform connects to Proxmox with an API token.

The recommended setup is:

1. Create a dedicated Proxmox user for Terraform
2. Assign permissions to that user
3. Create an API token for that user
4. Store the token values in Vault

Simple Homelab Setup

For a small homelab project, the simplest option is to create a dedicated user and assign the built-in PVEAdmin role.

Example:

pveum user add terraform@pve
pveum aclmod / -user terraform@pve -role PVEAdmin
pveum user token add terraform@pve terraform-token --privsep 0

The full token ID will look like:

terraform@pve!terraform-token

When --privsep 0 is used, the token inherits the user's permissions.

UI Path

You can also create the token from the Proxmox UI:

Datacenter -> Permissions -> API Tokens -> Add

Example token format:

User: terraform@pve
Token ID: terraform-token
Full token ID: terraform@pve!terraform-token

Custom Role Option

For a stricter setup, create a custom role instead of using PVEAdmin.

Example role:

pveum role add TerraformProv -privs "VM.Allocate VM.Audit VM.Clone VM.Config.CPU VM.Config.Cloudinit VM.Config.Disk VM.Config.HWType VM.Config.Memory VM.Config.Network VM.Config.Options VM.Monitor VM.PowerMgmt Datastore.Audit Datastore.AllocateSpace Sys.Audit SDN.Use"
pveum user add terraform@pve
pveum aclmod / -user terraform@pve -role TerraformProv
pveum user token add terraform@pve terraform-token --privsep 0

This custom role is a starting point.
Depending on your Proxmox storage, network, template, and provider configuration, you may need to adjust permissions.

Terraform needs permissions to:

clone VM templates
create VMs
configure VM hardware
configure disks
configure cloud-init
start and stop VMs
use the target storage
use the target network bridge

Vault Secrets

Proxmox credentials are stored in local HashiCorp Vault.

Vault runs locally on my machine.

Vault access is provided with a local zsh environment variable:

export VAULT_TOKEN="..."

Vault KV engine:

secret

Vault secret path:

secret/proxmox

KV v2 API path:

secret/data/proxmox

Required Vault keys:

proxmox_api_url
proxmox_token_id
proxmox_token_secret

Example values:

proxmox_api_url      = https://10.0.0.10:8006/api2/json
proxmox_token_id     = terraform@pve!terraform-token
proxmox_token_secret = <PROXMOX_TOKEN_SECRET>

Example command:

vault kv put secret/proxmox \
  proxmox_api_url="https://10.0.0.10:8006/api2/json" \
  proxmox_token_id="terraform@pve!terraform-token" \
  proxmox_token_secret="<PROXMOX_TOKEN_SECRET>"

Terraform Vault Usage

Terraform reads the Proxmox values from Vault:

provider "vault" {}

ephemeral "vault_kv_secret_v2" "proxmox" {
  mount = var.vault_mount
  name  = var.proxmox_secret
}

The Proxmox provider uses the Vault values:

provider "proxmox" {
  endpoint  = ephemeral.vault_kv_secret_v2.proxmox.data["proxmox_api_url"]
  api_token = "${ephemeral.vault_kv_secret_v2.proxmox.data["proxmox_token_id"]}=${ephemeral.vault_kv_secret_v2.proxmox.data["proxmox_token_secret"]}"
  insecure  = true
}

Terraform Flow

Run from the Terraform directory:

cd terraform
terraform init
terraform plan
terraform apply

Terraform creates the VM in Proxmox.

Terraform outputs:

vm_name
vm_id
vm_ipv4_address

Ansible SSH Access

Ansible connects to the VM with a local SSH private key.

Example ansible/inventory.ini:

[web]
tf-vm-docker-01 ansible_host=10.0.20.201

[web:vars]
ansible_user=ubuntu
ansible_python_interpreter=/usr/bin/python3.12
ansible_ssh_private_key_file=~/.ssh/id_ed25519
nginx_port=80

The private SSH key stays local and is not committed to Git.

Ansible Flow

Run from the Ansible directory:

cd ansible

Bootstrap the VM:

ansible-playbook playbooks/bootstrap.yaml

Deploy Docker and Nginx:

ansible-playbook playbooks/site.yaml

Cleanup Nginx:

ansible-playbook playbooks/cleanup.yaml

Playbooks

bootstrap.yaml -> runs system role
site.yaml      -> runs docker role and nginx role
cleanup.yaml   -> removes the Nginx deployment

Roles

system -> apt update and apt upgrade
docker -> install Docker, Docker Compose, and check Docker status
nginx  -> copy docker-compose template, run Nginx, and check HTTP 200

Full Flow

1. Create Ubuntu 24.04 cloud-init template in Proxmox
2. Create a Proxmox user, role and API token for Terraform
3. Store proxmox_api_url, proxmox_token_id, and proxmox_token_secret in Vault
4. Export VAULT_TOKEN locally in zsh
5. Run Terraform to create the VM
6. Run Ansible bootstrap
7. Run Ansible site
8. Open Nginx on port 80
9. Run Ansible cleanup when needed
10. Run terraform destroy when the VM is no longer needed

Security Notes

Do not commit:

terraform.tfstate
terraform.tfstate.backup
terraform.tfvars
Vault token
Proxmox token secret
SSH private key

Recommended .gitignore entries:

# Terraform
terraform/**/.terraform/*
terraform/**/*.tfstate
terraform/**/*.tfstate.*
terraform/**/*.tfvars
terraform/**/*.tfvars.json
terraform/**/crash.log
terraform/**/crash.*.log
terraform/**/*.tfplan
terraform/**/*.plan
terraform/**/*.tfoverride
terraform/**/*.tfoverride.json
terraform/**/*_override.tf
terraform/**/*_override.tf.json

# Ansible
ansible/**/*.retry
ansible/**/.ansible/
ansible/**/ansible.log
ansible/**/.vault_pass*
ansible/**/vault-password*

# Secrets / env
.env
.env.*
!.env.example
*.pem
*.key

# macOS / IDE
.DS_Store
.vscode/
.idea/

Note

This is a personal portfolio project.
PRs will not be reviewed or merged.

About

IaC project using Terraform, Vault and Ansible to provision a Proxmox VM and deploy Nginx with Docker Compose.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages