The first time you deploy a new application, you probably do it manually in the terminal. This is great for learning and prototyping, but soon you will want to automate this process. The faster your deployment process is, the more likely you are to iterate quickly and make progress.
Unfortunately, there is a huge gap in complexity between manual terminal commands and automation tools like Ansible. Instead, consider scripting your deploy process with Fabric, a simple yet powerful Python library for automating SSH sessions. You can level up to Ansible if you ever actually need it.
Why Fabric
You can do nearly anything you can imagine using Bash scripts, but Bash isn't very comfortable to work in. Fabric powers up your Python scripts by making it easy to do common tasks on a remote machine:
- run commands
- act as a superuser
- transfer files
Because you have the full power of Python as well, it's easy to handle errors, perform actions conditionally, and pull in data from your filesystem or the Internet.
Deploying a Django app with Fabric
Here I'll show a quick example of a Fabric-powered script that deploys a Django application.
This script is wrapped in a Django management command so you run it with ./manage.py deploy
and it has access to the application settings, etc.
# django_app/management/commands/deploy.py
import getpass
from django.core.management.base import BaseCommand
from fabric import Connection, Config
class Command(BaseCommand):
help = "Deploy the application."
# This is a path on the remote server, not your local machine
MANAGE_PY = "/path/to/virtualenv/bin/python manage.py"
def add_arguments(self, parser):
pass
def handle(self, *args, **options):
# Get the sudo password interactively.
# This can be automated if you have a safe way to store the password.
sudo_pass = getpass.getpass("Enter sudo password for the remote user:\n")
config = Config(overrides={"sudo": {"password": sudo_pass}})
# Open an SSH session
with Connection("yourhost.com", config=config) as c:
# Set the working directory
with c.cd("path/to/project"):
# Fetch latest code using a read-only git account
c.run("git pull")
# These are Django-specific commands to verify config,
# run any database migrations, and prepare static files
c.run(f"{self.MANAGE_PY} check --deploy --fail-level=WARNING")
c.run(f"{self.MANAGE_PY} migrate")
c.run(f"{self.MANAGE_PY} collectstatic --no-input")
# Since systemd manages my web server, I use it to roll out
# workers running the new application version
c.sudo("systemctl reload myproject.service")
print("\nOK!\n")
The convenience of a scripted deploy versus manual SSH makes a huge difference in developer ergonomics and reduces the chance for errors. If any command in the script fails, Fabric raises an exception, halts the script, and dumps a stack trace.