Automating vSphere Power Management driven by Ansible and SemaphoreUI
In this post I’ll walk through how I use vSphere Power Management driven by Ansible and SemaphoreUI to automatically reduce ESXi host electricity consumption — saving real money on my Octopus Agile tariff by toggling hosts between Low Power and Balanced policies.
Table of Contents
Introudction
One of the larger costs of running my homelab is the electricity. I have previously talked about my supplier Octopus Energy and how they operate an innovative tariff called Agile. With this tariff the price I pay changes every 30 minutes based on the demand on the grid. This is greatly influenced by a number of factors including the external temperature and time of day.
The supply side also has a significant impact to the pricing. A sunny and windy day can lead to an excess of electricity in the grid where the pricing can occasionally go negative. Below is the pricing I will be paying for today (15th April) as you can see the afternoon rate drops almost to zero and then from 1600-1900hrs it is more expensive as that is the largest demand on the grid. This profile is typical especially the 1600-1900hrs peak.

Octopus also published a graph on the carbon intensity of the grid

To try and optimise the costs for my lab and also improve my green credentials I wanted to try and reduce the usage during those peak hours. I wanted to see if vSphere Power Management driven by Ansible could help me change the vSphere power management profile from balanced to low on a schedule. Initially this will be a fixed schedule going to low at 1600 everyday and back to balanced at 1900hrs. I will probably enable the low power mode overnight as well.
As I have been using Ansible a lot lately therefore I decided to create two playbooks that I could use to achieve this and then use SemaphoreUI to schedule and run them for me.
How vSphere Power Management driven by Ansible Works
vSphere’s default power policy is “Balanced,” which leverages P-states aggressively that govern frequency and voltage scaling. while preserving turbo boost and fast ramp-up behaviour. With C-states enabled which govern idle sleep states, the CPU can opportunistically run at higher frequencies during bursts, processing workloads more quickly.
Low Power mode tells vSphere to choose efficiency over speed. The CPU spends more time in deep C-states when idle, sits at lower P-states when active, and is far less willing to ramp up frequency or engage turbo boost. The result is reduced power draw and heat output, at the cost of slower response to sudden workload spikes.
On my HP Z840 which is my box that stays on 24×7 and contains 2x Xeon E5-2673v3 I am expecting an approximate 30w difference between the two modes. (I will likely try and measure this in the future.) The average price I’ve paid over the 1600-1900hrs time period for the last year is 33.10p/kWh. This gives me an approximate saving of £11.00 a year. Nothing huge, but I often will have 3 more additional vSphere servers on at the same time. I will use this setting globally. It’s nice to save some money and be a bit greener at the same time.
Manually changing these settings across multiple hosts through the vSphere client is tedious and who wants to do that. By automating you get a repeatable, process that runs in seconds regardless of how many hosts you manage.
The Playbooks
This needed a lot of AI help to get right due to the way that Semaphore executes the playbooks. Hence the bootstrap wrappers described below.
The solution uses four playbooks: two bootstrap wrappers that handle dependency installation, and two core playbooks that apply the power policy. The bootstrap wrappers are needed because community.vmware does not officially support Ansible 2.18, so the collection must be installed and patched at runtime. The wrappers also install the required PyVmomi Python library.
Both core playbooks use the community.vmware.vmware_host_powermgmt_policy module with the cluster_name parameter, which applies the policy to all hosts in the specified cluster. They also support multiple clusters by accepting a list via cluster_names.
These can be found in my Infrastructure as Code Github Repo
run_set_power_low.yml (Bootstrap Wrapper)
This is the playbook you point Semaphore at. It installs PyVmomi, installs the community.vmware collection to a temporary path, patches the Ansible version constraint, then launches the core playbook as a subprocess with the correct collection path configured:
---
- name: Bootstrap and run power management playbook
hosts: localhost
gather_facts: no
tasks:
- name: Install PyVmomi Python library
ansible.builtin.pip:
name: PyVmomi
state: present
- name: Install collection and patch compatibility
ansible.builtin.shell: |
ansible-galaxy collection install community.vmware --force -p /tmp/vmware_collections 2>&1
MANIFEST="/tmp/vmware_collections/ansible_collections/community/vmware/MANIFEST.json"
if [ -f "$MANIFEST" ]; then
sed -i 's/"requires_ansible": ".*"/"requires_ansible": ">=2.15.0"/' "$MANIFEST"
fi
META="/tmp/vmware_collections/ansible_collections/community/vmware/meta/runtime.yml"
if [ -f "$META" ]; then
sed -i 's/requires_ansible: .*/requires_ansible: ">=2.15.0"/' "$META"
fi
- name: Write extra vars file
ansible.builtin.copy:
content: |
vcenter_host: "{{ vcenter_host }}"
vcenter_user: "{{ vcenter_user }}"
vcenter_pass: "{{ vcenter_pass }}"
cluster_names: {{ cluster_names | to_json }}
dest: /tmp/.powermgmt_vars.yml
mode: "0600"
no_log: true
- name: Run Set Power Low playbook
ansible.builtin.shell: |
cat > /tmp/ansible_vmware.cfg << 'EOF'
[defaults]
collections_path = /tmp/vmware_collections
collections_scan_sys_path = false
EOF
export ANSIBLE_CONFIG=/tmp/ansible_vmware.cfg
ansible-playbook {{ playbook_dir }}/set_power_low.yml -e @/tmp/.powermgmt_vars.yml 2>&1
register: playbook_result
- name: Clean up temp files
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- /tmp/.powermgmt_vars.yml
- /tmp/ansible_vmware.cfg
- name: Show playbook output
ansible.builtin.debug:
msg: "{{ playbook_result.stdout_lines }}"set_power_low.yml
This is the core playbook that sets all hosts in the specified clusters to Low Power mode:
---
- name: Set vSphere host power management policy to Low Power
hosts: localhost
gather_facts: no
collections:
- community.vmware
vars:
vcenter_hostname: "{{ vcenter_host }}"
vcenter_username: "{{ vcenter_user }}"
vcenter_password: "{{ vcenter_pass }}"
validate_certs: false
tasks:
- name: Set power management policy to Low Power on all hosts in cluster
community.vmware.vmware_host_powermgmt_policy:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
validate_certs: "{{ validate_certs }}"
cluster_name: "{{ item }}"
policy: low-power
loop: "{{ cluster_names if cluster_names is iterable and cluster_names is not string else [cluster_names] }}"
register: power_results
- name: Display results
ansible.builtin.debug:
msg: "{{ item.item }}: {{ 'changed' if item.changed else 'ok' }}"
loop: "{{ power_results.results }}"set_power_balanced.yml
This playbook switches all hosts in the specified clusters back to Balanced, restoring the default trade-off between performance and power consumption:
---
- name: Set vSphere host power management policy to Balanced
hosts: localhost
gather_facts: no
collections:
- community.vmware
vars:
vcenter_hostname: "{{ vcenter_host }}"
vcenter_username: "{{ vcenter_user }}"
vcenter_password: "{{ vcenter_pass }}"
validate_certs: false
tasks:
- name: Set power management policy to Balanced on all hosts in cluster
community.vmware.vmware_host_powermgmt_policy:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
validate_certs: "{{ validate_certs }}"
cluster_name: "{{ item }}"
policy: balanced
loop: "{{ cluster_names if cluster_names is iterable and cluster_names is not string else [cluster_names] }}"
register: power_results
- name: Display results
ansible.builtin.debug:
msg: "{{ item.item }}: {{ 'changed' if item.changed else 'ok' }}"
loop: "{{ power_results.results }}"Variables
Each playbook expects the following variables to be passed in via Semaphore:
- vcenter_host — the FQDN or IP of your vCenter Server
- vcenter_user — a vCenter user with host configuration privileges
- vcenter_pass — the password for the above user (mark as secret in Semaphore)
- cluster_names — the vSphere cluster(s) to target, either a single name (e.g.
"GPU") or a list (e.g.["GPU", "Compute"])
These can be set in the Semaphore task templat as variables. Semaphore injects them at runtime, so you never need to store sensitive vCenter credentials alongside your playbook code.
Running the Playbooks
From Semaphore, point your task template at run_set_power_low.yml or run_set_power_balanced.yml (the bootstrap wrappers). These handle installing the community.vmware collection and PyVmomi automatically, then launch the core playbook. If you prefer the command line:
ansible-playbook run_set_power_low.yml -e "vcenter_host=vcsa.local [email protected] vcenter_pass=YourPassword cluster_names=MyCluster"If everything is working as expected you should see the task pop like

Scheduling
For now I am just using fixed time based schedules in SemaphoreUI setting low power at 1600 and balanced at 1900. The next iteration will be to integrate with Home Assistant as that can already see Octopus Agile pricing via the Octopus API. I would like to set a cost threshold where if the electricity price is above this then low power mode is triggered. The likelihood is this will also take care of the 1600-1900 window.
Prerequisites
Before running these playbooks, make sure you have the following in place:
- Ansible 2.15 or later installed
- The bootstrap wrapper playbooks handle installing the
community.vmwarecollection andPyVmomiautomatically — no manual setup needed - A vCenter Server user account with permission to change host power management settings
- Network connectivity from the Ansible control node (or Semaphore runner) to the vCenter Server






