Homeserver infrastructure built with Terraform and Ansible on Proxmox VE.
┌───────────────────────────────────────────────────────┐
│ PROXMOX VE │
│ 32GB RAM │ 1TB SSD │ Ryzen 7 7730U │
├───────────────────────────────────────────────────────┤
│ Infrastructure VMs (Rocky Linux 10) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────────┐ │
│ │ IdM │ │ Vault │ │ Monitor │ │ PostgreSQL │ │
│ │ 4G/40G │ │ 4G/40G │ │ 4G/200G │ │ 4G/100G │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────────┘ │
├───────────────────────────────────────────────────────┤
│ Application VMs (Rocky Linux 10) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ App 1 │ │ App 2 │ │ ... │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└───────────────────────────────────────────────────────┘
This repository is split into two parts:
homeserver-iac/
├── infrastructure/ # Core infrastructure services
│ ├── terraform/ # VMs: IdM, Vault, Monitoring, PostgreSQL
│ └── ansible/ # Configuration for infrastructure
│
└── applications/ # Application VMs (separate management)
├── terraform/ # VMs: Custom applications
└── ansible/ # Configuration for applications
| Component | Tool | Purpose |
|---|---|---|
| Hypervisor | Proxmox VE | VM management |
| OS | Rocky Linux 10 | Base |
| Provisioning | Terraform | Create VMs |
| Configuration | Ansible | Configure VMs |
| Identity | FreeIPA | LDAP, Kerberos, CA |
| Secrets | HashiCorp Vault | Secret management |
| Management | Cockpit | Web-based server management (per-host) |
| Monitoring | Loki + Prometheus + Grafana | Logs and metrics |
| Database | PostgreSQL 17 | Application database |
| VM | Hostname | Ports |
|---|---|---|
| IdM | idm.home.local | 80, 443, 389, 636, 88, 464, 9090 |
| Vault | vault.home.local | 8200, 9090 |
| Monitoring | mon.home.local | 3000, 3100, 9091, 9090 |
| PostgreSQL | db.home.local | 5432, 9090 |
Note: Port 9090 is Cockpit (web-based management) on all hosts. Prometheus uses 9091.
- Proxmox VE 8.x installed on the server
- Terraform >= 1.5.0
- Ansible >= 2.15
- Python 3.9+
Download the minimal ISO from rockylinux.org:
- Rocky-10-latest-x86_64-minimal.iso
Upload to Proxmox storage (Datacenter → Storage → ISO Images → Upload).
# Generate SSH key if not already available
# No need to setup a password, as later, this account will be disabled on active hosts.
ssh-keygen -t ed25519 -f ~/.ssh/homeserver_setup -C "homeserver-setup"
# Display public key (needed during installation)
cat ~/.ssh/homeserver_setup.pub# On Proxmox console - create VM
qm create 9000 --name rocky10-template --memory 2048 --cores 2 \
--cpu host --machine q35 --agent enabled=1 --net0 virtio,bridge=vmbr0
qm set 9000 --scsihw virtio-scsi-pci
qm set 9000 --scsi0 local-lvm:32
qm set 9000 --ide2 local:iso/Rocky-10-latest-x86_64-minimal.iso,media=cdrom
qm set 9000 --boot order=ide2
qm start 9000Open the VM console and complete installation:
- Set root password (or disable root)
- Create user
setupwith administrator privileges - Set timezone
- Use automatic partitioning
After installation completes, stop the VM and remove the ISO:
# On Proxmox console
qm stop 9000
qm set 9000 --ide2 none
qm set 9000 --boot order=scsi0
qm start 9000After installation completes and VM reboots, access via console (or SSH):
# Login as setup user, then:
# Use official Rocky Linux mirror (avoids out-of-sync regional mirrors)
sudo sed -i 's|^mirrorlist=|#mirrorlist=|g' /etc/yum.repos.d/rocky*.repo
sudo sed -i 's|^#baseurl=http://dl.rockylinux.org|baseurl=https://download.rockylinux.org|g' /etc/yum.repos.d/rocky*.repo
sudo dnf clean all
sudo dnf makecache
# Update all packages
sudo dnf update -y
# Install required packages
sudo dnf install -y qemu-guest-agent cloud-init vim nc
# Enable guest agent
sudo systemctl enable --now qemu-guest-agent
# Add SSH key
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "YOUR_PUBLIC_KEY_HERE" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
# Configure cloud-init for Proxmox
echo "datasource_list: [NoCloud, None]" | sudo tee /etc/cloud/cloud.cfg.d/99-proxmox.cfg
# Reboot
sudo reboot
# Check
sudo systemctl status qemu-guest-agent
cat /etc/cloud/cloud.cfg.d/99-proxmox.cfg
cat ~/.ssh/authorized_keys
# Clean up for template
sudo cloud-init clean
sudo truncate -s 0 /etc/machine-id
sudo shutdown -h now# On Proxmox console
qm stop 9000
qm set 9000 --delete ide2
qm set 9000 --ide2 local-lvm:cloudinit
qm set 9000 --ciuser setup
qm set 9000 --boot order=scsi0
qm template 9000# On Proxmox console
pveum user add terraform@pve
pveum aclmod / -user terraform@pve -role Administrator
pveum user token add terraform@pve terraform --privsep=0The infrastructure provides core services: IdM, Vault, Monitoring, PostgreSQL.
cd infrastructure/terraform
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with actual valuesterraform init
terraform plan
terraform applyImportant: After provisioning, reboot all VMs to ensure clean state.
cd ../ansible
# Copy and edit inventory (update IPs to match terraform.tfvars)
cp inventory/hosts.yml.example inventory/hosts.yml
# Copy and edit network variables
cp inventory/group_vars/all/network.yml.example inventory/group_vars/all/network.yml
# Copy and edit software versions
cp inventory/group_vars/all/versions.yml.example inventory/group_vars/all/versions.ymlNote: Grafana and Alloy install the latest version from their repos automatically.
# Create vault for secrets
ansible-vault create inventory/group_vars/all/vault.ymlAdd the following variables to vault.yml:
# IdM
vault_idm_admin_password: "YourSecurePassword123!"
vault_idm_dm_password: "YourSecurePassword123!"
vault_idm_enroller_password: "YourSecurePassword123!" # For client enrollment
# Grafana
vault_grafana_admin_password: "YourSecurePassword123!"
# Monitoring Authentication
vault_monitoring_user: "monitor"
vault_monitoring_password: "YourSecurePassword123!"
vault_loki_tenant_id: "homeserver"Note: The IdM setup creates a dedicated
enrolleraccount with the Host Enrollment role. This account is used for client enrollment instead of the admin password.
# Install required collections
ansible-galaxy collection install -r requirements.yml
# Test connectivity
ansible all -m ping --ask-vault-pass
# Run playbooks in order
ansible-playbook playbooks/00-base.yml --ask-vault-pass
ansible-playbook playbooks/01-idm.yml --ask-vault-pass
ansible-playbook playbooks/02-enrol.yml --ask-vault-pass
ansible-playbook playbooks/03-vault.yml --ask-vault-pass
ansible-playbook playbooks/04-monitoring.yml --ask-vault-pass
ansible-playbook playbooks/05-postgresql.yml --ask-vault-passBefore disabling the setup user, generate a new SSH key for IdM access (make sure to use a password for the SSH key):
# On local machine
ssh-keygen -t ed25519 -f ~/.ssh/homeserver_admin -C "homeserver-admin"
cat ~/.ssh/homeserver_admin.pub# On IdM server - upload the public key to admin account
ssh setup@idm.home.local
kinit admin
ipa user-mod admin --sshpubkey="ssh-ed25519 AAAA... homeserver-admin"# Test access
ssh admin@idm.home.local -i ~/.ssh/homeserver_adminansible-playbook playbooks/06-finalise.yml --ask-vault-passWarning: After this step, the
setupuser is disabled. Use IdM credentials for all future access.
| Order | Playbook | Description | Dependencies |
|---|---|---|---|
| 1 | 00-base.yml | Base hardening + Cockpit | None |
| 2 | 01-idm.yml | IdM server | Base |
| 3 | 02-enrol.yml | IdM enrollment | IdM server |
| 4 | 03-vault.yml | Vault | IdM enrollment |
| 5 | 04-monitoring.yml | Monitoring | IdM enrollment |
| 6 | 05-postgresql.yml | PostgreSQL | IdM enrollment |
| 7 | 06-finalise.yml | Disable setup user | All above |
After infrastructure is running, you can add application VMs.
cd applications/terraform
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars - add your VMs to the 'vms' mapExample VM configuration:
vms = {
myapp = {
vmid = 131
ip_address = "10.10.10.31/24"
memory = 2048
disk_size = 40
cpu_cores = 2
description = "My Application Server"
tags = ["web", "production"]
}
}terraform init
terraform plan
terraform applycd ../ansible
# Generate inventory from Terraform output (or create manually)
cp inventory/hosts.yml.example inventory/hosts.yml
# Copy network config (must match infrastructure values)
cp inventory/group_vars/all/network.yml.example inventory/group_vars/all/network.yml
# Create vault for secrets
ansible-vault create inventory/group_vars/all/vault.ymlAdd to vault.yml:
# Use the enroller password (created automatically by infrastructure IdM setup)
vault_idm_enroller_password: "your-enroller-password"# Run playbooks
ansible-playbook playbooks/00-base.yml --ask-vault-pass
ansible-playbook playbooks/01-enrol.yml --ask-vault-pass
ansible-playbook playbooks/02-alloy.yml --ask-vault-pass # Optional: monitoringansible-playbook playbooks/03-finalise.yml --ask-vault-passWarning: After this step, the
setupuser is disabled. Use IdM credentials for all future access.
# On Proxmox console
pveum user delete terraform@pveThis can be done via the Web UI. It's a security risk to use the main IDM admin on other servers.
| Service | URL | Default User |
|---|---|---|
| IdM | https://idm.home.local | admin |
| Vault | https://vault.home.local:8200 | (token auth) |
| Grafana | http://mon.home.local:3000 | admin |
| Prometheus | http://mon.home.local:9091 | (vault_monitoring_user) |
| Cockpit | https://<any-host>:9090 | (IdM users) |
# Install pre-commit
pip install pre-commit
# Install hooks
pre-commit install
# Run manually
pre-commit run --all-files# Terraform
terraform fmt -recursive
terraform validate
# Ansible
ansible-lint playbooks/ roles/
yamllint ansible/