How to automate infrastructure provisioning with PyInfra 3.8.0 on Linux servers
Why PyInfra 3.8.0 for Infrastructure Automation
PyInfra is a Python-based infrastructure automation tool that lets you manage servers without learning domain-specific languages like HCL or YAML configuration syntax. With the release of PyInfra 3.8.0, the tool brings performance improvements and expanded functionality for DevOps teams managing Linux infrastructure at scale.
If you're currently writing shell scripts to provision servers or managing infrastructure manually, PyInfra 3.8.0 offers a more maintainable, testable alternative that integrates seamlessly into your Python development workflow.
Key Improvements in PyInfra 3.8.0
The 3.8.0 release focuses on stability and developer experience:
- Enhanced module compatibility: Better support for modern Linux distributions (Ubuntu 24.04, Debian 12, CentOS 9)
- Improved idempotency: Operations now better detect when they've already been applied, reducing unnecessary re-runs
- Performance optimizations: Faster fact gathering and parallel execution capabilities
- Extended package manager support: Improved handling of apt, yum, and dnf across different Linux versions
Setting Up PyInfra 3.8.0
Installation on Your Control Machine
# Install PyInfra via pip
pip install pyinfra==3.8.0
# Verify installation
pyinfra --version
You'll run PyInfra from your local machine (Linux, macOS, or Windows with WSL). It connects to target servers via SSH without requiring agents.
Basic Project Structure
Create a new PyInfra project with this structure:
my-infra/
├── deploy.py # Main deployment script
├── inventory.py # Server inventory and groups
└── operations/
├── __init__.py
├── base.py # Common setup operations
└── web.py # Web server operations
Defining Your Server Inventory
Start by creating an inventory.py file that defines your servers:
# inventory.py
from pyinfra.api import Config
# Define server groups
servers = {
'web': [
'web1.example.com',
'web2.example.com',
],
'database': [
'db1.example.com',
]
}
# SSH configuration (optional)
config = Config(
sudo=True, # Use sudo for commands
sudo_user='deploy', # Run as specific user
ssh_port=2222, # Custom SSH port
shell_executable='/bin/bash',
)
Creating Your First Deployment
Here's a practical example: provisioning a basic web server with Nginx:
# deploy.py
from pyinfra.operations import (
apt,
files,
service,
systemd,
)
# Update system packages
apt.update()
# Install required software
apt.packages(
name='Install web server stack',
packages=['nginx', 'curl', 'git'],
update=True,
)
# Copy custom Nginx config
files.put(
name='Deploy Nginx configuration',
src='./configs/nginx.conf',
dest='/etc/nginx/nginx.conf',
user='root',
group='root',
mode='0644',
)
# Enable and start Nginx
systemd.service(
name='Start Nginx',
service='nginx',
enabled=True,
running=True,
restarted=True, # Restart if config changed
)
Executing Your Deployment
Run your infrastructure code against specific server groups:
# Deploy to all web servers
pyinfra inventory.py deploy.py
# Deploy only to specific group
pyinfra @web inventory.py deploy.py
# Dry-run to see what would change
pyinfra --dry inventory.py deploy.py
# Show what would be executed without running it
pyinfra --plan inventory.py deploy.py
Advanced Features in 3.8.0
Conditional Operations
Execute operations based on server facts:
from pyinfra import host
# Only run on Ubuntu systems
if 'ubuntu' in host.fact.os:
apt.packages(packages=['ubuntu-specific-tool'])
# Only run if package not already installed
if 'nginx' not in host.fact.deb_packages:
apt.packages(packages=['nginx'])
Using Variables and Groups
# inventory.py
servers = {
'web': {
'hosts': {
'web1.example.com': {'app_env': 'production'},
'web2.example.com': {'app_env': 'staging'},
}
}
}
# deploy.py
from pyinfra import host
app_env = host.data.get('app_env', 'development')
files.put(
name=f'Deploy config for {app_env}',
src=f'./configs/app-{app_env}.conf',
dest='/etc/app/config.conf',
)
Comparison: PyInfra vs Alternatives
| Feature | PyInfra 3.8.0 | Ansible | Terraform | Puppet | |---------|--------------|---------|-----------|--------| | Language | Python | YAML | HCL | Ruby/Puppet DSL | | Learning curve | Moderate | Low | Moderate | High | | Agent required | No | No | No | Yes | | Idempotent | Yes | Yes | Yes | Yes | | Infrastructure state tracking | Limited | Moderate | Full (tfstate) | Full | | Best for | Python teams, Config mgmt | Multi-tool orchestration | Cloud infrastructure | Enterprise config |
Common Pitfalls with PyInfra 3.8.0
Forgetting Idempotency
Always design operations to be safe when run multiple times:
# ✅ Good: Creates file only if it doesn't exist
files.put(
name='Create app config',
src='app.conf',
dest='/etc/app.conf',
create_remote_dir=True,
)
# ❌ Bad: Overwrites file every time
files.replace(
name='Update app config',
path='/etc/app.conf',
text='new content',
)
SSH Key Authentication Issues
Ensure your control machine can reach target servers:
# Test SSH connectivity
ssh -i ~/.ssh/deploy_key deploy@web1.example.com 'echo connected'
# Add key to ssh-agent for PyInfra
ssh-add ~/.ssh/deploy_key
Managing Secrets
Never hardcode passwords. Use environment variables or secret management:
import os
from pyinfra.operations import files
db_password = os.environ['DATABASE_PASSWORD']
files.template(
name='Deploy database config',
src='db-config.j2',
dest='/etc/app/database.conf',
context={'password': db_password},
)
Running PyInfra in CI/CD Pipelines
Integrate PyInfra 3.8.0 into GitHub Actions or GitLab CI:
# .github/workflows/deploy.yml
name: Deploy Infrastructure
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- run: pip install pyinfra==3.8.0
- run: pyinfra --dry inventory.py deploy.py
- run: pyinfra inventory.py deploy.py
if: github.ref == 'refs/heads/main'
env:
SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
Performance Tips for Large Deployments
When managing 50+ servers, optimize PyInfra's execution:
# Use parallel execution
pyinfra --parallel 10 inventory.py deploy.py
# Limit fact gathering
config = Config(
gather_facts=False, # Skip if not needed
fact_cache=True, # Cache facts between runs
)
Conclusion
PyInfra 3.8.0 brings Python's simplicity to infrastructure automation, making it ideal for teams already invested in Python workflows. With proper SSH setup, idempotent operations, and CI/CD integration, you can automate complex server provisioning without the overhead of heavier tools like Terraform or Ansible.
Start with a single server group, validate your deployment scripts with dry-runs, and gradually expand to manage your entire infrastructure declaratively.
Recommended Tools
- DigitalOceanSimplicity in the cloud
- VercelDeploy web apps at the speed of inspiration