pyinfra
pyinfra is an infrastructure automation tool that transforms Python code into shell commands, executing them on remote servers, local machines, or Docker containers. It offers fast, agentless deployments and scales from single servers to thousands, aiming to be a Python-based alternative to YAML-centric tools like Ansible. It is currently at version 3.7 and maintains an active development and release cadence.
Common errors
-
ModuleNotFoundError: No module named 'pyinfra.operations.apt'
cause Attempting to import a specific operation directly from `pyinfra.operations` without specifying the operation module (e.g., `apt`).fixImport operations from their respective sub-modules, such as `from pyinfra.operations import apt`, then call `apt.packages()`. -
pyinfra.exceptions.ConnectError: SSH connection failed: Permission denied (publickey,password).
cause Incorrect SSH credentials, missing SSH keys, or issues with SSH agent forwarding when connecting to a remote host.fixVerify your SSH configuration (`~/.ssh/config`), ensure your SSH agent is running and has the correct keys, or provide explicit `--ssh-user` and `--ssh-password` (or `--ssh-password-prompt`) arguments to the `pyinfra` CLI. -
Operation doesn't execute or fails without error messages in pyinfra output.
cause The operation's `_if` or `_when` conditions were not met, causing it to be skipped, or an underlying command failed silently.fixRun pyinfra with `--debug` and `--log-level debug` flags. Check the conditions on the operation. If it's a `server.shell` operation, inspect the `stdout` and `stderr` after execution for clues. -
pyinfra reports 'no changes' but the desired state is not met, or 'changes' when no changes should occur.
cause The facts gathered by pyinfra do not accurately reflect the host's state, or the operation's logic for detecting changes is flawed.fixDebug facts by inspecting `host.get_fact(FactName)` results directly. For operations, use the `--dry` flag to see what commands *would* be run, and review the operation's source or implement explicit state checks.
Warnings
- breaking The `_use_sudo_password` argument has been renamed to `_sudo_password` in pyinfra v3.x.
- breaking The `@deploy` decorator must now be called as a function: `@deploy()` instead of `@deploy` in pyinfra v3.x.
- breaking The `winrm` connector and `windows*` operations/facts have been removed from the core `pyinfra` package in v3.x and moved to the `pyinfra-windows` plugin.
- gotcha Operations can fail silently or not execute as expected without clear error messages, especially if conditions (`_if`, `_when`) are not met.
- gotcha An operation might run every time, even if the desired state is already met, indicating a lack of proper idempotency checks.
Install
-
pip install pyinfra -
uv tool install pyinfra
Imports
- apt.packages
from pyinfra.operations import apt
- host
from pyinfra import host
- Hostname (fact)
from pyinfra.facts.server import Hostname
- deploy (decorator)
@deploy
from pyinfra.api import deploy
Quickstart
import os
from pyinfra.operations import apt, files
# Define target hosts (using @local for a quick test)
# For remote SSH, change to: hosts = ['my-server.net']
# For Docker, change to: hosts = ['@docker/ubuntu:latest']
# Create a dummy inventory file if running with `pyinfra inventory.py deploy.py`
# In a real scenario, this would connect to actual hosts.
# If running directly, pyinfra will prompt or use @local if no inventory is specified.
# deploy.py
# Ensure apt packages are updated and Nginx is installed
apt.update(name="Update apt repositories", _sudo=True)
apt.packages(name="Install Nginx", packages=['nginx'], _sudo=True)
# Ensure Nginx service is running and enabled
files.file(
name="Ensure Nginx config is present",
path="/etc/nginx/sites-available/default",
contents="""
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
server_name _;
location / {
try_files $uri $uri/ =404;
}
}
""",
_sudo=True
)
apt.update(
name="Run apt update (idempotent)",
_sudo=True
)
apt.packages(
name="Install Nginx (idempotent)",
packages=["nginx"],
_sudo=True
)
# To make this runnable directly as a Python script for illustration:
# This part is typically handled by the `pyinfra` CLI.
# We will simulate a direct execution for simplicity, but real use is via `pyinfra <inventory> <deploy>`.
# import sys
# from pyinfra.api.local import Local
# with Local(['@local']) as state:
# # In a real deploy.py, the operations would run directly.
# # For this quickstart, we just define them as above.
# print("Deploy operations defined. Run with: pyinfra @local deploy.py")
# print("Or via SSH: pyinfra my-host.net deploy.py")
# Example of how to execute from CLI:
# Create a file named 'deploy.py' with the above content.
# Then run: pyinfra @local deploy.py
# Or to a remote host: pyinfra my-server.net deploy.py --ssh-user <your_user> --ssh-password "$PYINFRA_SSH_PASSWORD"