Introduction

VMware vSphere remains the dominant virtualization platform in enterprise datacenters. Managing hundreds of VMs through the vSphere Client works for small environments, but enterprise operations — provisioning dev environments, enforcing snapshot policies, maintaining golden templates, and migrating workloads — need automation. Ansible's community.vmware collection provides 150+ modules covering every vSphere API operation, from VM lifecycle to distributed switch configuration.

Setup

Install the Collection

ansible-galaxy collection install community.vmware
pip install pyvmomi requests

Connection Variables

# group_vars/all/vmware.yml
vcenter_hostname: vcenter.example.com
vcenter_username: "{{ vault_vcenter_user }}"
vcenter_password: "{{ vault_vcenter_password }}"
vcenter_validate_certs: true
vcenter_datacenter: DC-Primary
vcenter_cluster: Cluster-Prod

Module Reference

ModulePurpose
vmware_guestCreate, clone, modify, delete VMs
vmware_guest_powerstateStart, stop, restart, suspend VMs
vmware_guest_snapshotCreate, delete, revert snapshots
vmware_guest_diskAdd, remove, resize disks
vmware_guest_networkManage VM network adapters
vmware_vm_inventoryDynamic inventory from vCenter
vmware_content_deploy_templateDeploy from content library
vmware_dvswitchManage distributed virtual switches
vmware_hostESXi host management
vmware_datastore_infoDatastore capacity reporting

Pattern 1: VM Provisioning from Template

---
- name: Provision VMs from template
  hosts: localhost
  connection: local
  vars:
    vm_specs:
      - name: web-prod-01
        template: rhel9-golden
        folder: /Production/Web
        num_cpus: 4
        memory_mb: 8192
        disk_gb: 100
        network: VLAN-100-Production
        ip: 10.1.100.11
        gateway: 10.1.100.1
      - name: web-prod-02
        template: rhel9-golden
        folder: /Production/Web
        num_cpus: 4
        memory_mb: 8192
        disk_gb: 100
        network: VLAN-100-Production
        ip: 10.1.100.12
        gateway: 10.1.100.1
      - name: db-prod-01
        template: rhel9-golden
        folder: /Production/Database
        num_cpus: 8
        memory_mb: 32768
        disk_gb: 500
        network: VLAN-200-Database
        ip: 10.1.200.11
        gateway: 10.1.200.1

  tasks:
    - name: Deploy VMs from template
      community.vmware.vmware_guest:
        hostname: "{{ vcenter_hostname }}"
        username: "{{ vcenter_username }}"
        password: "{{ vcenter_password }}"
        validate_certs: "{{ vcenter_validate_certs }}"
        datacenter: "{{ vcenter_datacenter }}"
        cluster: "{{ vcenter_cluster }}"
        name: "{{ item.name }}"
        template: "{{ item.template }}"
        folder: "{{ item.folder }}"
        state: poweredon
        hardware:
          num_cpus: "{{ item.num_cpus }}"
          memory_mb: "{{ item.memory_mb }}"
        disk:
          - size_gb: "{{ item.disk_gb }}"
            type: thin
            datastore: DS-Production-SSD
        networks:
          - name: "{{ item.network }}"
            ip: "{{ item.ip }}"
            netmask: 255.255.255.0
            gateway: "{{ item.gateway }}"
            dns_servers:
              - 10.1.1.10
              - 10.1.1.11
        customization:
          hostname: "{{ item.name }}"
          domain: example.com
          dns_servers:
            - 10.1.1.10
            - 10.1.1.11
        wait_for_customization: true
        wait_for_customization_timeout: 600
        wait_for_ip_address: true
      loop: "{{ vm_specs }}"
      register: deployed_vms

    - name: Display VM details
      ansible.builtin.debug:
        msg: "{{ item.item.name }}: {{ item.instance.ipv4 }}"
      loop: "{{ deployed_vms.results }}"
      loop_control:
        label: "{{ item.item.name }}"

Pattern 2: Golden Template Pipeline

---
- name: Build golden VM template
  hosts: localhost
  connection: local
  vars:
    template_name: "rhel9-golden-{{ lookup('pipe', 'date +%Y%m%d') }}"
    base_vm: rhel9-template-builder

  tasks:
    - name: Clone base VM for building
      community.vmware.vmware_guest:
        hostname: "{{ vcenter_hostname }}"
        username: "{{ vcenter_username }}"
        password: "{{ vcenter_password }}"
        validate_certs: "{{ vcenter_validate_certs }}"
        datacenter: "{{ vcenter_datacenter }}"
        cluster: "{{ vcenter_cluster }}"
        name: "{{ base_vm }}"
        template: rhel9-base
        folder: /Templates/Build
        state: poweredon
        wait_for_ip_address: true
      register: build_vm

    - name: Wait for SSH to become available
      ansible.builtin.wait_for:
        host: "{{ build_vm.instance.ipv4 }}"
        port: 22
        delay: 30
        timeout: 300
      delegate_to: localhost

    - name: Add build VM to inventory
      ansible.builtin.add_host:
        name: "{{ build_vm.instance.ipv4 }}"
        groups: template_build
        ansible_user: root

- name: Configure golden template
  hosts: template_build
  become: true
  tasks:
    - name: Update all packages
      ansible.builtin.dnf:
        name: "*"
        state: latest

    - name: Install standard packages
      ansible.builtin.dnf:
        name:
          - vim
          - tmux
          - htop
          - chrony
          - open-vm-tools
          - python3
          - rsyslog
        state: present

    - name: Apply CIS Level 1 baseline
      ansible.builtin.include_role:
        name: cis-rhel9-baseline

    - name: Configure monitoring agent
      ansible.builtin.include_role:
        name: monitoring-agent

    - name: Clean up for templating
      ansible.builtin.shell: |
        dnf clean all
        rm -rf /var/cache/dnf
        rm -f /etc/ssh/ssh_host_*
        truncate -s 0 /etc/machine-id
        rm -f /var/log/*.log
        rm -f /root/.bash_history
        history -c

- name: Convert to template
  hosts: localhost
  connection: local
  tasks:
    - name: Shutdown build VM
      community.vmware.vmware_guest_powerstate:
        hostname: "{{ vcenter_hostname }}"
        username: "{{ vcenter_username }}"
        password: "{{ vcenter_password }}"
        validate_certs: "{{ vcenter_validate_certs }}"
        name: "{{ base_vm }}"
        state: shutdown-guest
        state_change_timeout: 120

    - name: Convert VM to template
      community.vmware.vmware_guest:
        hostname: "{{ vcenter_hostname }}"
        username: "{{ vcenter_username }}"
        password: "{{ vcenter_password }}"
        validate_certs: "{{ vcenter_validate_certs }}"
        name: "{{ base_vm }}"
        is_template: true

    - name: Rename to dated template name
      community.vmware.vmware_guest:
        hostname: "{{ vcenter_hostname }}"
        username: "{{ vcenter_username }}"
        password: "{{ vcenter_password }}"
        validate_certs: "{{ vcenter_validate_certs }}"
        name: "{{ base_vm }}"
        new_name: "{{ template_name }}"

Pattern 3: Snapshot Management

---
- name: Enforce snapshot policy across all VMs
  hosts: localhost
  connection: local
  vars:
    max_snapshot_age_days: 7
    max_snapshots_per_vm: 3

  tasks:
    - name: Get all VMs with snapshots
      community.vmware.vmware_guest_snapshot_info:
        hostname: "{{ vcenter_hostname }}"
        username: "{{ vcenter_username }}"
        password: "{{ vcenter_password }}"
        validate_certs: "{{ vcenter_validate_certs }}"
        datacenter: "{{ vcenter_datacenter }}"
        folder: "/{{ vcenter_datacenter }}/vm"
      register: all_snapshots

    - name: Remove snapshots older than policy allows
      community.vmware.vmware_guest_snapshot:
        hostname: "{{ vcenter_hostname }}"
        username: "{{ vcenter_username }}"
        password: "{{ vcenter_password }}"
        validate_certs: "{{ vcenter_validate_certs }}"
        datacenter: "{{ vcenter_datacenter }}"
        name: "{{ item.0.guest_name }}"
        snapshot_name: "{{ item.1.name }}"
        state: absent
      loop: "{{ all_snapshots.guest_snapshots | subelements('snapshots', skip_missing=True) }}"
      when:
        - item.1.creation_time is defined
        - (now() - item.1.creation_time | to_datetime).days > max_snapshot_age_days
      loop_control:
        label: "{{ item.0.guest_name }}: {{ item.1.name }}"

    - name: Create pre-maintenance snapshot
      community.vmware.vmware_guest_snapshot:
        hostname: "{{ vcenter_hostname }}"
        username: "{{ vcenter_username }}"
        password: "{{ vcenter_password }}"
        validate_certs: "{{ vcenter_validate_certs }}"
        datacenter: "{{ vcenter_datacenter }}"
        name: "{{ item }}"
        snapshot_name: "pre-maintenance-{{ ansible_date_time.date }}"
        description: "Pre-maintenance snapshot by Ansible"
        memory_dump: false
        quiesce: true
        state: present
      loop: "{{ groups['maintenance_targets'] }}"

Pattern 4: Capacity Reporting

---
- name: Generate datacenter capacity report
  hosts: localhost
  connection: local
  tasks:
    - name: Get datastore information
      community.vmware.vmware_datastore_info:
        hostname: "{{ vcenter_hostname }}"
        username: "{{ vcenter_username }}"
        password: "{{ vcenter_password }}"
        validate_certs: "{{ vcenter_validate_certs }}"
        datacenter: "{{ vcenter_datacenter }}"
      register: datastore_info

    - name: Get cluster resource usage
      community.vmware.vmware_cluster_info:
        hostname: "{{ vcenter_hostname }}"
        username: "{{ vcenter_username }}"
        password: "{{ vcenter_password }}"
        validate_certs: "{{ vcenter_validate_certs }}"
        datacenter: "{{ vcenter_datacenter }}"
        cluster_name: "{{ vcenter_cluster }}"
      register: cluster_info

    - name: Display datastore capacity
      ansible.builtin.debug:
        msg: >
          {{ item.name }}:
          Total={{ (item.capacity / 1073741824) | round(1) }}GB,
          Free={{ (item.freeSpace / 1073741824) | round(1) }}GB,
          Used={{ ((item.capacity - item.freeSpace) / item.capacity * 100) | round(1) }}%
      loop: "{{ datastore_info.datastores }}"
      loop_control:
        label: "{{ item.name }}"

    - name: Alert on low-capacity datastores
      ansible.builtin.mail:
        host: smtp.internal
        to: vmware-ops@example.com
        subject: "⚠️ Datastore {{ item.name }} at {{ ((item.capacity - item.freeSpace) / item.capacity * 100) | round(1) }}% capacity"
        body: "Datastore {{ item.name }} has only {{ (item.freeSpace / 1073741824) | round(1) }}GB free."
      loop: "{{ datastore_info.datastores }}"
      when: (item.freeSpace / item.capacity) < 0.15
      loop_control:
        label: "{{ item.name }}"

Pattern 5: Dynamic Inventory from vCenter

# inventory/vmware.yml
plugin: community.vmware.vmware_vm_inventory
hostname: vcenter.example.com
username: "ansible-svc@vsphere.local"
password: "{{ lookup('env', 'VMWARE_PASSWORD') }}"
validate_certs: true
with_nested_properties: true
hostnames:
  - config.name
keyed_groups:
  - key: config.guestId
    prefix: os
  - key: guest.net[0].network
    prefix: network
  - key: "tags.get('environment', 'unknown')"
    prefix: env
filters:
  - runtime.powerState == "poweredOn"
  - config.template == false
compose:
  ansible_host: guest.ipAddress
  ansible_user: "'ansible'"
# Test the dynamic inventory
ansible-inventory -i inventory/vmware.yml --graph

Troubleshooting

SSL Certificate Errors

# Use a CA bundle or disable verification (dev only!)
vcenter_validate_certs: true
# Export the vCenter CA cert:
# openssl s_client -connect vcenter.example.com:443 < /dev/null 2>/dev/null | openssl x509 > vcenter-ca.pem
# Then set: REQUESTS_CA_BUNDLE=/path/to/vcenter-ca.pem

VM Customization Timeout

# Increase timeout for slow guest customization
wait_for_customization: true
wait_for_customization_timeout: 900  # 15 minutes

Permission Errors

Ansible needs a vCenter service account with these minimum privileges:

  • Virtual Machine: All privileges
  • Datastore: Allocate space, Browse
  • Network: Assign network
  • Resource: Assign VM to resource pool
  • Content Library: Download files (if using templates)

Conclusion

Ansible's community.vmware collection covers the full VM lifecycle: provision from templates with vmware_guest, enforce snapshot policies, build golden templates automatically, generate capacity reports, and use vCenter as a dynamic inventory source. For enterprise environments, combine with AAP for self-service VM provisioning (surveys for CPU/memory/network), scheduled snapshot cleanup, and monthly template rebuilds. The key is treating VMs like cattle, not pets — automate creation, configuration, and decommissioning end to end.