Build a Proxmox VE Health Monitor CLI

Build a Python CLI tool that monitors your Proxmox server with color-coded alerts.

Introduction

30 Second Summary

Every Proxmox VE server owner knows the routine. Log into the web UI, click through your nodes, and squint at CPU and memory numbers to figure out if anything needs attention.

In this project, you will build a Python CLI tool that connects to your Proxmox server's REST API and prints a color-coded health report in your terminal with a single command.

What You'll Build

Run one command on your Mac, and your terminal fills with a formatted report showing your node's vitals, every VM and container's status, and red warnings for anything that needs attention.

By the end of this project, you'll have:

  • A Python tool that pulls live health data from your Proxmox server through a secure, read-only API token you create yourself.
  • A color-coded node health report where CPU, memory, and disk usage glow green, yellow, or red so problems stand out at a glance.
  • A guest table showing every VM and container on your server with its power state and resource usage, plus automatic warnings when anything crosses 80% usage.
  • Secret Mission: Add a watch mode that auto-refreshes the report every few seconds, turning your terminal into a live monitoring dashboard.

Are there any prerequisites?

You need a running Proxmox VE server that your Mac can reach over the network. No cloud account or paid service is required.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters to your daily workflow.

Set Up Python and Connect to Proxmox

Your Proxmox VE server exposes a REST API on port 8006. Instead of clicking through the web UI every morning to check on things, you can talk to that API programmatically from your Mac.

In this step, you will install Python and the proxmoxer library, create a secure read-only API token on your Proxmox server, and write your first script that connects and pulls live data.

In this step, get ready to:
  • Install Python 3.13 and set up a virtual environment with dependencies.
  • Create a read-only API token on your Proxmox server.
  • Write and run a connection test script.
Install Python 3.13 and create your project

Python is the language you will use to talk to the Proxmox API. The official python.org installer is the simplest way to get it on your Mac.

  • Download the Python 3.13 macOS installer from the official downloads page.
  • Run the installer and follow the prompts, accepting the defaults.

What does the installer do?

The installer places a Python framework in /Library/Frameworks/Python.framework and creates a symlink in /usr/local/bin/. It also adds that location to your shell path so you can run python3 from Terminal.

Now verify Python is installed and accessible from your terminal.

  • Press Cmd+Space to open Spotlight.
  • Type Terminal and press Enter to open it.
  • Check your Python version by running this command:
python3 --version

✔️ I see version 3.13 or higher

Python is ready to go. Continue to the next instruction below.

ⓧ I see an older version

macOS may ship with an older Python that shadows the version you just installed. You can verify the new install is available by running:

python3.13 --version

If that shows 3.13, use python3.13 in place of python3 for all commands in this project.

If python3.13 is also not found, re-download and run the installer from the Python 3.13 downloads page.

ⓧ Command not found

Python is not installed yet. Download and run the macOS installer from the Python 3.13 downloads page.

After installation completes, close and reopen Terminal, then run python3 --version again.

Next, create your project folder. Every terminal instruction that creates a new folder starts from your Desktop so you can find it easily.

  • Move to your Desktop and create the project folder by running these commands:
cd ~/Desktop
mkdir pve-monitor
cd pve-monitor
  • Confirm you are inside the new folder by running:
pwd

You should see a path ending in /Desktop/pve-monitor.

A virtual environment keeps this project's packages separate from your system Python. This prevents version conflicts with other projects.

  • Create and activate a virtual environment by running these commands:
python3 -m venv venv
source venv/bin/activate

Your terminal prompt now shows (venv) at the beginning. That confirms the virtual environment is active.

What is a virtual environment?

A virtual environment is an isolated Python installation inside your project folder. When activated, pip install puts packages into venv/ instead of your system-wide Python. This keeps your project self-contained.

  • Install the project dependencies by running:
pip install proxmoxer==2.3.0 requests

What are these packages?

The proxmoxer library is a Python wrapper for the Proxmox VE API. It handles authentication and gives you a clean way to call API endpoints without writing raw HTTP requests.

The requests library is the HTTP backend that proxmoxer uses under the hood to make HTTPS calls to your server.

  • Verify the library is importable by running:
python3 -c "from proxmoxer import ProxmoxAPI; print('proxmoxer ready')"

You should see proxmoxer ready printed in your terminal.

Seeing an import error?

  • Make sure your virtual environment is active. You should see (venv) in your prompt. If not, run source venv/bin/activate again.
  • If pip install failed earlier, re-run it and check for error messages about network connectivity.

Still stuck? Help me troubleshoot my proxmoxer installation.

Create a Proxmox API token

Your Python script needs credentials to authenticate with the Proxmox API. API token authentication is the right choice here because tokens are stateless and do not expire like session tickets.

You will create a dedicated user with read-only access so the monitoring script can never accidentally change anything on your server.

Why a dedicated user instead of root?

Using root@pam for a monitoring script gives it full administrative access. A dedicated user with the PVEAuditor role can only read status data. If the token is ever leaked, the damage is limited to someone seeing your server stats.

  • Open your Proxmox VE web UI in a browser. The URL is https:// followed by your Proxmox server's IP address, then :8006.
  • Log in with your root account.

First, create the monitoring user.

  • In the left sidebar, click Datacenter.
  • Expand Permissions and click Users.
  • Click Add.
  • In the User name field, enter monitor.
  • Set the Realm to Proxmox VE authentication server (pve).
  • Set a password for the user.
  • Click Add to create the user.

You should see monitor@pve appear in the users list.

Next, create an API token for this user.

  • Under Permissions, click API Tokens.
  • Click Add.
  • Set User to monitor@pve.
  • In the Token ID field, enter monitoring.
  • Make sure the Privilege Separation checkbox is checked (it is by default).
  • Click Add.

Copy the token secret now

A dialog appears showing your token ID and secret value. Copy both of these immediately and save them somewhere safe (such as a notes app). The secret is displayed only once. If you close this dialog without copying it, you will need to delete the token and create a new one.

  • Record your token ID: your token ID.
  • Record your token secret value (keep this private).

Finally, grant the token read-only access to your entire server.

  • Click Permissions (the parent item, not a sub-item).
  • Click Add, then select API Token Permission.
  • Set Path to / (the root path, giving access to all resources).
  • Set the API Token to monitor@pve!monitoring.
  • Set Role to PVEAuditor.
  • Make sure Propagate is checked.
  • Click Add.

What is PVEAuditor?

PVEAuditor is a built-in Proxmox role that grants read-only access. It allows your script to view node status, VM and container info, and storage data. It cannot start, stop, modify, or delete anything.

You should now see a permission entry in the table showing monitor@pve!monitoring with the PVEAuditor role on path /.

Write the connection script

You have Python ready and an API token created. Now it is time to write the script that connects to your Proxmox server and pulls live data.

  • Switch back to your code editor.
  • Open the pve-monitor folder you created on your Desktop.
  • Create a new file called pve_monitor.py.

Start by adding the imports, configuration constants, and the connection function. Copy the following code into pve_monitor.py:

import sys
from proxmoxer import ProxmoxAPI

# --- Configuration ---
PVE_HOST = "your-proxmox-ip-here"
PVE_USER = "monitor@pve"
PVE_TOKEN_NAME = "monitoring"
PVE_TOKEN_VALUE = "your-api-token-value-here"


def connect():
    return ProxmoxAPI(
        PVE_HOST,
        user=PVE_USER,
        token_name=PVE_TOKEN_NAME,
        token_value=PVE_TOKEN_VALUE,
        verify_ssl=False,
    )

What does this code do?

  • The PVE_HOST, PVE_USER, PVE_TOKEN_NAME, and PVE_TOKEN_VALUE constants store the credentials your script needs to authenticate with the Proxmox API.
  • The connect() function creates a ProxmoxAPI client using API token authentication. The token_name and token_value parameters tell proxmoxer to use the stateless token header instead of a session ticket.
  • The verify_ssl=False flag tells proxmoxer to accept the self-signed certificate that Proxmox VE ships with by default.
  • Replace your-proxmox-ip-here with your Proxmox server's IP address (for example, 192.168.1.100).
  • Replace your-api-token-value-here with the token secret value you copied earlier.

Now add the main() function that calls connect() and prints the raw node data. Add this code below the connect() function:

def main():
    print("Proxmox VE Health Monitor")
    print(f"Connecting to {PVE_HOST}...")
    try:
        proxmox = connect()
    except Exception as e:
        print(f"Connection failed: {e}")
        sys.exit(1)
    print(proxmox.nodes.get())


if __name__ == "__main__":
    main()

What does this code do?

  • The main() function calls connect() to establish a connection to your Proxmox server. If the connection fails, it prints the error and exits.
  • The proxmox.nodes.get() call hits the /nodes API endpoint and returns a list of all nodes on your server. This is the simplest possible test to confirm your token works.
  • The if __name__ == "__main__" guard ensures main() only runs when you execute the file directly, not when it is imported.
  • Save pve_monitor.py.

Before you run the script, what do you think will happen? Will you see your node's name, an error, or something else?

  • Switch to your Terminal (make sure you still see (venv) in the prompt) and run the script:
python3 pve_monitor.py

You should see output like this (your node name and values will differ):

Proxmox VE Health Monitor
Connecting to 192.168.1.100...
[{'node': 'pve', 'status': 'online', 'cpu': 0.0234, ...}]

The raw list of dictionaries confirms your script successfully authenticated with the API token and pulled live data from your server.

Seeing a connection error?

  • Double-check that PVE_HOST in your script matches your Proxmox server's IP address exactly. You can verify the IP by checking the URL in your browser where you access the Proxmox web UI.
  • Make sure PVE_TOKEN_VALUE is the full secret string you copied when creating the token. If you lost it, go back to API Tokens in the Proxmox web UI, delete the old token, create a new one, and copy the new secret.
  • Confirm your Mac can reach the server by running ping followed by your server's IP in Terminal.

Still stuck? Help me debug my Proxmox API connection error.

✔️ Awesome, I've got everything!

Great. Double check you have saved pve_monitor.py.

ⓧ I'd like to double check the full code

Your pve_monitor.py should contain two chunks. Scroll up and confirm each block is present in order:

  • The imports (sys and ProxmoxAPI), the four configuration constants, and the connect() function.
  • The main() function and the if __name__ == "__main__" guard at the bottom.

Make sure PVE_HOST and PVE_TOKEN_VALUE contain your actual values, not the placeholder strings.

Your Python environment is set up, your API token is secure, and your script is talking to Proxmox. Next up, you will parse that raw data into a readable health report showing CPU, memory, and disk usage at a glance.

Pull Node Health Data

Your script can talk to Proxmox, but right now it just dumps a raw list of node dictionaries. The first thing you check every morning is whether the server itself is healthy. That means CPU, memory, and disk usage.

Right now, getting those numbers requires logging into the Proxmox VE web UI and clicking through dashboards. In this step, you will pull that data straight into your terminal with color-coded output so problems stand out at a glance.

In this step, get ready to:
  • Add color-coding constants and a helper function for green, yellow, and red output.
  • Fetch live CPU, memory, and disk data from the Proxmox API.
  • Display a formatted node health report in your terminal.
Add color-coding constants and a helper function

A wall of plain numbers is hard to scan. You want 45% CPU to look different from 90% CPU so you can spot trouble instantly.

ANSI escape codes let you print colored text in the terminal. Combined with threshold constants, you can make any value appear green, yellow, or red depending on how close it is to a warning level.

  • In pve_monitor.py, find the blank line between the configuration block and the def connect(): function.
  • Add the following threshold constants, ANSI color constants, and color_value helper function in that gap:
# --- Thresholds ---
CPU_WARN = 80
MEM_WARN = 80
DISK_WARN = 80

# --- ANSI Colors ---
RED = "\033[91m"
YELLOW = "\033[93m"
GREEN = "\033[92m"
BOLD = "\033[1m"
RESET = "\033[0m"

What do these constants do?

  • CPU_WARN, MEM_WARN, and DISK_WARN set the percentage threshold where a value turns red.
  • The ANSI codes are special character sequences that tell your terminal to switch text color. RESET switches the color back to normal after each value.
  • Now add the color_value helper function below the ANSI color constants, before def connect()::
def color_value(value, threshold):
    # Return a color-coded percentage string
    if value >= threshold:
        return f"{RED}{value}%{RESET}"
    elif value >= threshold * 0.75:
        return f"{YELLOW}{value}%{RESET}"
    return f"{GREEN}{value}%{RESET}"

How does the color logic work?

  • A value at or above the threshold (80%) turns red.
  • A value at or above 75% of the threshold (60%) turns yellow as a caution.
  • Anything below 60% stays green, meaning healthy.
  • Save pve_monitor.py.
  • Test the color helper by running this command in your terminal:
python3 -c "from pve_monitor import color_value; print(color_value(45, 80)); print(color_value(65, 80)); print(color_value(90, 80))"

You should see three percentage values printed in different colors: green for 45%, yellow for 65%, and red for 90%.

Seeing plain text with no colors?

  • Make sure you are running the command in a terminal that supports ANSI colors. The default macOS Terminal app works fine.
  • Check that the escape codes in your file use a backslash followed by 033 and not a copy-paste artifact.
  • If you see an ImportError, make sure your virtual environment is activated and you are running the command from your project folder.

Still stuck? Help me debug why my color_value function is not printing colored output in the terminal.

Fetch and display node health data

The Proxmox VE REST API exposes a status endpoint for each node that returns live CPU, memory, and disk metrics. Your proxmoxer library maps that endpoint to a clean Python call.

  • In pve_monitor.py, add the get_node_status function below connect():
def get_node_status(proxmox, node_name):
    # Fetch live status from the Proxmox API
    status = proxmox.nodes(node_name).status.get()
    cpu_percent = round(status["cpu"] * 100, 1)
    mem_used = status["memory"]["used"]
    mem_total = status["memory"]["total"]
    mem_percent = round(mem_used / mem_total * 100, 1)
    rootfs_used = status["rootfs"]["used"]
    rootfs_total = status["rootfs"]["total"]
    disk_percent = round(rootfs_used / rootfs_total * 100, 1)
    uptime_hours = round(status["uptime"] / 3600, 1)

    return {
        "name": node_name,
        "cpu_percent": cpu_percent,
        "mem_percent": mem_percent,
        "mem_used_gb": round(mem_used / (1024**3), 1),
        "mem_total_gb": round(mem_total / (1024**3), 1),
        "disk_percent": disk_percent,
        "disk_used_gb": round(rootfs_used / (1024**3), 1),
        "disk_total_gb": round(rootfs_total / (1024**3), 1),
        "uptime_hours": uptime_hours,
    }

What does this function do?

The Proxmox API returns CPU as a decimal between 0 and 1, and memory and disk values in raw bytes. This function converts everything into human-readable percentages and gigabytes.

  • proxmox.nodes(node_name).status.get() calls the /nodes/{node}/status API endpoint.
  • status["cpu"] is a float like 0.03, so multiplying by 100 gives you a percentage.
  • Memory and disk values come in bytes, so dividing by 1024**3 converts them to GB.
  • Save pve_monitor.py. Nothing calls this function yet, so there is no visible change. The next chunk connects it to your report output.

Now you need two things: a function to print the data in a readable format, and an updated main() that calls both functions.

  • Add the print_node_report function below get_node_status:
def print_node_report(node_info):
    # Display a formatted health section for one node
    print(f"\n{BOLD}=== Node: {node_info['name']} ==={RESET}")
    print(f"  Uptime:  {node_info['uptime_hours']} hours")
    print(f"  CPU:     {color_value(node_info['cpu_percent'], CPU_WARN)}")
    print(
        f"  Memory:  {color_value(node_info['mem_percent'], MEM_WARN)}"
        f"  ({node_info['mem_used_gb']} / {node_info['mem_total_gb']} GB)"
    )
    print(
        f"  Disk:    {color_value(node_info['disk_percent'], DISK_WARN)}"
        f"  ({node_info['disk_used_gb']} / {node_info['disk_total_gb']} GB)"
    )

What does this function do?

  • It prints a bold header with the node name, then each metric on its own line.
  • CPU shows just the color-coded percentage. Memory and disk show the percentage plus the actual GB used out of the total.
  • Each percentage passes through color_value so it appears green, yellow, or red based on how close it is to the threshold.

Finally, you need to update main() to loop through nodes and call these new functions instead of printing raw data.

  • In pve_monitor.py, scroll down to the existing main() function.
  • Find this code:
def main():
    print("Proxmox VE Health Monitor")
    print(f"Connecting to {PVE_HOST}...")
    try:
        proxmox = connect()
    except Exception as e:
        print(f"Connection failed: {e}")
        sys.exit(1)
    print(proxmox.nodes.get())
  • Replace the entire main() function with this updated version:
def main():
    # Print a bold title and connect to Proxmox
    print(f"{BOLD}Proxmox VE Health Monitor{RESET}")
    print(f"Connecting to {PVE_HOST}...")

    try:
        proxmox = connect()
    except Exception as e:
        print(f"{RED}Connection failed: {e}{RESET}")
        sys.exit(1)

    # Loop through each node and print its health report
    nodes = proxmox.nodes.get()
    for node in nodes:
        node_name = node["node"]
        node_info = get_node_status(proxmox, node_name)
        print_node_report(node_info)

What changed in main?

  • The title and error message now use the ANSI color constants (BOLD, RED, RESET) for styled terminal output.
  • Instead of printing the raw list from proxmox.nodes.get(), the function now loops through each node, fetches its status, and prints a formatted report.
  • Save pve_monitor.py.

Before you run the script, what do you expect to see in the output?

  • Run the health monitor by running this command:
python3 pve_monitor.py

You should see your Proxmox node name in a bold header, followed by uptime in hours, and CPU, memory, and disk usage as color-coded percentages with GB values.

Seeing a connection error or traceback?

  • If you see Connection failed, double-check that PVE_HOST in your configuration matches your Proxmox server's IP address and that the server is reachable from your Mac.
  • If you see a KeyError for memory or rootfs, your API response may use a slightly different structure. Try adding print(status) inside get_node_status to inspect the raw response and adjust your keys to match.
  • If you see a NameError for BOLD or color_value, make sure the constants and function are defined above the main() function in the file.

Still stuck? Help me debug why my Proxmox health monitor is not printing formatted node data.

✔️ Awesome, I've got everything!

Great. Double check you have saved pve_monitor.py and your output shows colored CPU, memory, and disk percentages.

ⓧ I'd like to double check the full code

Scroll through pve_monitor.py and confirm each section appears in this order:

  • Imports (sys and ProxmoxAPI).
  • Configuration constants (PVE_HOST, PVE_USER, PVE_TOKEN_NAME, PVE_TOKEN_VALUE).
  • Threshold constants (CPU_WARN, MEM_WARN, DISK_WARN all set to 80).
  • ANSI color constants (RED, YELLOW, GREEN, BOLD, RESET).
  • connect() function.
  • color_value(value, threshold) function.
  • get_node_status(proxmox, node_name) function.
  • print_node_report(node_info) function.
  • main() function that loops through nodes and calls get_node_status then print_node_report.
  • if __name__ == "__main__": main() at the bottom.

Your terminal now shows a clean, color-coded snapshot of your Proxmox server's health. Next up, you will pull a list of every VM and container running on the node so you can see what is actually using those resources.

List All VMs and Containers

Your script can tell you whether the server itself is healthy. But knowing the node's CPU and memory are fine is only half the picture.

You also need to see what is actually running on the node. In this step, you will pull every VM and container from the Proxmox VE API and display them in a clean table sorted by VMID.

In this step, get ready to:
  • Fetch all QEMU VMs and LXC containers from the Proxmox API.
  • Display a formatted guest table with VMID, type, name, and power status.
Fetch all VMs and containers

Proxmox manages two types of guests. QEMU VMs are full virtual machines with their own kernel. LXC containers are lighter, sharing the host's kernel.

The API exposes separate endpoints for each type, so you need to query both and combine the results into a single list.

  • In pve_monitor.py, find the print_node_report function and add the following get_guests function below it:
def get_guests(proxmox, node_name):
    guests = []
    # Fetch all QEMU virtual machines
    for vm in proxmox.nodes(node_name).qemu.get():
        guests.append({
            "vmid": vm["vmid"],
            "name": vm.get("name", "unnamed"),
            "type": "VM",
            "status": vm["status"],
            "cpu_percent": round(vm.get("cpu", 0) * 100, 1),
            "mem_used": vm.get("mem", 0),
            "mem_total": vm.get("maxmem", 1),
        })
    # Fetch all LXC containers
    for ct in proxmox.nodes(node_name).lxc.get():
        guests.append({
            "vmid": ct["vmid"],
            "name": ct.get("name", "unnamed"),
            "type": "CT",
            "status": ct["status"],
            "cpu_percent": round(ct.get("cpu", 0) * 100, 1),
            "mem_used": ct.get("mem", 0),
            "mem_total": ct.get("maxmem", 1),
        })
    return guests

What does this code do?

This function queries two separate Proxmox API endpoints and merges everything into one list.

  • proxmox.nodes(node_name).qemu.get() fetches all QEMU VMs from the /nodes/{node}/qemu endpoint.
  • proxmox.nodes(node_name).lxc.get() fetches all LXC containers from the /nodes/{node}/lxc endpoint.
  • Each guest dict stores cpu_percent, mem_used, and mem_total alongside the basic info. You will use those fields in the next step to display resource usage.
  • Save pve_monitor.py.
Print a formatted guest table

Now that you can fetch every guest, you need a function that formats them into a readable table. You also need an orchestrator function that ties your node report and guest report together.

  • In pve_monitor.py, add the print_guest_report function below get_guests:
def print_guest_report(guests):
    print(f"\n{BOLD}=== Virtual Machines and Containers ==={RESET}")
    if not guests:
        print("  No VMs or containers found.")
        return []

    # Print table header
    print(f"  {'ID':<6} {'Type':<5} {'Name':<20} {'Status':<10}")
    print(f"  {'-' * 41}")

    # Print each guest sorted by VMID
    for g in sorted(guests, key=lambda x: x["vmid"]):
        if g["status"] == "running":
            status_str = f"{GREEN}{g['status']}{RESET}"
        else:
            status_str = f"{RED}{g['status']}{RESET}"
        print(f"  {g['vmid']:<6} {g['type']:<5} {g['name']:<20} {status_str}")

    return []

What does this code do?

  • If there are no guests, it prints a message and returns early.
  • sorted(guests, key=lambda x: x["vmid"]) orders the guests by their VMID so the table is consistent every time you run the script.
  • Running guests appear in green. Stopped guests appear in red. The color makes it easy to spot anything that should not be off.
  • The function returns an empty list for now. In the next step, you will use this return value to pass warnings upstream.
  • Now add the run_report function below print_guest_report. This orchestrator ties your node report and guest report together in one flow:
def run_report(proxmox):
    # Orchestrate the full health report
    nodes = proxmox.nodes.get()
    for node in nodes:
        node_name = node["node"]
        node_info = get_node_status(proxmox, node_name)
        print_node_report(node_info)
        guests = get_guests(proxmox, node_name)
        print_guest_report(guests)

Why a separate run_report function?

Right now, main() handles both connection logic and the reporting loop. Pulling the reporting into its own function keeps main() focused on setup and error handling. This separation also makes it easy to call the report from different contexts later.

The last piece is updating main() to use the new run_report function instead of its inline loop.

  • In your main() function, find these lines after the try/except block:
    nodes = proxmox.nodes.get()
    for node in nodes:
        node_name = node["node"]
        node_info = get_node_status(proxmox, node_name)
        print_node_report(node_info)
  • Replace those five lines with a single call to run_report, so the end of main() now looks like this:
    run_report(proxmox)
  • Save pve_monitor.py.

Before you run the script, what do you expect to see below the node health output? How many guests do you think will appear?

  • Run the script to see the full output by running:
python3 pve_monitor.py

You should see the same node health section from before, followed by a new table headed === Virtual Machines and Containers ===. Every VM and container on your server appears as a row with its VMID, type (VM or CT), name, and status in green or red.

Not seeing any guests in the table?

  • If you see "No VMs or containers found", confirm that your Proxmox server actually has at least one VM or container created. An empty server returns an empty list.
  • If you get a permissions error, make sure the API token's user has the PVEAuditor role assigned at the / path so it can read all nodes and guests.
  • If the script errors on run_report, check that you removed the old five-line loop from main() and replaced it with run_report(proxmox).

Why aren't my VMs and containers showing up in the Proxmox health monitor?

✔️ Awesome, I've got everything!

Great. Double check you have saved pve_monitor.py.

ⓧ I'd like to double check the full code

Your pve_monitor.py file should contain all of the following sections in this order. Scroll up through the step and confirm each block is present.

  • Imports (sys, ProxmoxAPI) and configuration constants at the top.
  • connect() and color_value() helper functions from the previous step.
  • get_node_status() and print_node_report() from the previous step.
  • get_guests() fetching QEMU VMs and LXC containers.
  • print_guest_report() printing the formatted table with ID, Type, Name, and Status columns.
  • run_report() orchestrating the full report.
  • main() calling run_report(proxmox) instead of the old inline loop.

Your monitor now shows node health and every guest on the server. But the table only shows power state. A VM could say "running" while silently eating 95% of your memory. Next up, you will add resource usage columns and color-coded warnings so problems stand out at a glance.

Build the Health Report with Warnings

Your monitor now lists every VM and container on your server with its name, type, and power state. You can see what exists and whether it is running or stopped.

Knowing a VM is "running" tells you it is alive, but not how hard it is working. A guest could be silently consuming 95% of your memory while showing a cheerful green status. This step adds resource metrics, threshold-based color coding, and automatic warnings that surface problems the moment they appear.

In this step, get ready to:
  • Add CPU and memory usage columns to the guest table with color coding.
  • Build a warnings section that flags any resource over 80%.
  • Add a summary line showing guest counts and overall node health.
Extend the guest table with resource usage columns

Your color_value() function already applies green, yellow, and red color coding based on thresholds. The guest table just does not use it yet.

  • In pve_monitor.py, find the print_guest_report() function and replace the entire function with this updated version:
def print_guest_report(guests):
    print(f"\n{BOLD}=== Virtual Machines and Containers ==={RESET}")
    if not guests:
        print("  No VMs or containers found.")
        return []

    warnings = []  # Collect threshold violations
    print(f"  {'ID':<6} {'Type':<5} {'Name':<20} {'Status':<10} {'CPU':<10} {'Memory':<10}")
    print(f"  {'-' * 61}")

    for g in sorted(guests, key=lambda x: x["vmid"]):
        if g["status"] == "running":
            status_str = f"{GREEN}{g['status']}{RESET}"
            # Calculate memory percentage, guard against division by zero
            mem_percent = round(g["mem_used"] / g["mem_total"] * 100, 1) if g["mem_total"] > 0 else 0
            cpu_str = color_value(g["cpu_percent"], CPU_WARN)
            mem_str = color_value(mem_percent, MEM_WARN)
            # Flag resources over the 80% threshold
            if g["cpu_percent"] >= CPU_WARN:
                warnings.append(f"{g['type']} {g['vmid']} ({g['name']}): CPU at {g['cpu_percent']}%")
            if mem_percent >= MEM_WARN:
                warnings.append(f"{g['type']} {g['vmid']} ({g['name']}): Memory at {mem_percent}%")
        else:
            status_str = f"{RED}{g['status']}{RESET}"
            cpu_str = "-"  # No usage data for stopped guests
            mem_str = "-"

        print(f"  {g['vmid']:<6} {g['type']:<5} {g['name']:<20} {status_str:<19} {cpu_str:<19} {mem_str:<10}")

    return warnings

What changed in this function?

The table header now includes CPU and Memory columns. For running guests, the function calculates memory percentage and passes both CPU and memory values through color_value() for color coding.

The function also collects warnings now. Any running guest with CPU or memory over 80% gets added to a warnings list, which the function returns instead of an empty list. Stopped guests show - for their resource columns since they have no active usage.

  • Save pve_monitor.py.
  • Run your script to see the new columns by running this command:
python3 pve_monitor.py

Your guest table now shows CPU and Memory columns. Running guests display color-coded percentages (green, yellow, or red). Stopped guests show - in both columns.

Seeing a syntax error or missing columns?

  • Make sure you replaced the entire print_guest_report() function, including the def line. Leaving part of the old function behind causes indentation or logic errors.
  • Check that the print format string uses :<19 for status_str and cpu_str. The ANSI escape codes are invisible characters that take up width, so these columns need extra padding.
  • Still stuck? Help me debug my print_guest_report function. I replaced it but the table columns are not showing correctly.
Add a warnings section

The guest table now collects warnings behind the scenes, but nothing displays them yet. You need a function that checks both node-level and guest-level thresholds, then prints them so problems stand out.

  • In pve_monitor.py, add the print_warnings() function below print_guest_report():
def print_warnings(node_info, guest_warnings):
    # Start with guest warnings, then prepend node-level issues
    warnings = list(guest_warnings)
    if node_info["cpu_percent"] >= CPU_WARN:
        warnings.insert(0, f"Node {node_info['name']}: CPU at {node_info['cpu_percent']}%")
    if node_info["mem_percent"] >= MEM_WARN:
        warnings.insert(0, f"Node {node_info['name']}: Memory at {node_info['mem_percent']}%")
    if node_info["disk_percent"] >= DISK_WARN:
        warnings.insert(0, f"Node {node_info['name']}: Disk at {node_info['disk_percent']}%")

    if warnings:
        print(f"\n{BOLD}{YELLOW}Warnings:{RESET}")
        for w in warnings:
            print(f"  {YELLOW}! {w}{RESET}")
    else:
        # All clear message when nothing crosses the threshold
        print(f"\n{GREEN}No warnings. All systems healthy.{RESET}")

What does this function do?

  • It takes the guest warnings returned by print_guest_report() and adds any node-level threshold violations (CPU, memory, disk) at the front of the list.
  • If any warnings exist, they print in yellow with a ! prefix so they stand out. If everything is healthy, you see a green "all clear" message instead.

Now you need to wire this function into run_report() so it actually gets called.

  • In the run_report() function, find this line:
        print_guest_report(guests)
  • Replace that line with these two lines, which capture the warnings and pass them to the new function:
        guest_warnings = print_guest_report(guests)
        print_warnings(node_info, guest_warnings)
  • Save pve_monitor.py and run the script again:
python3 pve_monitor.py

Below the guest table, you should now see either a yellow Warnings section listing specific resources over 80%, or a green "No warnings. All systems healthy." message.

Not seeing the warnings section?

  • Make sure you replaced the old print_guest_report(guests) call in run_report() with the two new lines. If the old line is still there, print_warnings() never gets called.
  • Check that print_warnings() is defined at the module level (no extra indentation), not accidentally nested inside another function.
  • Still stuck? Help me debug my warnings section. I added print_warnings but nothing appears after the guest table.
Add a summary section

The report now shows node health, guest details, and warnings. A summary line at the bottom ties it all together so you can glance at one line and know the state of your server.

  • In pve_monitor.py, add the print_summary() function below print_warnings():
def print_summary(node_info, guests):
    # Count guests by status
    running = sum(1 for g in guests if g["status"] == "running")
    stopped = sum(1 for g in guests if g["status"] == "stopped")
    total = len(guests)
    print(f"\n{BOLD}=== Summary ==={RESET}")
    print(f"  Guests: {total} total, {GREEN}{running} running{RESET}, {stopped} stopped")
    # One-line node health overview with color-coded percentages
    print(
        f"  Node:   CPU {color_value(node_info['cpu_percent'], CPU_WARN)}"
        f" | Memory {color_value(node_info['mem_percent'], MEM_WARN)}"
        f" | Disk {color_value(node_info['disk_percent'], DISK_WARN)}"
    )

What does this function do?

  • It counts how many guests are running vs stopped and prints a totals line.
  • The node health overview line uses color_value() on CPU, memory, and disk so you can see all three at a glance with the same green/yellow/red color coding.

Now wire this function into run_report().

  • In run_report(), find the print_warnings(node_info, guest_warnings) line you just added. Add this line directly below it:
        print_summary(node_info, guests)
  • Save pve_monitor.py.

Before you run this, what do you expect to see at the bottom of the output that was not there before?

  • Run your health monitor to see the complete report:
python3 pve_monitor.py

You should see the full health report: color-coded node health at the top, a guest table with CPU and memory columns, a Warnings section (yellow if anything is over 80%, green if all clear), and a Summary line at the bottom with guest counts and a one-line node overview.

Summary not appearing?

  • Check that the print_summary(node_info, guests) line is indented inside the for node in nodes: loop, at the same indentation level as print_warnings().
  • Verify that print_summary() is defined at the module level, not accidentally nested inside print_warnings() or another function.
  • Still stuck? Help me debug my health report. The summary section is not showing at the bottom of my output.

✔️ Awesome, I've got everything!

Great. Double check you have saved pve_monitor.py.

ⓧ I'd like to double check the full code

Scroll up through your pve_monitor.py file and confirm these sections are present, in this order:

  • Configuration constants (PVE_HOST, PVE_USER, etc.), threshold constants, and ANSI color constants at the top.
  • connect() and color_value() functions (unchanged from previous steps).
  • get_node_status() and print_node_report() functions (unchanged from previous steps).
  • get_guests() function (unchanged from previous step).
  • Updated print_guest_report() with CPU/Memory columns, color coding, and a warnings return value.
  • New print_warnings() function that checks node and guest thresholds.
  • New print_summary() function that prints guest counts and node health.
  • Updated run_report() that calls all four report functions: print_node_report(), print_guest_report(), print_warnings(), and print_summary().
  • main() function and if __name__ == "__main__": block at the bottom (unchanged).

Secret mission

Add Watch Mode

Your health monitor runs once and exits. Real infrastructure monitoring means watching for changes over time. In this mission, you add a --watch flag that auto-refreshes the report every N seconds, turning your terminal into a live dashboard.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on your local Mac and connects to your own Proxmox VE server. There are no cloud services or paid APIs to worry about, so there are no ongoing costs.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Project folder on your Desktop containing pve_monitor.py and the venv virtual environment.
  • The monitor@pve user and monitoring API token on your Proxmox VE server.

Keep everything running

No action needed. Choose this if you plan to keep using your health monitor script or want to extend it further.

  • Your project folder stays on your Desktop, ready to run whenever you need a quick health check.
  • The monitor@pve user and API token remain on your Proxmox server with read-only (PVEAuditor) permissions, so they pose no security risk.
  • Python 3.13 remains installed on your Mac as a system-level tool.

Pause - I'll come back to this later

Shut down any active processes to free up your terminal. Your files and Proxmox configuration stay in place for when you return.

  • If watch mode is still running in your terminal, press Ctrl+C to stop it.
  • If your virtual environment is still active, deactivate it by running:
deactivate
  • Your project folder and Proxmox API token stay in place. When you come back, activate the virtual environment again with source venv/bin/activate from inside your project folder.

Delete - I don't want to use this again

Remove all project resources and start fresh. This deletes your local files and the monitoring credentials on your Proxmox server.

  • If your virtual environment is still active, deactivate it by running:
deactivate
  • Delete your project folder from the Desktop by running:
rm -rf ~/Desktop/[[PROJECT_FOLDER="your project folder name"]]
  • Confirm the folder is gone by running:
ls ~/Desktop
  • Your project folder should no longer appear in the output.
  • To remove the API token and monitoring user from your Proxmox server, open the Proxmox web UI in your browser.
  • Navigate to Datacenter, then Permissions, then API Tokens.
  • Select the monitor@pve!monitoring token and click Remove.
  • Navigate to Datacenter, then Permissions, then Users.
  • Select the monitor@pve user and click Remove.

Nice Work!

Nice Work!

Nice work! You just built a fully functional terminal-based health monitor for your Proxmox VE server.

You've learned how to:

  • Build a Python CLI tool that connects to your Proxmox VE server's REST API using a secure, read-only API token for safe programmatic monitoring.
  • Pull live node health data (CPU, memory, disk) and list every VM and container with its power state and resource usage in formatted tables.
  • Add threshold-based warnings with color-coded output so problems stand out at a glance, plus a summary line for a quick health overview.
  • Secret Mission: You added a watch mode that turns your terminal into a live monitoring dashboard with auto-refresh.

Ready to quiz yourself?