Automate Django Deployments with Fabric (Fabfile)

Blog / Server Management · June 4, 2017 · Updated June 10, 2026 · 7 min read
Automate Django Deployments with Fabric (Fabfile)

Fabric is a Python library and command-line tool that runs shell commands on remote servers over SSH — making it a clean way to script repeatable Django deployments (git pull, migrate, collectstatic, restart Gunicorn) from a single fabfile.py. In 2026, Fabric is still an excellent fit for straightforward SSH task automation, but be honest about scope: for full, idempotent server configuration most teams reach for Ansible, and for build/test/deploy pipelines they use CI/CD (GitHub Actions, GitLab CI) with containers. Use Fabric where it shines — scripted, on-demand remote tasks — and pair it with the right tool for everything else.

Key takeaways

  • Fabric ≠ what it was. Fabric 1.x and Fabric 2.x/3.x are a breaking rewrite. Old from fabric.api import run, env, cd, sudo code no longer works — modern Fabric uses an object-oriented Connection API built on Invoke + Paramiko.
  • Modern tasks take a context. Write @task functions as def deploy(c): and call c.run(), c.sudo(), with c.cd(path):, c.put(), c.get().
  • Hosts are explicit. No more global env.hosts. Pass hosts on the CLI (fab -H deploy@server deploy) or build a Connection('user@host') yourself.
  • A Django deploy task is just: pull code, install requirements in the venv, migrate, collectstatic, restart the systemd service.
  • Pick the right tool. Fabric for scripted SSH; Ansible for idempotent config management; Docker + CI/CD for repeatable pipelines.

What is Fabric, and how does it work in 2026?

Fabric is a high-level Python library for executing shell commands remotely over SSH. Under the hood it combines two libraries: Invoke (task execution and local command running) and Paramiko (the SSH layer). You define tasks in a fabfile.py, then run them with the fab command-line tool.

The current release line is Fabric 3.x (pip install fabric), which runs on Python 3.8+. Crucially, it is a complete redesign of the abandoned Fabric 1.x series — if you are copying a deployment script from an old tutorial, it almost certainly will not run on a modern install.

How is Fabric 2.x/3.x different from the old Fabric 1.x API?

Fabric 1.x relied on a magic global env object and a flat fabric.api namespace. Modern Fabric is explicit and object-oriented. Here is the direct mapping of the most common patterns:

Fabric 1.x (legacy, end-of-life) Fabric 2.x / 3.x (current)
from fabric.api import run, sudo, cd, env from fabric import Connection, task
Global env.hosts, env.user, env.key_filename Explicit Connection('user@host', connect_kwargs={...})
Bare task def deploy(): Context-taking task def deploy(c):
run('cmd') / sudo('cmd') (implicit host) c.run('cmd') / c.sudo('cmd')
with cd('/path'): with c.cd('/path'):
top-level put() / get() c.put() / c.get()
@roles, env.roledefs SerialGroup / ThreadingGroup
run('python manage.py syncdb') syncdb removed since Django 1.9 — use migrate
Python 2 only Python 3.8+ (built on Invoke + Paramiko)

How do I install Fabric?

Install from PyPI into your project's virtualenv. Ignore older guides that mention easy_install, apt-get install fabric, or "requires Python 2.5/2.6" — those refer to the dead 1.x line.

# Fabric 3.x runs on Python 3.8+ (Python 2 support was dropped long ago)
python -m pip install fabric

# Confirm the install and the CLI entry point
fab --version
# Fabric 3.x.x
# Paramiko 3.x.x
# Invoke 2.x.x

How do I write my first fabfile.py?

By default the fab tool looks for a file named fabfile.py in the current directory. Each task is a plain function decorated with @task that receives a context object (conventionally c). When you supply a host with -H, that context is a live SSH Connection, so c.run() executes on the remote machine, while c.local() always runs on your own machine.

# fabfile.py
from fabric import task


@task
def uptime(c):
    # c is a Connection when you pass -H; c.run() executes on the REMOTE host
    c.run("uptime")


@task
def whereami(c):
    # c.local() always runs on YOUR machine
    c.local("hostname")

List and run the tasks:

# See every task in the fabfile
fab --list

# Run a task against a host (user@host shorthand)
fab -H deploy@your-server.com uptime

How do I connect to a remote server?

Instead of the old global env variables, modern Fabric uses an explicit Connection object. You can pass user@host:port shorthand on the CLI, or construct a Connection in code when you need finer control (SSH key path, timeouts, jump hosts).

from fabric import Connection

# Explicit connection with a specific SSH key
c = Connection(
    host="your-server.com",
    user="deploy",
    connect_kwargs={"key_filename": "/home/me/.ssh/id_ed25519"},
)

result = c.run("uname -s", hide=True)
print(result.stdout.strip())   # -> 'Linux'
print(result.ok)               # -> True

# Or supply the host on the command line instead of hard-coding it:
#   fab -i ~/.ssh/id_ed25519 -H deploy@your-server.com uptime

How do I write a Django deployment task with Fabric?

This is the core use case: one deploy task that pulls the latest code, updates dependencies inside the virtualenv, runs migrations, collects static files, and restarts the app server. Use with c.cd(path): to run commands inside the project directory, and c.sudo() for the systemd restarts that need root.

# fabfile.py
from fabric import task

# --- Settings for the target server ---
PROJECT_DIR = "/srv/myproject"
VENV = "/srv/myproject/.venv"
BRANCH = "main"


@task
def deploy(c):
    """Pull code, install deps, migrate, collectstatic, restart services."""
    with c.cd(PROJECT_DIR):
        # 1. Pull the latest code
        c.run(f"git pull origin {BRANCH}")

        # 2. Install/update Python dependencies inside the virtualenv
        c.run(f"{VENV}/bin/pip install -r requirements.txt")

        # 3. Apply database migrations (syncdb is long gone)
        c.run(f"{VENV}/bin/python manage.py migrate --noinput")

        # 4. Collect static files for nginx to serve
        c.run(f"{VENV}/bin/python manage.py collectstatic --noinput")

    # 5. Restart Gunicorn and reload nginx (needs root)
    c.sudo("systemctl restart gunicorn")
    c.sudo("systemctl reload nginx")
    print("Deployment complete.")

Run the whole deployment in one command:

# --prompt-for-sudo-password if your deploy user needs a sudo password
fab --prompt-for-sudo-password -H deploy@your-server.com deploy

How do I deploy to multiple servers at once?

For a fleet of web servers, use SerialGroup (one host at a time) or ThreadingGroup (all hosts in parallel). Both mirror the Connection API — run, sudo, put, get — and return a GroupResult. This replaces the old @roles / env.roledefs mechanism.

# fabfile.py
from fabric import task, ThreadingGroup


@task
def restart_all(c):
    # Restart Gunicorn across the whole web tier concurrently
    pool = ThreadingGroup(
        "deploy@web1.example.com",
        "deploy@web2.example.com",
    )
    pool.sudo("systemctl restart gunicorn")


# Or skip hard-coded hosts entirely and fan out from the CLI;
# the task runs once per host:
#   fab -H web1.example.com,web2.example.com deploy

When should I use Fabric vs Ansible vs CI/CD?

Fabric is deliberately small: it runs commands over SSH and gets out of your way. That makes it perfect for ad-hoc operations and simple, scripted deploys — but it is not idempotent (running a task twice does whatever you scripted, twice) and it is not a configuration-management system. Choose the tool that matches the job:

Approach Best for Idempotent? Agent needed Notes
Plain shell / SSH scripts One-off commands, tiny setups No No Fastest to start, hardest to maintain
Fabric (Python) Scripted SSH tasks, app deploys, ad-hoc ops No (you script it) No Great when your team already writes Python
Ansible Full server/config management, provisioning Yes No (agentless) Declarative YAML playbooks
CI/CD (GitHub Actions, GitLab CI) Build → test → deploy pipelines, containers Pipeline-defined Runner Pairs naturally with Docker

A common, healthy setup: provision and configure servers with Ansible, package the app with Docker, serve it behind nginx with uWSGI/Gunicorn, and keep a tiny Fabric fabfile.py for the handful of manual operations (one-off migrations, cache clears, log pulls) that don't belong in a pipeline.

Frequently Asked Questions

Does old Fabric 1.x code still work in 2026?

No. from fabric.api import run, env, cd, sudo, env.hosts, @roles, and @hosts were all removed in the Fabric 2.x rewrite and remain gone in 3.x. If you install modern Fabric (pip install fabric) you must use the Connection/@task object API shown above. The legacy 1.x line is end-of-life and unmaintained.

Is Fabric still maintained and worth using in 2026?

Yes. Fabric 3.x is actively maintained and built on the well-supported Invoke and Paramiko libraries. It remains a great choice for scripted SSH automation and simple Django deploys. For idempotent configuration management or full build pipelines, complement it with Ansible or a CI/CD system rather than stretching Fabric to do everything.

How do I run a Fabric task on a specific host?

Pass the host with -H: fab -H user@host taskname. You can target multiple hosts with a comma-separated list (fab -H web1,web2 deploy) and choose an SSH key with -i (fab -i ~/.ssh/key -H user@host deploy). Alternatively, build a Connection('user@host') inside your code for full control.

How do I run sudo commands like restarting Gunicorn?

Use c.sudo('systemctl restart gunicorn') inside a task. If the deploy user needs a sudo password, supply it interactively with fab --prompt-for-sudo-password ..., or set sudo.password via a Config object or your fabric.yml config file rather than hard-coding it.

How do I copy files to and from the server with Fabric?

Use c.put(local_path, remote=remote_path) to upload and c.get(remote_path, local=local_path) to download. These replace the top-level put() / get() from Fabric 1.x and run over the same SSH connection, so there is no separate SFTP setup.

Should I use Fabric or Ansible for Django deployment?

Use Fabric when your team is comfortable in Python and you want a small, explicit, scripted deploy. Use Ansible when you need idempotent, repeatable server configuration across many machines. Many teams use both, and lean on our server maintenance services to design and run the right deployment workflow for their stack.

Share this article