Set up automated Django deployment with Git hooks, testing pipelines, and production rollbacks. Configure continuous integration with database migrations, static file management, and zero-downtime deployments.
Prerequisites
- Django application with requirements.txt
- PostgreSQL database configured
- sudo access on target server
- Git repository with Django project
What this solves
Continuous deployment automates your Django application releases, reducing manual errors and deployment time. This tutorial sets up Git hooks that automatically test, build, and deploy your Django application when code is pushed to your repository, including database migrations and static file collection.
Step-by-step configuration
Create deployment user and directories
Set up a dedicated user for deployments and create the necessary directory structure.
sudo adduser --system --group --home /home/deploy deploy
sudo mkdir -p /var/www/myapp/{releases,shared/{logs,media,static}}
sudo chown -R deploy:deploy /var/www/myapp
sudo chmod 755 /var/www/myapp
Install required packages
Install Python, Git, and other dependencies needed for Django deployment.
sudo apt update
sudo apt install -y python3 python3-pip python3-venv git nginx postgresql-client redis-tools
pip3 install virtualenv gunicorn
Initialize bare Git repository
Create a bare Git repository that will receive pushes and trigger deployments.
sudo -u deploy git init --bare /home/deploy/myapp.git
sudo -u deploy mkdir -p /home/deploy/myapp.git/hooks
sudo chmod 755 /home/deploy/myapp.git/hooks
Create deployment script
Set up the main deployment script that handles testing, building, and deploying your Django application.
#!/bin/bash
set -e
Configuration
APP_NAME="myapp"
APP_DIR="/var/www/$APP_NAME"
RELEASE_DIR="$APP_DIR/releases/$(date +%Y%m%d_%H%M%S)"
SHARED_DIR="$APP_DIR/shared"
CURRENT_DIR="$APP_DIR/current"
REPO_DIR="/home/deploy/$APP_NAME.git"
BRANCH="main"
echo "Starting deployment of $APP_NAME..."
Create release directory
mkdir -p $RELEASE_DIR
cd $RELEASE_DIR
Clone the repository
git clone $REPO_DIR .
git checkout $BRANCH
Create virtual environment
python3 -m venv venv
source venv/bin/activate
Install dependencies
pip install -r requirements.txt
Run tests
echo "Running tests..."
python manage.py test --settings=myapp.settings.test
if [ $? -ne 0 ]; then
echo "Tests failed. Aborting deployment."
rm -rf $RELEASE_DIR
exit 1
fi
Link shared directories
ln -s $SHARED_DIR/logs logs
ln -s $SHARED_DIR/media media
ln -s $SHARED_DIR/static staticfiles
Collect static files
echo "Collecting static files..."
python manage.py collectstatic --noinput --settings=myapp.settings.production
Run database migrations
echo "Running database migrations..."
python manage.py migrate --settings=myapp.settings.production
Update current symlink atomically
ln -sfn $RELEASE_DIR $CURRENT_DIR
Restart application server
sudo systemctl restart gunicorn
sudo systemctl reload nginx
Keep only last 5 releases
cd $APP_DIR/releases
ls -1t | tail -n +6 | xargs -d '\n' rm -rf --
echo "Deployment completed successfully!"
echo "Release: $(basename $RELEASE_DIR)"
Set deployment script permissions
Make the deployment script executable and set correct ownership.
sudo chown deploy:deploy /home/deploy/deploy.sh
sudo chmod 755 /home/deploy/deploy.sh
Create post-receive Git hook
Set up the Git hook that automatically triggers deployment when code is pushed.
#!/bin/bash
set -e
echo "Received push to repository. Starting deployment..."
Change to deploy user
sudo -u deploy /home/deploy/deploy.sh
echo "Deployment hook completed."
Set hook permissions
Make the Git hook executable.
sudo chown deploy:deploy /home/deploy/myapp.git/hooks/post-receive
sudo chmod 755 /home/deploy/myapp.git/hooks/post-receive
Configure Django test settings
Create a separate settings file for running tests during deployment.
from .base import *
Use SQLite for tests
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
}
Disable migrations during tests
class DisableMigrations:
def __contains__(self, item):
return True
def __getitem__(self, item):
return None
MIGRATION_MODULES = DisableMigrations()
Speed up tests
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.MD5PasswordHasher',
]
Disable logging during tests
LOGGING_CONFIG = None
Test-specific settings
DEBUG = False
TEMPLATE_DEBUG = False
Create production settings
Configure Django settings for production deployment with proper security and performance settings.
from .base import *
import os
Security settings
DEBUG = False
ALLOWED_HOSTS = ['example.com', 'www.example.com']
Database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME', 'myapp_prod'),
'USER': os.environ.get('DB_USER', 'myapp'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOST', 'localhost'),
'PORT': os.environ.get('DB_PORT', '5432'),
}
}
Cache configuration with Redis
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
Session storage
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'
Static files
STATIC_URL = '/static/'
STATIC_ROOT = '/var/www/myapp/shared/static'
MEDIA_URL = '/media/'
MEDIA_ROOT = '/var/www/myapp/shared/media'
Logging
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': '/var/www/myapp/shared/logs/django.log',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'INFO',
'propagate': True,
},
},
}
Configure Gunicorn service
Set up systemd service for running Django with Gunicorn.
[Unit]
Description=Gunicorn daemon for Django myapp
Requires=gunicorn.socket
After=network.target
[Service]
User=deploy
Group=deploy
WorkingDirectory=/var/www/myapp/current
Environment="DJANGO_SETTINGS_MODULE=myapp.settings.production"
ExecStart=/var/www/myapp/current/venv/bin/gunicorn \
--access-logfile /var/www/myapp/shared/logs/gunicorn-access.log \
--error-logfile /var/www/myapp/shared/logs/gunicorn-error.log \
--workers 3 \
--bind unix:/run/gunicorn.sock \
myapp.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
RestartSec=5
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
[Install]
WantedBy=multi-user.target
Create Gunicorn socket
Configure systemd socket for Gunicorn.
[Unit]
Description=Gunicorn socket
[Socket]
ListenStream=/run/gunicorn.sock
SocketUser=www-data
SocketGroup=www-data
SocketMode=0660
[Install]
WantedBy=sockets.target
Configure Nginx
Set up Nginx to serve your Django application with proper static file handling.
upstream myapp_server {
server unix:/run/gunicorn.sock fail_timeout=0;
}
server {
listen 80;
server_name example.com www.example.com;
client_max_body_size 50M;
keepalive_timeout 5;
# Static files
location /static/ {
alias /var/www/myapp/shared/static/;
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Media files
location /media/ {
alias /var/www/myapp/shared/media/;
expires 1y;
add_header Cache-Control "public";
}
# Application
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_buffering off;
proxy_pass http://myapp_server;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}
Enable and start services
Enable the site configuration and start all required services.
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl daemon-reload
sudo systemctl enable gunicorn.socket
sudo systemctl start gunicorn.socket
sudo systemctl enable nginx
sudo systemctl restart nginx
Create rollback script
Set up a script to quickly rollback to the previous release if needed.
#!/bin/bash
set -e
APP_DIR="/var/www/myapp"
CURRENT_DIR="$APP_DIR/current"
RELEASES_DIR="$APP_DIR/releases"
Get current and previous releases
CURRENT_RELEASE=$(readlink $CURRENT_DIR)
PREVIOUS_RELEASE=$(ls -1t $RELEASES_DIR | sed -n '2p')
if [ -z "$PREVIOUS_RELEASE" ]; then
echo "No previous release found. Cannot rollback."
exit 1
fi
echo "Rolling back from $(basename $CURRENT_RELEASE) to $PREVIOUS_RELEASE"
Switch to previous release
ln -sfn $RELEASES_DIR/$PREVIOUS_RELEASE $CURRENT_DIR
Restart services
sudo systemctl restart gunicorn
sudo systemctl reload nginx
echo "Rollback completed successfully!"
echo "Current release: $PREVIOUS_RELEASE"
Set rollback script permissions
Make the rollback script executable.
sudo chown deploy:deploy /home/deploy/rollback.sh
sudo chmod 755 /home/deploy/rollback.sh
Configure sudoers for deployment
Allow the deploy user to restart services without password.
sudo visudo -f /etc/sudoers.d/deploy
deploy ALL=(ALL) NOPASSWD: /bin/systemctl restart gunicorn
deploy ALL=(ALL) NOPASSWD: /bin/systemctl reload nginx
deploy ALL=(ALL) NOPASSWD: /bin/systemctl status gunicorn
deploy ALL=(ALL) NOPASSWD: /bin/systemctl status nginx
Add deployment remote to your project
Configure your local Git repository to push to the deployment server.
cd /path/to/your/django/project
git remote add production deploy@your-server:/home/deploy/myapp.git
git push production main
Configure automated testing pipeline
Create comprehensive test configuration
Set up Django tests to run automatically during deployment with proper database and cache configuration.
from .base import *
import tempfile
Use in-memory SQLite for speed
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
'TEST': {
'NAME': ':memory:',
},
}
}
Disable cache during tests
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
}
Use temporary directory for media
MEDIA_ROOT = tempfile.mkdtemp()
Fast password hashing
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.MD5PasswordHasher',
]
Disable migrations
class DisableMigrations:
def __contains__(self, item):
return True
def __getitem__(self, item):
return None
MIGRATION_MODULES = DisableMigrations()
Test settings
DEBUG = False
SECRET_KEY = 'test-secret-key-not-for-production'
EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
LOGGING_CONFIG = None
Add pre-deployment checks
Enhance the deployment script with comprehensive checks before deployment.
#!/bin/bash
set -e
echo "Running pre-deployment checks..."
Check if virtual environment exists and activate it
if [ ! -d "venv" ]; then
echo "Creating virtual environment..."
python3 -m venv venv
fi
source venv/bin/activate
Install dependencies
echo "Installing dependencies..."
pip install -r requirements.txt
Check Django configuration
echo "Checking Django configuration..."
python manage.py check --settings=myapp.settings.production
Run Django system checks
echo "Running Django system checks..."
python manage.py check --deploy --settings=myapp.settings.production
Test database connection
echo "Testing database connection..."
python manage.py dbshell --settings=myapp.settings.production <<< "\q" > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Database connection failed!"
exit 1
fi
Run tests
echo "Running test suite..."
python manage.py test --settings=myapp.settings.test --verbosity=2
if [ $? -ne 0 ]; then
echo "Tests failed!"
exit 1
fi
Check for pending migrations
echo "Checking for pending migrations..."
MIGRATIONS_OUTPUT=$(python manage.py showmigrations --plan --settings=myapp.settings.production | grep "\[ \]")
if [ ! -z "$MIGRATIONS_OUTPUT" ]; then
echo "Pending migrations found:"
echo "$MIGRATIONS_OUTPUT"
fi
Run collectstatic dry-run
echo "Testing static file collection..."
python manage.py collectstatic --noinput --dry-run --settings=myapp.settings.production > /dev/null
echo "All pre-deployment checks passed!"
Update main deployment script
Integrate the pre-deployment checks into the main deployment script.
#!/bin/bash
set -e
Configuration
APP_NAME="myapp"
APP_DIR="/var/www/$APP_NAME"
RELEASE_DIR="$APP_DIR/releases/$(date +%Y%m%d_%H%M%S)"
SHARED_DIR="$APP_DIR/shared"
CURRENT_DIR="$APP_DIR/current"
REPO_DIR="/home/deploy/$APP_NAME.git"
BRANCH="main"
echo "Starting deployment of $APP_NAME at $(date)..."
Create release directory
mkdir -p $RELEASE_DIR
cd $RELEASE_DIR
Clone the repository
git clone $REPO_DIR .
git checkout $BRANCH
echo "Repository cloned successfully. Commit: $(git rev-parse --short HEAD)"
Run pre-deployment checks
/home/deploy/pre_deploy_checks.sh
Link shared directories
echo "Linking shared directories..."
ln -s $SHARED_DIR/logs logs
ln -s $SHARED_DIR/media media
ln -s $SHARED_DIR/static staticfiles
Collect static files
echo "Collecting static files..."
source venv/bin/activate
python manage.py collectstatic --noinput --settings=myapp.settings.production
Run database migrations
echo "Running database migrations..."
python manage.py migrate --settings=myapp.settings.production
Warm up the application
echo "Warming up application..."
python manage.py check --settings=myapp.settings.production
Update current symlink atomically
echo "Switching to new release..."
ln -sfn $RELEASE_DIR $CURRENT_DIR
Restart application server
echo "Restarting services..."
sudo systemctl restart gunicorn
sleep 2
Verify deployment
echo "Verifying deployment..."
if ! sudo systemctl is-active --quiet gunicorn; then
echo "Gunicorn failed to start! Rolling back..."
/home/deploy/rollback.sh
exit 1
fi
sudo systemctl reload nginx
Health check
echo "Performing health check..."
sleep 5
if ! curl -f -s http://localhost/health/ > /dev/null; then
echo "Health check failed! Application may not be responding correctly."
echo "Manual intervention may be required."
fi
Keep only last 5 releases
echo "Cleaning up old releases..."
cd $APP_DIR/releases
ls -1t | tail -n +6 | xargs -d '\n' rm -rf -- 2>/dev/null || true
echo "Deployment completed successfully at $(date)!"
echo "Release: $(basename $RELEASE_DIR)"
echo "Commit: $(cd $RELEASE_DIR && git rev-parse --short HEAD)"
Set script permissions
Make all deployment scripts executable.
sudo chown deploy:deploy /home/deploy/pre_deploy_checks.sh
sudo chmod 755 /home/deploy/pre_deploy_checks.sh
sudo chmod 755 /home/deploy/deploy.sh
Configure Redis for caching and sessions
For optimal Django performance, integrate Redis for caching and session storage. You can follow our detailed guide on configuring Django Redis caching and session storage for complete setup instructions.
Install Redis
Install Redis server for caching and session management.
sudo apt install -y redis-server
sudo systemctl enable --now redis-server
Add Redis to requirements
Include Redis client in your Django project dependencies.
Django>=4.2,<5.0
psycopg2-binary>=2.9.0
django-redis>=5.2.0
gunicorn>=20.1.0
whitenoise>=6.4.0
django-environ>=0.10.0
Verify your setup
# Check Git repository status
sudo -u deploy git --git-dir=/home/deploy/myapp.git --work-tree=/var/www/myapp/current status
Verify deployment structure
ls -la /var/www/myapp/
ls -la /var/www/myapp/releases/
Check services status
sudo systemctl status gunicorn
sudo systemctl status nginx
sudo systemctl status redis-server
Test deployment by pushing code
cd /path/to/your/project
git add .
git commit -m "Test deployment"
git push production main
Check deployment logs
sudo journalctl -u gunicorn -n 50
tail -f /var/www/myapp/shared/logs/django.log
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Tests fail during deployment | Missing test dependencies or incorrect settings | Check /var/www/myapp/current/logs/test.log and verify test settings |
| Static files not loading | collectstatic failed or incorrect permissions | Run python manage.py collectstatic --settings=myapp.settings.production manually |
| Database migration errors | Migration conflicts or database permissions | Check migration status with python manage.py showmigrations |
| Gunicorn fails to restart | Python environment issues or code errors | Check sudo journalctl -u gunicorn for detailed error logs |
| Permission denied errors | Incorrect file ownership or permissions | Fix with sudo chown -R deploy:deploy /var/www/myapp and chmod 755 |
| Git hook not executing | Hook script not executable or incorrect permissions | Ensure chmod 755 /home/deploy/myapp.git/hooks/post-receive |
Next steps
- Configure Django Redis caching and session storage for improved performance
- Set up PostgreSQL streaming replication with PgBouncer for database high availability
- Configure NGINX rate limiting and DDoS protection for security hardening
- Set up Django monitoring with Prometheus and Grafana for observability
- Configure Django SSL certificates with Certbot for HTTPS encryption
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Configuration
DEFAULT_APP_NAME="myapp"
DEFAULT_BRANCH="main"
usage() {
echo "Usage: $0 [APP_NAME] [GIT_BRANCH]"
echo " APP_NAME: Django application name (default: $DEFAULT_APP_NAME)"
echo " GIT_BRANCH: Git branch to deploy (default: $DEFAULT_BRANCH)"
exit 1
}
log() {
echo -e "${GREEN}[INFO]${NC} $1"
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
exit 1
}
cleanup() {
if [[ -n "${TEMP_FILES:-}" ]]; then
rm -f $TEMP_FILES
fi
}
trap cleanup ERR EXIT
# Check if running as root
if [[ $EUID -ne 0 ]]; then
error "This script must be run as root (use sudo)"
fi
# Parse arguments
APP_NAME="${1:-$DEFAULT_APP_NAME}"
BRANCH="${2:-$DEFAULT_BRANCH}"
if [[ "$APP_NAME" =~ [^a-zA-Z0-9_] ]]; then
error "App name must contain only alphanumeric characters and underscores"
fi
# Detect distribution
if [[ ! -f /etc/os-release ]]; then
error "/etc/os-release not found. Cannot detect distribution."
fi
source /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
NGINX_SITES_DIR="/etc/nginx/sites-available"
NGINX_ENABLED_DIR="/etc/nginx/sites-enabled"
PYTHON_CMD="python3"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
NGINX_SITES_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR="/etc/nginx/conf.d"
PYTHON_CMD="python3"
;;
fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
NGINX_SITES_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR="/etc/nginx/conf.d"
PYTHON_CMD="python3"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
NGINX_SITES_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR="/etc/nginx/conf.d"
PYTHON_CMD="python3"
;;
*)
error "Unsupported distribution: $ID"
;;
esac
log "[1/8] Creating deployment user and directories"
if ! id "deploy" >/dev/null 2>&1; then
case "$ID" in
ubuntu|debian)
adduser --system --group --home /home/deploy --shell /bin/bash deploy
;;
*)
useradd -r -s /bin/bash -d /home/deploy -m deploy
;;
esac
fi
mkdir -p "/var/www/${APP_NAME}/"{releases,shared/{logs,media,static}}
mkdir -p "/home/deploy"
chown -R deploy:deploy "/var/www/${APP_NAME}" "/home/deploy"
chmod 755 "/var/www/${APP_NAME}"
log "[2/8] Installing required packages"
$PKG_UPDATE
case "$ID" in
ubuntu|debian)
$PKG_INSTALL python3 python3-pip python3-venv git nginx postgresql-client redis-tools sudo
;;
*)
$PKG_INSTALL python3 python3-pip git nginx postgresql redis sudo
;;
esac
log "[3/8] Installing Python packages"
pip3 install --upgrade pip
pip3 install virtualenv gunicorn
log "[4/8] Initializing bare Git repository"
sudo -u deploy git init --bare "/home/deploy/${APP_NAME}.git"
sudo -u deploy mkdir -p "/home/deploy/${APP_NAME}.git/hooks"
chmod 755 "/home/deploy/${APP_NAME}.git/hooks"
log "[5/8] Creating deployment script"
cat > "/home/deploy/deploy.sh" << EOF
#!/bin/bash
set -e
# Configuration
APP_NAME="$APP_NAME"
APP_DIR="/var/www/\$APP_NAME"
RELEASE_DIR="\$APP_DIR/releases/\$(date +%Y%m%d_%H%M%S)"
SHARED_DIR="\$APP_DIR/shared"
CURRENT_DIR="\$APP_DIR/current"
REPO_DIR="/home/deploy/\$APP_NAME.git"
BRANCH="$BRANCH"
echo "Starting deployment of \$APP_NAME..."
# Create release directory
mkdir -p "\$RELEASE_DIR"
cd "\$RELEASE_DIR"
# Clone the repository
git clone "\$REPO_DIR" .
git checkout "\$BRANCH"
# Create virtual environment
$PYTHON_CMD -m venv venv
source venv/bin/activate
# Install dependencies
if [[ -f requirements.txt ]]; then
pip install -r requirements.txt
fi
# Run tests if test settings exist
if [[ -f manage.py ]] && $PYTHON_CMD manage.py help test >/dev/null 2>&1; then
echo "Running tests..."
if ! $PYTHON_CMD manage.py test 2>/dev/null; then
echo "Tests failed. Aborting deployment."
rm -rf "\$RELEASE_DIR"
exit 1
fi
fi
# Link shared directories
ln -sf "\$SHARED_DIR/logs" logs
ln -sf "\$SHARED_DIR/media" media
ln -sf "\$SHARED_DIR/static" staticfiles
# Collect static files if Django project
if [[ -f manage.py ]]; then
echo "Collecting static files..."
$PYTHON_CMD manage.py collectstatic --noinput || true
echo "Running database migrations..."
$PYTHON_CMD manage.py migrate || true
fi
# Update current symlink atomically
ln -sfn "\$RELEASE_DIR" "\$CURRENT_DIR"
# Restart services if they exist
systemctl is-active gunicorn >/dev/null && systemctl restart gunicorn || true
systemctl is-active nginx >/dev/null && systemctl reload nginx || true
# Keep only last 5 releases
cd "\$APP_DIR/releases"
ls -1t | tail -n +6 | xargs -r rm -rf
echo "Deployment completed successfully!"
echo "Release: \$(basename \$RELEASE_DIR)"
EOF
chown deploy:deploy "/home/deploy/deploy.sh"
chmod 755 "/home/deploy/deploy.sh"
log "[6/8] Creating post-receive Git hook"
cat > "/home/deploy/${APP_NAME}.git/hooks/post-receive" << 'EOF'
#!/bin/bash
set -e
echo "Received push to repository. Starting deployment..."
# Change to deploy user and run deployment
sudo -u deploy /home/deploy/deploy.sh
echo "Deployment hook completed."
EOF
chown deploy:deploy "/home/deploy/${APP_NAME}.git/hooks/post-receive"
chmod 755 "/home/deploy/${APP_NAME}.git/hooks/post-receive"
log "[7/8] Configuring sudo permissions for deploy user"
cat > "/etc/sudoers.d/deploy" << EOF
deploy ALL=(root) NOPASSWD: /bin/systemctl restart gunicorn
deploy ALL=(root) NOPASSWD: /bin/systemctl reload nginx
deploy ALL=(root) NOPASSWD: /home/deploy/deploy.sh
EOF
chmod 440 "/etc/sudoers.d/deploy"
log "[8/8] Enabling and starting services"
systemctl enable nginx
systemctl start nginx
# Configure firewall if available
if command -v ufw >/dev/null 2>&1; then
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
elif command -v firewall-cmd >/dev/null 2>&1; then
firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
fi
log "Django continuous deployment setup completed successfully!"
echo
echo "Next steps:"
echo "1. Add your remote repository:"
echo " git remote add production deploy@$(hostname -I | awk '{print $1}'):/home/deploy/${APP_NAME}.git"
echo "2. Push your code:"
echo " git push production ${BRANCH}"
echo "3. Configure gunicorn service for your Django app"
echo "4. Configure nginx virtual host"
echo
echo "App directory: /var/www/${APP_NAME}"
echo "Git repository: /home/deploy/${APP_NAME}.git"
Review the script before running. Execute with: bash install.sh