Fabric (Python) Cheat Sheet
Overview
Fabric is a Python library and command-line tool for streamlining SSH-based application deployment and system administration tasks. It provides a Pythonic interface for executing shell commands remotely, transferring files, and managing connections to multiple servers. Fabric 2.x is built on top of Invoke (for local tasks) and Paramiko (for SSH).
Fabric replaces shell scripts with Python functions that can run commands locally and remotely, handle file transfers, and manage complex deployment workflows. It is widely used for automating deployments, server provisioning, and operational tasks across fleets of servers.
Installation
pip install fabric
# With additional SSH features
pip install fabric[pytest]
# Verify
fab --version
python -c "import fabric; print(fabric.__version__)"
Core Usage
Basic fabfile.py
from fabric import Connection, task
@task
def deploy(c):
"""Deploy the application to production."""
conn = Connection('web1.example.com')
with conn.cd('/opt/myapp'):
conn.run('git pull origin main')
conn.run('pip install -r requirements.txt')
conn.run('python manage.py migrate')
conn.run('sudo systemctl restart myapp')
@task
def check(c):
"""Check server status."""
conn = Connection('web1.example.com')
conn.run('uptime')
conn.run('df -h')
conn.run('free -m')
@task
def logs(c):
"""View application logs."""
conn = Connection('web1.example.com')
conn.run('tail -100 /var/log/myapp/app.log')
# Run tasks
fab deploy
fab check
fab logs
Connection Management
from fabric import Connection
# Basic connection
conn = Connection('web1.example.com')
# With username
conn = Connection('deploy@web1.example.com')
# With specific port and key
conn = Connection(
host='web1.example.com',
user='deploy',
port=2222,
connect_kwargs={
'key_filename': '/path/to/key.pem',
}
)
# Using SSH config
conn = Connection('web1') # Uses ~/.ssh/config
# With password (use keys instead when possible)
conn = Connection(
host='web1.example.com',
user='deploy',
connect_kwargs={'password': 'secret'}
)
Commands and Operations
Running Commands
from fabric import Connection, task
@task
def server_info(c):
conn = Connection('web1.example.com')
# Run command and capture output
result = conn.run('uname -a', hide=True)
print(f"OS: {result.stdout.strip()}")
# Check return code
result = conn.run('test -f /etc/myapp.conf', warn=True)
if result.ok:
print("Config file exists")
else:
print("Config file missing")
# Run with sudo
conn.sudo('systemctl restart nginx')
conn.sudo('apt update && apt upgrade -y')
# Run with environment variables
conn.run('echo $APP_ENV', env={'APP_ENV': 'production'})
# Change directory
with conn.cd('/opt/myapp'):
conn.run('git status')
conn.run('pip install -r requirements.txt')
File Transfers
@task
def upload_config(c):
conn = Connection('web1.example.com')
# Upload file
conn.put('local/config.yaml', '/etc/myapp/config.yaml')
# Download file
conn.get('/var/log/myapp/app.log', 'local/app.log')
# Upload with sudo (upload to temp, then move)
conn.put('nginx.conf', '/tmp/nginx.conf')
conn.sudo('mv /tmp/nginx.conf /etc/nginx/nginx.conf')
conn.sudo('systemctl reload nginx')
Local Commands
from invoke import task
@task
def build(c):
"""Build locally before deploying."""
c.run('npm run build')
c.run('docker build -t myapp:latest .')
c.run('docker push myregistry/myapp:latest')
Configuration
Multi-Server Deployment
from fabric import Connection, SerialGroup, ThreadingGroup, task
WEB_SERVERS = [
'web1.example.com',
'web2.example.com',
'web3.example.com',
]
@task
def deploy(c):
"""Deploy to all web servers."""
group = SerialGroup(*WEB_SERVERS, user='deploy')
group.run('cd /opt/myapp && git pull')
group.run('cd /opt/myapp && pip install -r requirements.txt')
group.run('sudo systemctl restart myapp')
@task
def deploy_parallel(c):
"""Deploy to all servers in parallel."""
group = ThreadingGroup(*WEB_SERVERS, user='deploy')
group.run('cd /opt/myapp && git pull')
@task
def rolling_deploy(c):
"""Rolling deploy one server at a time."""
for host in WEB_SERVERS:
conn = Connection(host, user='deploy')
conn.sudo('systemctl stop myapp')
with conn.cd('/opt/myapp'):
conn.run('git pull origin main')
conn.run('pip install -r requirements.txt')
conn.sudo('systemctl start myapp')
# Wait for health check
conn.run('sleep 10 && curl -f http://localhost:8080/health')
print(f"Deployed to {host}")
SSH Config Integration
from fabric import Config, Connection
# Use SSH config file
config = Config(ssh_config=True)
conn = Connection('myserver', config=config)
# Custom fabric config
config = Config(overrides={
'run': {
'echo': True,
'warn': True,
},
'sudo': {
'password': 'mypassword',
},
'connect_kwargs': {
'key_filename': '/path/to/key.pem',
},
})
Advanced Usage
Complete Deployment Pipeline
from fabric import Connection, task
from invoke import Exit
import datetime
@task
def deploy(c, branch='main', skip_tests=False):
"""Full deployment pipeline."""
conn = Connection('web1.example.com', user='deploy')
if not skip_tests:
print("Running tests locally...")
result = c.run('pytest tests/', warn=True)
if not result.ok:
raise Exit("Tests failed! Aborting deploy.")
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
with conn.cd('/opt/myapp'):
# Create backup
conn.run(f'cp -r current backup_{timestamp}')
# Pull latest code
conn.run(f'git fetch && git checkout {branch} && git pull')
# Install dependencies
conn.run('pip install -r requirements.txt')
# Run migrations
conn.run('python manage.py migrate --noinput')
# Collect static files
conn.run('python manage.py collectstatic --noinput')
# Restart application
conn.sudo('systemctl restart myapp')
# Verify health
result = conn.run('curl -sf http://localhost:8080/health', warn=True)
if not result.ok:
print("Health check failed! Rolling back...")
conn.run(f'cp -r backup_{timestamp} current')
conn.sudo('systemctl restart myapp')
raise Exit("Deploy failed and was rolled back.")
print(f"Deploy of {branch} complete!")
@task
def rollback(c, backup_name=None):
"""Rollback to a previous version."""
conn = Connection('web1.example.com', user='deploy')
if backup_name is None:
result = conn.run('ls -t /opt/myapp/backup_* | head -1', hide=True)
backup_name = result.stdout.strip()
with conn.cd('/opt/myapp'):
conn.run(f'cp -r {backup_name} current')
conn.sudo('systemctl restart myapp')
print(f"Rolled back to {backup_name}")
Watchers for Interactive Prompts
from invoke import Responder
@task
def setup_db(c):
conn = Connection('db.example.com')
sudopass = Responder(
pattern=r'\[sudo\] password:',
response='mypassword\n',
)
conn.run('sudo createdb myapp', watchers=[sudopass])
Troubleshooting
| Issue | Solution |
|---|---|
| SSH connection refused | Check SSH key, port, and firewall rules |
| Permission denied | Verify user permissions; use sudo() |
| Command hangs | Set timeout parameter; check for interactive prompts |
| File upload fails | Check remote directory permissions |
| Parallel execution errors | Use SerialGroup for debugging; check for race conditions |
fab command not found | Ensure fabric is installed; check PATH |
# Debug connection
fab -d deploy
# List available tasks
fab --list
# Run with specific host
fab -H web1.example.com deploy
# Verbose output
fab deploy --echo