PyDeployer is a lightweight, modular deployment automation tool designed specifically for Django applications on Ubuntu LTS systems. It uses a separation-of-concerns architecture where a Python orchestrator coordinates focused shell and Python scripts to handle different deployment aspects.
- Lightweight Orchestrator:
deploy.pycoordinates deployment workflow - Focused Scripts: Each script in
scripts/handles one specific deployment aspect - Environment Variables: Configuration and data passing between orchestrator and scripts
- Modular Design: Testable, maintainable, and reusable components
- Branch-Only Deployment: Deploy any branch without environment parameters
- Fallback Configuration:
deploy-{branch}.yml→deploy.ymlautomatic fallback - Domain Configuration: Flexible domain management with service-level overrides
- Multi-Site Support: Serve multiple sites from same codebase with different domains
- Service-Level Overrides: Per-service environment variables and domain configuration
- Modular Script Architecture: Separate scripts for each deployment concern
- Automated Environment Setup: Python virtual environments with version management
- Dependency Management: System and Python dependencies from YAML configuration
- Database Integration: PostgreSQL setup with user permissions and validation
- Process Management: Dynamic Supervisor configuration generation and installation
- Nginx Reverse Proxy: Automatic nginx configuration with port 80 access for all services
- Port Allocation: Intelligent port assignment (8000, 8001, 8002...) for conflicting services
- Git Integration: Repository cloning with branch support and SSH keys
- Comprehensive Validation: Multi-level deployment validation and health checks
- Hook System: Pre/post-deploy hooks supporting both commands and scripts
- Error Handling: Detailed logging with color-coded output and proper exit codes
install-system-dependencies.sh- Ubuntu system package installationsetup-python-environment.sh- Python virtual environment with version managementclone-repository.sh- Git repository cloning with branch and SSH supportinstall-python-dependencies.sh- Python packages and requirements installationgenerate-supervisor-configs.py- Dynamic Supervisor configuration generationinstall-supervisor-configs.sh- System Supervisor configuration installationgenerate-nginx-configs.py- Nginx reverse proxy configuration generationinstall-nginx-configs.sh- System nginx configuration installation and domain setupvalidate-deployment.sh- Complete deployment validation
verify-postgresql-database.sh- PostgreSQL database setup and verificationvalidate-django-environment.sh- Django environment and configuration validationcreate-django-superuser.sh- Django admin user creation with validation
/srv/deployments/{project}/{normalized-branch}/
├── code/ # Application source code (cloned repository)
├── config/ # Generated configuration files
│ ├── supervisor/ # Supervisor service configurations
│ └── nginx/ # Nginx reverse proxy configurations
├── logs/ # Application and service logs
│ ├── supervisor/ # Supervisor process logs
│ └── app/ # Application logs
├── static/ # Django static files (if applicable)
├── media/ # Django media files (if applicable)
└── venv/ # Python virtual environment
Branch Normalization: Branch names with slashes are normalized (e.g., feature/auth → feature-auth)
- Ubuntu LTS Server (18.04, 20.04, 22.04, or later)
- Python 3.8+ (Python 3.12 recommended)
- Git for repository operations
- sudo privileges for system package installation
- Clone the repository:
git clone git@github.com:unomena/deployment-tool.git
cd deployment-tool- Install system dependencies (PostgreSQL, Redis, Supervisor, Nginx):
sudo ./install
# OR using Makefile
make system-install- Install Python dependencies:
make build- Ensure scripts are executable (should already be set):
chmod +x scripts/*.sh scripts/*.pyThe deployment tool uses a simplified 2-parameter interface:
./deploy <repository_url> <branch_or_sha>The deployment tool uses a fallback configuration system:
- Branch-specific config:
deploy-{normalized-branch}.yml(e.g.,deploy-dev.yml,deploy-feature-auth.yml) - Default fallback:
deploy.yml(used when branch-specific config doesn't exist)
Examples:
deploy-main.ymlfor main branch deploymentsdeploy-dev.ymlfor dev branch deploymentsdeploy-feature-auth.ymlfor feature/auth branch deploymentsdeploy.ymlas fallback for any branch without specific config
Deploy directly from any repository:
# Deploy main branch
./deploy https://github.com/myorg/myapp.git main
# Deploy feature branch (creates feature-new-ui deployment)
./deploy git@github.com:myorg/myapp.git feature/new-ui
# Deploy specific version
./deploy https://github.com/myorg/myapp.git v1.2.3
# Deploy qa branch
./deploy git@github.com:myorg/myapp.git qa# Deploy with Makefile
make deploy REPO_URL=https://github.com/myorg/myapp.git BRANCH=mainFor development of the deployment tool itself:
chmod +x deploy.pyThe deployment script reads YAML configuration files with support for domain configuration and service-level overrides:
name: sample-app # Project name
repo: git@github.com:user/repo.git # Git repository (optional)
python_version: "3.12" # Python version
domain: "myapp.local" # Default domain for all web services
dependencies:
system: # Ubuntu packages
- postgresql-client
- libpq-dev
- python3-dev
- build-essential
python: # Python packages
- postgresql
- redis
- celery
- gunicorn
python-requirements: # Requirements files
- requirements.txt
env_vars: # Root environment variables (inherited by all services)
DJANGO_SETTINGS_MODULE: project.settings_dev
DEBUG: "0"
SECRET_KEY: "your-secret-key"
DB_HOST: "localhost"
DB_NAME: "${PROJECT_NAME}-${NORMALIZED_BRANCH}" # Note: hyphens, not underscores
database: # Database configuration
type: postgresql
name: ${DB_NAME}
user: ${DB_USER}
password: ${DB_PASSWORD}
host: ${DB_HOST}
port: ${DB_PORT}
services: # Services to run
- name: web
type: gunicorn
command: "gunicorn project.wsgi:application"
workers: 3
port: 8000
# Uses default domain: myapp.local
# Uses all root env_vars
- name: admin
type: gunicorn
command: "gunicorn project.wsgi:application"
workers: 2
port: 8001
domain: "admin.myapp.local" # Service-specific domain override
env_vars: # Service-specific environment variables
DJANGO_SETTINGS_MODULE: "project.settings.admin" # Override root setting
DEBUG: "True" # Override root setting
ADMIN_ONLY: "True" # New variable specific to this service
# All other root env_vars are still inherited
- name: worker
type: celery
command: "celery -A project worker -l info"
workers: 4
# No domain (not a web service)
# Uses all root env_vars as-isIf no domain is specified in the configuration, the default pattern is:
{project-name}-{normalized-branch}
Examples:
sample-app+main→sample-app-maingood-times-unomena+feature/auth→good-times-unomena-feature-auth
Set a default domain for all web services:
domain: "myapp.local" # All web services use this domain by defaultOverride domain for specific services:
services:
- name: web
domain: "www.myapp.local" # Override for this service only
- name: admin
domain: "admin.myapp.local" # Different domain for adminServe completely different sites from the same codebase:
domain: "myapp.local" # Default domain
services:
- name: web
# Uses default domain: myapp.local
- name: admin
domain: "admin.myapp.local"
env_vars:
DJANGO_SETTINGS_MODULE: "myapp.settings.admin"
SITE_THEME: "admin"
- name: api
domain: "api.myapp.local"
env_vars:
DJANGO_SETTINGS_MODULE: "myapp.settings.api"
API_VERSION: "v2"The deployment tool automatically generates and installs nginx reverse proxy configurations for all web services, providing seamless port 80 access.
- Port 80 Access: All services accessible via standard HTTP port
- Domain-Based Routing: Each service gets its own domain/subdomain
- Intelligent Port Allocation: Automatic port assignment (8000, 8001, 8002...) for conflicting services
- Automatic Installation: Nginx configs generated and enabled during deployment
- Domain Management: Automatic
/etc/hostsentries for local testing
config/nginx/{domain}.conf- Individual site configurationsconfig/nginx/README.md- Deployment instructions and URLs
- Upstream configuration based on allocated service ports
- SSL-ready configurations (commented out by default)
- Static/media file serving for Django applications
- Security headers and performance optimizations
- Health check endpoints (
/nginx-health)
After deployment, services are accessible via:
- Main service:
http://{project-name}-{branch}/→ Django on port 8000 - Additional services:
http://{custom-domain}/→ Django on allocated ports - Example:
http://sampleapp-dev/,http://goodtimes.local/,http://admin.goodtimes.local/
# Check nginx status
make nginx-status
# Test nginx configuration
make nginx-test
# Reload nginx after manual changes
make nginx-reload# Basic deployment
./deploy <repository_url> <branch>
# Examples
./deploy https://github.com/myorg/myapp.git main
./deploy git@github.com:myorg/myapp.git feature/auth# Deploy using Makefile
make deploy REPO_URL=<url> BRANCH=<branch>
# Quick shortcuts
make deploy-main REPO_URL=<url>
make deploy-dev REPO_URL=<url>
# Configuration validation
make validate-config PROJECT=<project> BRANCH=<branch>
make show-config PROJECT=<project> BRANCH=<branch>- Reads Configuration: Parses YAML config and validates required fields
- Creates Directory Structure: Sets up organized folder hierarchy
- Installs System Dependencies: Uses
apt-getto install Ubuntu packages - Sets Up Python Environment: Creates virtual environment with specified Python version
- Clones Repository: Downloads code from Git repository (if specified)
- Installs Python Dependencies: Installs packages and requirements in virtual environment
- Generates Supervisor Configs: Creates service configurations for each service
- Installs Supervisor Configs: Copies configurations to system and reloads Supervisor
- Validates Deployment: Runs comprehensive checks to ensure everything is working
For each service, the script generates a Supervisor configuration like:
[program:sample-app-main-web]
command=/srv/deployments/sample-app/main/venv/bin/gunicorn project.wsgi:application
directory=/srv/deployments/sample-app/main/code
user=www-data
autostart=true
autorestart=true
startsecs=10
startretries=3
stdout_logfile=/srv/deployments/sample-app/main/logs/supervisor/web.log
stderr_logfile=/srv/deployments/sample-app/main/logs/supervisor/web_error.log
environment=DJANGO_SETTINGS_MODULE=project.settings_dev,DEBUG=0,SECRET_KEY=your-secret-key
numprocs=3
process_name=%(program_name)s_%(process_num)02d- Ubuntu LTS system
- Python 3.8+ available on system
sudoaccess for installing packages and Supervisor configs- Git (if using repository cloning)
- Supervisor installed (
sudo apt-get install supervisor)
The script performs these validation checks:
- ✓ Directory structure exists
- ✓ Virtual environment is functional
- ✓ Python dependencies are installed
- ✓ Supervisor configurations are in place
sudo apt-get update
sudo apt-get install supervisor
sudo systemctl enable supervisor
sudo systemctl start supervisorsudo supervisorctl statussudo supervisorctl restart sample-app-main-web:*
sudo supervisorctl restart sample-app-main-worker:*Make sure to run the script with sudo for system-level operations:
sudo python3 deploy.py config.ymlThe script automatically installs Python versions using the deadsnakes PPA:
# This is done automatically by the script
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get install python3.12 python3.12-venvCheck Supervisor logs:
sudo supervisorctl tail sample-app-main-web stderr
sudo tail -f /srv/deployments/sample-app/main/logs/supervisor/web_error.log- The script requires
sudoaccess for system operations - Services run as
www-datauser for security - Environment variables are passed securely to processes
- Logs are stored in project-specific directories
This tool is designed for internal deployment automation. Modify as needed for your specific requirements.