Skip to main content
This guide will walk you through installing pyinfra and running your first deployment. You’ll learn the core workflow and deploy a simple web server configuration.

Prerequisites

Before you begin, ensure you have:
  • Python 3.10 or higher installed
  • SSH access to a server (or use Docker/local execution)
  • Basic familiarity with Python and command line
Don’t have a server? No problem! You can use @docker/ubuntu or @local to test pyinfra on your local machine.

Installation

Install pyinfra using your preferred package manager:
uv tool install pyinfra
Verify the installation:
pyinfra --version
If you see a version number, you’re ready to go!

Your First Command

Let’s run a simple command on a target. We’ll use a Docker container for this example:
pyinfra @docker/ubuntu exec -- echo "Hello from pyinfra!"
This command:
  • Targets a Docker container (@docker/ubuntu)
  • Executes a shell command (exec)
  • Runs echo "Hello from pyinfra!"
  • @docker/ubuntu: The target connector and image
  • exec: The pyinfra command to execute
  • --: Separates pyinfra arguments from the shell command
  • echo "Hello from pyinfra!": The actual command to run

Try Different Targets

# Replace with your server hostname
pyinfra my-server.net exec -- echo "Hello from pyinfra!"

Using Operations

Commands are great for ad-hoc tasks, but pyinfra really shines with operations. Operations are declarative and idempotent. Let’s install a package using the apt.packages operation:
pyinfra @docker/ubuntu apt.packages iftop update=true _sudo=true
This operation:
  • Installs the iftop package
  • Updates the apt cache first (update=true)
  • Runs with sudo privileges (_sudo=true)
  • Only makes changes if iftop isn’t already installed
Run this command twice - notice the second time it says “no changes”? That’s idempotency in action.

Creating Your First Deploy File

For anything more complex than a single operation, you’ll want to create a deploy file. This is a Python file that defines your infrastructure.
1

Create a deploy file

Create a file called deploy.py:
deploy.py
from pyinfra.operations import apt, files, server

# Update package lists and upgrade all packages
apt.update(
    name="Update apt cache",
    _sudo=True,
)

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

# Create a simple nginx landing page
files.put(
    name="Deploy custom index.html",
    src="index.html",
    dest="/var/www/html/index.html",
    mode="644",
    _sudo=True,
)

# Ensure nginx is running
server.service(
    name="Ensure nginx is running",
    service="nginx",
    running=True,
    enabled=True,
    _sudo=True,
)
2

Create the index.html file

Create index.html in the same directory:
index.html
<!DOCTYPE html>
<html>
<head>
    <title>pyinfra deployed this!</title>
</head>
<body>
    <h1>Hello from pyinfra!</h1>
    <p>This page was deployed using pyinfra.</p>
</body>
</html>
3

Run the deploy

Execute your deploy file:
pyinfra @docker/ubuntu deploy.py
You’ll see output showing each operation and what changes were made:
--> Loading deploy file: deploy.py
--> Loaded operations: 4

--> Executing operations...
[SUCCESS] Update apt cache
[SUCCESS] Install essential tools (4 packages added)
[SUCCESS] Deploy custom index.html (file uploaded)
[SUCCESS] Ensure nginx is running (service started)

--> Results:
    Operations: 4
    Commands: 12
    Errors: 0
4

Verify it worked

Check that nginx is serving your page:
pyinfra @docker/ubuntu exec -- curl localhost
You should see your HTML page!

Creating an Inventory File

Typing host names on the command line works for quick tasks, but for real deployments you’ll want an inventory file. Create inventory.py:
inventory.py
# Define your hosts
targets = [
    "@docker/ubuntu",  # Docker container
    "web1.example.com",  # Production web server
    ("web2.example.com", {"ssh_port": 2222}),  # Server with custom SSH port
]

# Define groups for organizing hosts
webservers = [
    "web1.example.com",
    "web2.example.com",
]

containers = [
    "@docker/ubuntu",
]
Now run your deploy against the inventory:
pyinfra inventory.py deploy.py
This will execute deploy.py on all hosts defined in inventory.py.
You can limit execution to specific hosts or groups using the --limit flag:
pyinfra inventory.py deploy.py --limit webservers

Dry Run Mode

Before making changes to production servers, use dry run mode to see what would happen:
pyinfra inventory.py deploy.py --dry
This shows all the commands that would be executed without actually running them.
Always test with --dry before deploying to production!

Debugging

Need to see exactly what’s happening? Use verbose output:
# Show operation details
pyinfra inventory.py deploy.py -v

# Show commands being executed
pyinfra inventory.py deploy.py -vv

# Show all output including stderr
pyinfra inventory.py deploy.py -vvv

Using Python Features

Remember, deploy files are just Python! Use all the language features you know:
deploy.py
from pyinfra import host
from pyinfra.operations import apt, server

# Define a reusable function
def install_web_stack(domain):
    apt.packages(
        name=f"Install packages for {domain}",
        packages=["nginx", "certbot"],
        _sudo=True,
    )
    
    files.template(
        name=f"Configure nginx for {domain}",
        src="nginx.conf.j2",
        dest=f"/etc/nginx/sites-available/{domain}",
        domain=domain,  # Pass variables to template
        _sudo=True,
    )

# Use conditionals
if host.get_fact("Os") == "Ubuntu":
    install_web_stack("example.com")

# Loop over data
for port in [80, 443, 8080]:
    server.shell(
        name=f"Open port {port}",
        commands=f"ufw allow {port}",
        _sudo=True,
    )

Understanding the Workflow

Here’s what happens when you run a deploy:
1

Load inventory

pyinfra loads your inventory file and connects to all target hosts.
2

Gather facts

For each host, pyinfra gathers facts (OS, packages, files, etc.) to understand current state.
3

Generate operations

Your deploy file is executed, generating a list of operations to perform.
4

Generate commands

Each operation compares desired state with current state and generates the minimal shell commands needed.
5

Execute in parallel

Commands are executed on all hosts in parallel for maximum speed.
6

Report results

pyinfra shows you what changed (or didn’t) on each host.

Common Operations

Here are some operations you’ll use frequently:
from pyinfra.operations import apt

# Install packages
apt.packages(
    packages=["nginx", "postgresql", "redis"],
    update=True,
    _sudo=True,
)

# Remove packages
apt.packages(
    packages=["apache2"],
    present=False,
    _sudo=True,
)
from pyinfra.operations import files

# Upload a file
files.put(
    src="local-file.txt",
    dest="/remote/path/file.txt",
    mode="644",
)

# Create directory
files.directory(
    path="/opt/myapp",
    user="ubuntu",
    group="ubuntu",
    mode="755",
    _sudo=True,
)

# Use Jinja2 templates
files.template(
    src="config.j2",
    dest="/etc/myapp/config.yaml",
    setting_value="production",
    _sudo=True,
)
from pyinfra.operations import server

# Manage systemd services
server.service(
    service="nginx",
    running=True,
    enabled=True,
    restarted=True,  # Restart if running
    _sudo=True,
)
from pyinfra.operations import server

# Execute shell commands
server.shell(
    name="Run custom script",
    commands=[
        "cd /opt/myapp",
        "./setup.sh",
    ],
    _sudo=True,
)

# Run a script
server.script(
    src="scripts/deploy.sh",
    _sudo=True,
)

Next Steps

You’ve learned the basics of pyinfra! Here’s where to go next:

Core Concepts

Deep dive into how pyinfra works

Operations Reference

Explore all 40+ operation modules

Inventory & Data

Learn advanced inventory management

CLI Reference

Master the pyinfra command line

Need Help?

GitHub Discussions

Ask questions and share knowledge

Matrix Chat

Real-time community support

Examples Repository

See real-world examples

Issue Tracker

Report bugs and request features

Build docs developers (and LLMs) love