Skip to main content
pyinfra is a powerful infrastructure automation tool that turns Python code into shell commands and runs them on your servers. This tutorial will guide you through installing pyinfra and creating your first deploy.

What You’ll Learn

By the end of this tutorial, you’ll be able to:
  • Install and configure pyinfra
  • Execute ad-hoc commands on remote hosts
  • Create inventory files to manage hosts
  • Write deploy files with operations
  • Understand pyinfra’s idempotent state management

Installation

1

Install pyinfra with uv

The recommended way to install pyinfra is using uv:
uv tool install pyinfra
You can also install pyinfra with pip: pip install pyinfra
2

Verify installation

Verify that pyinfra is installed correctly:
pyinfra --version

Your First pyinfra Command

Let’s start with some simple ad-hoc commands. The CLI always takes arguments in order: INVENTORY then OPERATIONS.

Execute Commands Locally

Test pyinfra on your local machine:
pyinfra @local exec -- echo "Hello from pyinfra!"
The @local connector executes commands on your local machine. This is perfect for testing!

Execute Commands via SSH

Connect to a remote server via SSH:
pyinfra my-server.net exec -- echo "Hello world"
With SSH options:
pyinfra my-server.net --ssh-user vagrant --ssh-port 2222 exec -- echo "Hello world"

Execute Commands in Docker

Run commands in a new Docker container:
pyinfra @docker/ubuntu:22.04 exec -- echo "Hello from Docker!"

Using Operations

Operations are pyinfra’s way of defining desired state. Unlike raw commands, operations are idempotent - they only make changes when necessary.

Install a Package

pyinfra @docker/ubuntu:22.04 apt.packages iftop update=true _sudo=true
Run this command twice - the second time, pyinfra will detect that iftop is already installed and report no changes needed.
pyinfra @docker/ubuntu:22.04 apt.packages iftop update=true _sudo=true
# Output: [ubuntu:22.04] apt.packages: Install packages: iftop
# Changes: 1

Creating Your First Deploy

Now let’s create a reusable deploy that you can save and version control.
1

Create an inventory file

Create a file named inventory.py to define your target hosts:
inventory.py
# Simple list of hostnames
targets = [
    "@local",
    "my-server.net",
]
You can also define hosts with custom connection parameters:
targets = [
    ("web-server", {
        "ssh_hostname": "192.168.1.10",
        "ssh_user": "deploy",
        "ssh_port": 2222,
    }),
]
2

Create a deploy file

Create a file named deploy.py with your operations:
deploy.py
from pyinfra.operations import apt, server

# Install essential packages
apt.packages(
    name="Install debugging tools",
    packages=["htop", "vim", "curl"],
    update=True,
    _sudo=True,
)

# Create a directory
server.shell(
    name="Create application directory",
    commands=["mkdir -p /opt/myapp"],
    _sudo=True,
)
3

Run your deploy

Execute your deploy file against your inventory:
pyinfra inventory.py deploy.py
pyinfra will connect to each host, detect what changes need to be made, and apply them in parallel across all hosts.
4

Run in dry-run mode

Before making actual changes, you can preview what pyinfra will do:
pyinfra inventory.py deploy.py --dry
This shows you exactly what commands will be executed without actually running them.

Understanding the Output

When you run a deploy, pyinfra provides detailed output:
--> Loading config...
--> Loading inventory...
--> Connecting to hosts...
    [local] Connected
--> Preparing operations...
--> Proposing operations...
--> Beginning operation run...
--> Starting operation: Install debugging tools
    [local] apt-get update
    [local] apt-get install -y htop vim curl
--> Results:
    Operations: 2
    Commands: 3
    Hosts: 1

Advanced Inventory with Groups

As your infrastructure grows, you can organize hosts into groups:
inventory.py
# Define groups of hosts
web_servers = [
    "web-01.example.com",
    "web-02.example.com",
]

db_servers = [
    ("db-01.example.com", {
        "ssh_user": "postgres",
    }),
]

# All hosts
targets = web_servers + db_servers
Then target specific groups in your deploy:
deploy.py
from pyinfra import host
from pyinfra.operations import apt

# Install nginx only on web servers
if "web_servers" in host.groups:
    apt.packages(
        name="Install nginx",
        packages=["nginx"],
        _sudo=True,
    )

# Install postgresql only on database servers  
if "db_servers" in host.groups:
    apt.packages(
        name="Install PostgreSQL",
        packages=["postgresql"],
        _sudo=True,
    )

Next Steps

Now that you understand the basics, explore more advanced topics:

Common Patterns

from pyinfra.operations import apt, files, server

# Install package
apt.packages(
    name="Install nginx",
    packages=["nginx"],
    _sudo=True,
)

# Upload configuration
files.put(
    name="Upload nginx config",
    src="files/nginx.conf",
    dest="/etc/nginx/nginx.conf",
    _sudo=True,
)

# Restart service
server.service(
    name="Restart nginx",
    service="nginx",
    restarted=True,
    _sudo=True,
)
Keep your deploy files in version control (git) so you can track changes to your infrastructure over time!

Build docs developers (and LLMs) love