Automating Django Deployments: A Reusable Shell Script

Managing multiple Django projects on a single server can quickly become complex, especially when coordinating code pulls, migrations, static file collection, and service restarts (like uWSGI, Celery, and Apache/Nginx). Manual execution is error-prone and time-consuming.

The solution is a robust, reusable shell script that handles both full deployments and simple service restarts, making your workflow predictable and smooth.

This script was developed during a troubleshooting session for the makeitexist project to standardize operations across five different Django websites hosted on a single Linode VPS.

The Goal: A Single Source of Truth

We aimed to create one script capable of managing all projects on the server, handling two main actions:

  1. deploy: Pull code, install dependencies, run migrations, collect static files, and restart services.
  2. restart: Just restart all associated system services (web server, worker, beat).

The script was designed to accommodate specific naming conventions, such as using the first letter of the project name for the virtual environment directory (e.g., m_env for makeitexist).

The Final Script: project_manager.sh

This script uses standard Unix utilities, functions, and conditional logic (case statements and if checks) for maximum flexibility.

Save the following code as /var/www/project_manager.sh on your server:

#!/bin/bash

# Define global variables based on arguments $1 (project name) and $2 (action)
PROJECT_NAME=$1
ACTION=$2

# Configuration Variables
PROJECT_DIR="/var/www/$PROJECT_NAME"
# Dynamically determine the VENV name (e.g., m_env for makeitexist)
FIRST_LETTER=$(echo "$PROJECT_NAME" | cut -c 1)
VENV_DIR="${FIRST_LETTER}_env" 

# Service names for uWSGI, Apache, and Celery (must match systemd config)
UWSGI_SERVICE_NAME="${PROJECT_NAME}.service"
APACHE_SERVICE_NAME="apache2.service"
CELERY_WORKER_SERVICE="/etc/systemd/system/${PROJECT_NAME}_celery_worker.service"
CELERY_BEAT_SERVICE="/etc/systemd/system/${PROJECT_NAME}_celery_beat.service"

# --- Functions ---

# Function to handle all service restarts
restart_services() {
    echo "--- Restarting services for $PROJECT_NAME ---"
    sudo systemctl daemon-reload

    # Restart uWSGI and Apache (these are always required)
    sudo systemctl restart $UWSGI_SERVICE_NAME 
    sudo systemctl restart $APACHE_SERVICE_NAME 

    # Conditionally restart Celery services if their files exist
    if [ -f "$CELERY_WORKER_SERVICE" ]; then
        echo "Restarting Celery Worker..."
        sudo systemctl restart $(basename $CELERY_WORKER_SERVICE) # Use basename for systemctl command
    else
        echo "Celery Worker service file not found. Skipping restart."
    fi

    if [ -f "$CELERY_BEAT_SERVICE" ]; then
        echo "Restarting Celery Beat..."
        sudo systemctl restart $(basename $CELERY_BEAT_SERVICE)
    else
        echo "Celery Beat service file not found. Skipping restart."
    fi

    echo "--- Restart complete ---"
}

# Function to handle the full deployment process
deploy_project() {
    echo "--- Starting full deployment for $PROJECT_NAME ---"
    # Navigate, pull code, install deps
    cd $PROJECT_DIR || { echo "Error: Project directory not found at $PROJECT_DIR"; exit 1; }
    git pull origin master 
    source $VENV_DIR/bin/activate
    pip install -r requirements.txt
    python manage.py migrate --noinput 
    python manage.py collectstatic --noinput
    deactivate
    restart_services # Calls the conditional restart function
    echo "--- Deployment finished successfully for $PROJECT_NAME ---"
    echo "NOTE: Remember to manually run loaddata if needed for initial fixtures."
}

# --- Main Logic (Argument Parsing) ---

if [ "$#" -ne 2 ]; then
    echo "Usage: $0 <project_name> <deploy|restart>"
    echo "Example (Full Deploy): $0 makeitexist deploy"
    echo "Example (Just Restart): $0 makeitexist restart"
    exit 1
fi

case "$ACTION" in
    deploy)
        deploy_project
        ;;
    restart)
        restart_services
        ;;
    *)
        echo "Invalid action: $ACTION. Use 'deploy' or 'restart'."
        exit 1
        ;;

esac

How to Use the Script

  1. Save the file: Place it in a shared directory like /var/www/project_manager.sh.
  2. Make it executable: bash chmod +x /var/www/project_manager.sh
  3. Ensure sudoers is configured: Edit /etc/sudoers to allow your deployment user to run the systemctl restart commands without a password.
  4. Run a Full Deployment: bash /var/www/project_manager.sh makeitexist deploy
  5. Run a Simple Restart: bash /var/www/project_manager.sh makeitexist restart

This script provides a professional, repeatable, and robust way to manage your Django projects, turning a tedious manual process into a single, reliable command.