Skip to main content
The Vagrant connector generates pyinfra inventory from running Vagrant VMs. This enables easy integration between Vagrant for local development and pyinfra for configuration management.

Overview

The Vagrant connector automatically detects running Vagrant VMs and generates inventory with SSH connection details.
# Deploy to all running Vagrant VMs
pyinfra @vagrant deploy.py

# Deploy to specific VM
pyinfra @vagrant/web deploy.py

Requirements

  • Vagrant installed and accessible
  • One or more running Vagrant VMs
  • Current directory contains Vagrantfile (or specify path)

Basic Usage

All Running VMs

# Start VMs
vagrant up

# Deploy to all running VMs
pyinfra @vagrant deploy.py

Specific VM

# Deploy to specific VM by name
pyinfra @vagrant/web deploy.py
pyinfra @vagrant/database deploy.py

Multiple VMs

# Deploy to multiple specific VMs
pyinfra @vagrant/web,@vagrant/database deploy.py

Vagrantfile Example

A typical multi-machine Vagrantfile:
# Vagrantfile
Vagrant.configure("2") do |config|
  # Web server
  config.vm.define "web" do |web|
    web.vm.box = "ubuntu/jammy64"
    web.vm.hostname = "web"
    web.vm.network "private_network", ip: "192.168.56.10"
  end
  
  # Database server
  config.vm.define "database" do |db|
    db.vm.box = "ubuntu/jammy64"
    db.vm.hostname = "database"
    db.vm.network "private_network", ip: "192.168.56.11"
  end
  
  # Cache server
  config.vm.define "cache" do |cache|
    cache.vm.box = "ubuntu/jammy64"
    cache.vm.hostname = "cache"
    cache.vm.network "private_network", ip: "192.168.56.12"
  end
end
# Start all VMs
vagrant up

# Deploy to all
pyinfra @vagrant deploy.py

# Or deploy to specific VMs
pyinfra @vagrant/web,@vagrant/database deploy.py

Configuration

The connector reads SSH configuration from vagrant ssh-config:
# View SSH config for a VM
vagrant ssh-config web
# Output:
# Host web
#   HostName 127.0.0.1
#   User vagrant
#   Port 2222
#   IdentityFile /path/to/.vagrant/machines/web/virtualbox/private_key
Pyinfra automatically uses these settings.

Custom Options

Provide additional host data via @vagrant.json:
{
  "web": {
    "app_port": 8000,
    "app_name": "myapp"
  },
  "database": {
    "db_port": 5432,
    "db_name": "myapp"
  }
}
# deploy.py
from pyinfra import host
from pyinfra.operations import apt

if host.name == "web":
    print(f"Deploying {host.data.app_name} on port {host.data.app_port}")
    apt.packages(
        name="Install nginx",
        packages=["nginx"],
        _sudo=True,
    )

elif host.name == "database":
    print(f"Deploying database {host.data.db_name}")
    apt.packages(
        name="Install postgresql",
        packages=["postgresql"],
        _sudo=True,
    )

Deployment Workflow

Typical Vagrant + pyinfra workflow:
# 1. Create Vagrantfile
cat > Vagrantfile <<EOF
Vagrant.configure("2") do |config|
  config.vm.define "web" do |web|
    web.vm.box = "ubuntu/jammy64"
  end
end
EOF

# 2. Start VM
vagrant up

# 3. Deploy with pyinfra
pyinfra @vagrant deploy.py

# 4. Make changes and redeploy
vi deploy.py
pyinfra @vagrant deploy.py

# 5. Destroy when done
vagrant destroy

Testing Deploys Locally

Use Vagrant to test deploys before running on production:
# Vagrantfile - mimics production
Vagrant.configure("2") do |config|
  config.vm.define "staging" do |staging|
    staging.vm.box = "ubuntu/jammy64"
    staging.vm.hostname = "staging"
    # Same as production
    staging.vm.network "private_network", ip: "192.168.56.10"
  end
end
# Same deploy.py used for staging and production
from pyinfra.operations import apt, files, systemd

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

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

systemd.service(
    name="Restart nginx",
    service="nginx",
    restarted=True,
    _sudo=True,
)
# Test locally
vagrant up
pyinfra @vagrant deploy.py

# Verify it works
curl http://192.168.56.10

# Deploy to production
pyinfra production.py deploy.py

Multi-Machine Examples

Web + Database Setup

# Vagrantfile
Vagrant.configure("2") do |config|
  # Web server
  config.vm.define "web" do |web|
    web.vm.box = "ubuntu/jammy64"
    web.vm.network "private_network", ip: "192.168.56.10"
    web.vm.provider "virtualbox" do |vb|
      vb.memory = "1024"
    end
  end
  
  # Database server
  config.vm.define "db" do |db|
    db.vm.box = "ubuntu/jammy64"
    db.vm.network "private_network", ip: "192.168.56.11"
    db.vm.provider "virtualbox" do |vb|
      vb.memory = "2048"
    end
  end
end
# @vagrant.json
{
  "web": {
    "role": "webserver",
    "db_host": "192.168.56.11"
  },
  "db": {
    "role": "database"
  }
}
# deploy.py
from pyinfra import host
from pyinfra.operations import apt, files

if host.data.role == "webserver":
    apt.packages(
        name="Install nginx",
        packages=["nginx", "php-fpm", "php-pgsql"],
        _sudo=True,
    )
    
    files.template(
        name="Configure app",
        src="templates/app.conf.j2",
        dest="/etc/app/config.ini",
        db_host=host.data.db_host,
        _sudo=True,
    )

elif host.data.role == "database":
    apt.packages(
        name="Install postgresql",
        packages=["postgresql", "postgresql-contrib"],
        _sudo=True,
    )

Load Balanced Web Cluster

# Vagrantfile
Vagrant.configure("2") do |config|
  # Load balancer
  config.vm.define "lb" do |lb|
    lb.vm.box = "ubuntu/jammy64"
    lb.vm.network "private_network", ip: "192.168.56.10"
  end
  
  # Web servers
  (1..3).each do |i|
    config.vm.define "web#{i}" do |web|
      web.vm.box = "ubuntu/jammy64"
      web.vm.network "private_network", ip: "192.168.56.#{10 + i}"
    end
  end
end
# deploy.py
from pyinfra import host
from pyinfra.operations import apt, files

if host.name == "lb":
    # Configure load balancer
    apt.packages(
        name="Install nginx",
        packages=["nginx"],
        _sudo=True,
    )
    
    files.template(
        name="Configure load balancer",
        src="templates/nginx-lb.conf.j2",
        dest="/etc/nginx/nginx.conf",
        backends=[
            "192.168.56.11:80",
            "192.168.56.12:80",
            "192.168.56.13:80",
        ],
        _sudo=True,
    )

elif host.name.startswith("web"):
    # Configure web server
    apt.packages(
        name="Install nginx",
        packages=["nginx"],
        _sudo=True,
    )

Inventory API

Use in Python API:
from pyinfra import Inventory, Config, State

# Vagrant connector generates hosts automatically
inventory = Inventory(
    (["@vagrant"], {})
)

# Or specific VMs
inventory = Inventory(
    ([
        "@vagrant/web",
        "@vagrant/database",
    ], {})
)

config = Config(SUDO=True)
state = State(inventory, config)
state.init(inventory, config)

Checking VM Status

# Check which VMs are running
vagrant status

# Check global VM status
vagrant global-status

# SSH config for specific VM
vagrant ssh-config web

Limitations

  • Only detects VMs in “running” state
  • Must run from directory containing Vagrantfile
  • Slower than SSH connector (queries Vagrant for each VM)
  • Requires Vagrant in PATH

Troubleshooting

No VMs Found

# Check VMs are running
vagrant status

# Start VMs
vagrant up

# Verify pyinfra can see them
pyinfra @vagrant --facts server.Hostname

Connection Errors

# Check SSH config
vagrant ssh-config

# Try SSHing manually
vagrant ssh web

# Restart VMs
vagrant halt
vagrant up

Slow Performance

The Vagrant connector queries vagrant ssh-config for each VM, which can be slow:
# Time the connector
time pyinfra @vagrant --facts server.Hostname

# For better performance with many VMs, consider using SSH connector
# with static inventory generated from Vagrant

Comparison with SSH Connector

Feature@vagrant@ssh
Auto-discoveryYesNo
Dynamic configYesNo
SpeedSlowerFaster
SetupAutomaticManual
Use caseDev/testingProduction

Source Reference

Location: src/pyinfra/connectors/vagrant.py:90

Key Methods

  • make_names_data() - Generate hosts from Vagrant
  • get_vagrant_config() - Read Vagrant SSH config (line 30)
  • get_vagrant_options() - Read @vagrant.json (line 83)

Build docs developers (and LLMs) love