Configuration Management
Using Ansible playbooks to configure what runs inside VMs, so provisioning and configuration are both reproducible instead of just one of them.
Terraform (or OpenTofu) from the previous page gets you a VM that exists - correct CPU, RAM, disk, on the right node. It has no opinion about what's installed inside that VM once it boots. That's a different problem, and a different tool: configuration management.
Provisioning vs. configuration - a clean split
- Terraform/OpenTofu answers "does this VM exist, with this shape of hardware?" It's declarative about infrastructure resources.
- Ansible answers "does this VM have Docker installed, this user created, this firewall rule set, these packages up to date?" It's declarative about the state of a running system.
Keeping the split clean matters: don't let Terraform run shell provisioners that install software (tempting, but it conflates the two tools and makes both harder to reason about), and don't have Ansible create or destroy VMs. Each tool does one job well.
Why Ansible specifically
Ansible connects over plain SSH and needs no agent installed on the target - a real advantage for a homelab where you don't want to maintain agent software on every VM and LXC container. It's YAML-based, which keeps the format consistent with the compose files you're already writing (see Docker Compose Basics). Alternatives like Puppet or Chef exist and are legitimate at company scale, but both require an agent and more infrastructure than a homelab needs.
A minimal inventory and playbook
inventory.yml:
all:
hosts:
media-vm:
ansible_host: 192.168.1.50
dns-vm:
ansible_host: 192.168.1.51
vars:
ansible_user: deploy
site.yml:
- name: Base server setup
hosts: all
become: true
tasks:
- name: Update apt cache and upgrade packages
apt:
update_cache: true
upgrade: dist
- name: Install Docker
apt:
name: docker.io
state: present
- name: Ensure deploy user is in the docker group
user:
name: deploy
groups: docker
append: true
ansible-playbook -i inventory.yml site.yml
become: trueruns tasks with sudo - Ansible connects as an unprivileged user over SSH and escalates per-task, rather than logging in as root directly.- Playbooks are idempotent by design - running
site.ymlagain on a host that's already configured makes no changes, since each task checks current state first. That's what makes it safe to re-run regularly rather than a one-time script. - Keep
inventory.ymlunder version control alongside your Terraform files - a VM's IP and role should live in one repo, not scattered across notes.
Where this fits with what you already have
If you followed Intermediate, you're likely already
managing services with Docker Compose on top of Proxmox VMs. Ansible
doesn't replace that - a common pattern is an Ansible playbook that
ensures Docker is installed and a docker-compose.yml file is present
and pulled up to date, while the compose file itself still defines the
services. Ansible handles the VM's base state; compose still handles
the containers running on it.
The honest cost
A homelab with three or four VMs that rarely change gets very little from Ansible over just SSHing in and running commands by hand when needed - you'd spend more time writing and debugging playbooks than you'd save. It starts paying for itself once you have enough hosts that "did I remember to do this on all of them" becomes a real question, or once you rebuild hosts often enough that doing setup by hand is genuinely tedious. Know which situation you're in before adopting it wholesale.
Next: Container Orchestration.