Learn how pyinfra executes operations in parallel across multiple hosts for maximum performance
One of pyinfra’s key strengths is its ability to execute operations in parallel across hundreds or thousands of hosts. This guide explains how parallel execution works and how to optimize your deploys for maximum performance.
Deploy code runs once per host to determine what operations to execute:
# This code runs sequentially for each hostfrom pyinfra import hostfrom pyinfra.operations import aptif "web_servers" in host.groups: apt.packages(name="Install nginx", packages=["nginx"])
Operations execute in the order they appear in your deploy file:
deploy.py
from pyinfra.operations import apt, files, server# 1. Runs first on all hosts in parallelapt.packages( name="Install nginx", packages=["nginx"], _sudo=True,)# 2. Runs second on all hosts in parallel (after step 1 completes)files.template( name="Upload nginx config", src="templates/nginx.conf.j2", dest="/etc/nginx/nginx.conf", _sudo=True,)# 3. Runs third on all hosts in parallel (after step 2 completes)server.service( name="Restart nginx", service="nginx", restarted=True, _sudo=True,)
Control how many hosts execute operations simultaneously:
# Default: all hosts in parallelpyinfra inventory.py deploy.py# Limit to 10 hosts at a timepyinfra inventory.py deploy.py --parallel 10# Process hosts one at a time (serial execution)pyinfra inventory.py deploy.py --parallel 1
Force an operation to run serially with _serial=True:
from pyinfra.operations import server# This operation runs on one host at a timeserver.shell( name="Run intensive migration", commands=["/opt/app/migrate.sh"], _serial=True, _sudo=True,)
Execution flow:
Host 1: Run migration ━━━━━━━━━━► Complete ⬇ Host 2: Run migration ━━━━━━━━━━► Complete ⬇ Host 3: Run migration ━━━━━━━━━━► Complete
Use _serial=True for operations that must not run simultaneously (database migrations, shared resource access, etc.).
from pyinfra import hostfrom pyinfra.operations import apt, server# Deploy to database servers firstif "db_servers" in host.groups: apt.packages( name="Install PostgreSQL", packages=["postgresql"], _sudo=True, ) server.shell( name="Run database migrations", commands=["/opt/app/migrate.sh"], _serial=True, # One DB at a time _sudo=True, )# Then deploy to web serversif "web_servers" in host.groups: apt.packages( name="Install nginx", packages=["nginx"], _sudo=True, )
from pyinfra import hostfrom pyinfra.operations import git, server# Update code on all web servers in parallelgit.repo( name="Pull latest code", src=host.data.app_repo, dest="/opt/myapp", branch="main", pull=True, _sudo=True,)# Restart app servers one at a time (rolling restart)server.service( name="Rolling restart of application", service="myapp", restarted=True, _serial=True, # One at a time to maintain availability _sudo=True,)
The prepare phase is critical for parallel execution:
deploy.py
from pyinfra import hostfrom pyinfra.operations import apt# This code runs once per host during prepareif host.name.startswith("web-"): apt.packages( name="Install nginx", packages=["nginx"], _sudo=True, )
What happens:
Prepare Phase (sequential): - Execute deploy.py for web-01 → Register "Install nginx" op - Execute deploy.py for web-02 → Register "Install nginx" op - Execute deploy.py for web-03 → Register "Install nginx" op - Build operation orderExecute Phase (parallel): - Operation "Install nginx" runs on web-01, web-02, web-03 simultaneously
The prepare phase must complete for all hosts before any operations execute. This ensures correct operation ordering.