Pyinfra: getting started

Automation is a huge time-saver in managing servers and containers. If you’ve ever been bogged down by blocks of YAML in Ansible or complex setups in Terraform, read on! This starts a little series of small posts on pyinfra. I’m assuming you’re already somewhat familiar with things like ansible or terraform. Basically, we want to automate a few things on other linux boxes.

My specific use case is managing tailscale on LXC containers on a proxmox server. I want to make sure that all of the lxc containers have tailscale running and are connected to the network. I currently access my internal network using tailscale running on proxmox and advertising itself as a gateway to my internal subnet. It’s not ideal and it means I will have a few extra steps when trying to keep track of what boxes are running, and their IPs. So let’s install and configure tailscale on all of the lxc containers.

Here’s where pyinfra is magical. With ansible, you’d need plugins, and blocks and blocks of yaml. Here is the file (the actual one minus the keys) that I use to manage tailscale on my lxc containers:

from ipaddress import IPv4Address, IPv4Network
from unittest import TestCase

from proxmoxer import ProxmoxAPI

def get_running_nodes(net: IPv4Network) -> list[IPv4Address]:
    proxmox = ProxmoxAPI(
        '...',
        user='...',
        token_name="proxmoxer-1",
        token_value='...',
        verify_ssl=False)

    lxc_ips: list[IPv4Address] = []
    for node in proxmox.nodes.get():
        for lxc in proxmox.nodes(node['node']).lxc.get():
            if lxc['status'] == 'running':
                lxc_interface_info: dict[str, str] = proxmox.nodes(node['node']).lxc(lxc['vmid']).interfaces.get()
                for interface in lxc_interface_info:
                    if (parsed := IPv4Address(interface['inet'].rpartition('/')[0])) in net:
                        lxc_ips.append(parsed)
    return lxc_ips

running_lxc = [(str(i), {"ssh_user": "root",}) for i in
               get_running_nodes(IPv4Network('192.168.0.0/16'))]

That’s it! A uv init, uv add pyinfra, and uv run pyinfra inventory.py exec ip addr show and you’re running against all of the lxc containers you’ve got a copied ssh key on. Don’t have a key copied on your containers? Just add "ssh_password": "..." to the dictionary in running_lxc. Imagine trying to do that in ansible.

A small video of the pyinfra run