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: true runs 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.yml again 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.yml under 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.