Configure zero-downtime deployments for Python web applications using Gunicorn blue-green deployment strategy with NGINX reverse proxy, automated health checks, and rollback mechanisms for production reliability.
Prerequisites
- Root or sudo access
- Python 3.x installed
- Basic understanding of systemd and NGINX
- Familiarity with Python web applications
What this solves
Blue-green deployment eliminates downtime during application updates by maintaining two identical production environments. While one environment serves live traffic, you deploy updates to the other, then switch traffic after verifying the new deployment works correctly. This tutorial shows you how to implement this pattern using Gunicorn application servers behind NGINX for Python web applications.
Step-by-step configuration
Update system packages
Start by updating your package manager to ensure you have the latest versions of all required components.
sudo apt update && sudo apt upgrade -yInstall Python, NGINX, and required packages
Install the base components needed for running Gunicorn applications with NGINX reverse proxy.
sudo apt install -y python3 python3-pip python3-venv nginx curl jqCreate application user and directory structure
Set up a dedicated user for running your Python applications and create the directory structure for blue-green deployments.
sudo useradd --system --shell /bin/bash --home-dir /opt/app app
sudo mkdir -p /opt/app/{blue,green,shared/{logs,uploads}}
sudo chown -R app:app /opt/app
sudo chmod 755 /opt/app
sudo chmod 775 /opt/app/shared/{logs,uploads}Create a sample Python application
Create a simple Flask application to demonstrate the blue-green deployment process. This will serve as your application template.
from flask import Flask, jsonify
import os
import socket
app = Flask(__name__)
@app.route('/health')
def health():
return jsonify({
'status': 'healthy',
'version': os.environ.get('APP_VERSION', 'blue'),
'hostname': socket.gethostname()
})
@app.route('/')
def home():
return jsonify({
'message': 'Hello from Blue Environment',
'version': os.environ.get('APP_VERSION', 'blue')
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)Set up Python virtual environments
Create separate virtual environments for blue and green deployments to isolate dependencies.
sudo -u app python3 -m venv /opt/app/blue/venv
sudo -u app python3 -m venv /opt/app/green/venv
Install dependencies for blue environment
sudo -u app /opt/app/blue/venv/bin/pip install flask gunicorn
Copy application to green environment
sudo -u app cp /opt/app/blue/app.py /opt/app/green/
sudo -u app /opt/app/green/venv/bin/pip install flask gunicornCreate Gunicorn configuration files
Configure Gunicorn with separate configuration files for blue and green environments, each using different ports.
bind = "127.0.0.1:8000"
workers = 2
worker_class = "sync"
worker_connections = 1000
timeout = 30
keepalive = 2
preload_app = True
max_requests = 1000
max_requests_jitter = 100
pidfile = "/opt/app/blue/gunicorn.pid"
accesslog = "/opt/app/shared/logs/blue-access.log"
errorlog = "/opt/app/shared/logs/blue-error.log"
loglevel = "info"
user = "app"
group = "app"bind = "127.0.0.1:8001"
workers = 2
worker_class = "sync"
worker_connections = 1000
timeout = 30
keepalive = 2
preload_app = True
max_requests = 1000
max_requests_jitter = 100
pidfile = "/opt/app/green/gunicorn.pid"
accesslog = "/opt/app/shared/logs/green-access.log"
errorlog = "/opt/app/shared/logs/green-error.log"
loglevel = "info"
user = "app"
group = "app"Create systemd service files
Set up systemd services for both blue and green Gunicorn instances to manage them as system services.
[Unit]
Description=Gunicorn Blue Environment
After=network.target
[Service]
Type=forking
User=app
Group=app
WorkingDirectory=/opt/app/blue
Environment=APP_VERSION=blue
ExecStart=/opt/app/blue/venv/bin/gunicorn --config gunicorn.conf.py app:app
ExecReload=/bin/kill -s HUP $MAINPID
PIDFile=/opt/app/blue/gunicorn.pid
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target[Unit]
Description=Gunicorn Green Environment
After=network.target
[Service]
Type=forking
User=app
Group=app
WorkingDirectory=/opt/app/green
Environment=APP_VERSION=green
ExecStart=/opt/app/green/venv/bin/gunicorn --config gunicorn.conf.py app:app
ExecReload=/bin/kill -s HUP $MAINPID
PIDFile=/opt/app/green/gunicorn.pid
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetConfigure NGINX upstream blocks
Set up NGINX with upstream configurations that can switch between blue and green environments. This configuration allows dynamic switching without reloading NGINX.
upstream app_blue {
server 127.0.0.1:8000 max_fails=3 fail_timeout=30s;
}
upstream app_green {
server 127.0.0.1:8001 max_fails=3 fail_timeout=30s;
}
upstream app_active {
server 127.0.0.1:8000 max_fails=3 fail_timeout=30s;
}Configure NGINX virtual host
Create the main NGINX configuration that uses the upstream blocks and includes health check endpoints.
server {
listen 80;
server_name example.com;
# Health check endpoints
location /health/blue {
proxy_pass http://app_blue/health;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
access_log off;
}
location /health/green {
proxy_pass http://app_green/health;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
access_log off;
}
# Main application
location / {
proxy_pass http://app_active;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Connection and timeout settings
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Buffer settings
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
}
# Status endpoint for deployment script
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
}Enable the NGINX configuration
Enable the site configuration and ensure NGINX can start successfully.
sudo ln -sf /etc/nginx/sites-available/app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxStart the blue environment
Enable and start the blue environment as the initial active deployment.
sudo systemctl daemon-reload
sudo systemctl enable app-blue
sudo systemctl start app-blue
sudo systemctl status app-blueCreate the deployment script
Build an automated deployment script that handles the blue-green switching process with health checks and rollback capability.
#!/bin/bash
set -euo pipefail
Configuration
BLUE_PORT=8000
GREEN_PORT=8001
HEALTH_TIMEOUT=30
HEALTH_RETRIES=6
NGINX_UPSTREAM_CONF="/etc/nginx/conf.d/upstream.conf"
LOG_FILE="/opt/app/shared/logs/deployment.log"
Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
Logging function
log() {
echo "$(date +'%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG_FILE"
}
error() {
log "ERROR: $1" >&2
}
info() {
log "INFO: $1"
}
Get current active environment
get_current_env() {
if grep -q "server 127.0.0.1:$BLUE_PORT" "$NGINX_UPSTREAM_CONF" | grep -q "app_active" -A1; then
if grep -A1 "upstream app_active" "$NGINX_UPSTREAM_CONF" | grep -q "127.0.0.1:$BLUE_PORT"; then
echo "blue"
else
echo "green"
fi
else
echo "blue" # Default to blue if unclear
fi
}
Get inactive environment
get_inactive_env() {
current=$(get_current_env)
if [ "$current" = "blue" ]; then
echo "green"
else
echo "blue"
fi
}
Health check function
health_check() {
local env=$1
local port
if [ "$env" = "blue" ]; then
port=$BLUE_PORT
else
port=$GREEN_PORT
fi
info "Performing health check for $env environment (port $port)"
for i in $(seq 1 $HEALTH_RETRIES); do
if curl -sf "http://127.0.0.1:$port/health" >/dev/null; then
info "Health check passed for $env environment (attempt $i/$HEALTH_RETRIES)"
return 0
fi
info "Health check failed for $env environment (attempt $i/$HEALTH_RETRIES)"
sleep 5
done
error "Health check failed for $env environment after $HEALTH_RETRIES attempts"
return 1
}
Switch NGINX upstream
switch_upstream() {
local target_env=$1
local target_port
if [ "$target_env" = "blue" ]; then
target_port=$BLUE_PORT
else
target_port=$GREEN_PORT
fi
info "Switching NGINX upstream to $target_env environment (port $target_port)"
# Create temporary config with new upstream
cat > "${NGINX_UPSTREAM_CONF}.tmp" << EOF
upstream app_blue {
server 127.0.0.1:$BLUE_PORT max_fails=3 fail_timeout=30s;
}
upstream app_green {
server 127.0.0.1:$GREEN_PORT max_fails=3 fail_timeout=30s;
}
upstream app_active {
server 127.0.0.1:$target_port max_fails=3 fail_timeout=30s;
}
EOF
# Test NGINX configuration
if nginx -t 2>/dev/null; then
mv "${NGINX_UPSTREAM_CONF}.tmp" "$NGINX_UPSTREAM_CONF"
systemctl reload nginx
info "Successfully switched to $target_env environment"
return 0
else
error "NGINX configuration test failed"
rm -f "${NGINX_UPSTREAM_CONF}.tmp"
return 1
fi
}
Deploy function
deploy() {
local current_env
local inactive_env
current_env=$(get_current_env)
inactive_env=$(get_inactive_env)
info "Starting deployment process"
info "Current active environment: $current_env"
info "Deploying to: $inactive_env"
# Stop inactive environment
info "Stopping $inactive_env environment"
systemctl stop "app-$inactive_env" || true
# Update green application for demo (normally you'd deploy your code here)
if [ "$inactive_env" = "green" ]; then
cat > "/opt/app/green/app.py" << 'EOF'
from flask import Flask, jsonify
import os
import socket
app = Flask(__name__)
@app.route('/health')
def health():
return jsonify({
'status': 'healthy',
'version': os.environ.get('APP_VERSION', 'green'),
'hostname': socket.gethostname()
})
@app.route('/')
def home():
return jsonify({
'message': 'Hello from Green Environment',
'version': os.environ.get('APP_VERSION', 'green')
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
EOF
fi
# Start inactive environment
info "Starting $inactive_env environment"
systemctl start "app-$inactive_env"
# Wait for service to be ready
sleep 5
# Perform health checks
if health_check "$inactive_env"; then
info "$inactive_env environment is healthy, proceeding with switch"
# Switch traffic
if switch_upstream "$inactive_env"; then
info "Deployment successful! Traffic switched to $inactive_env"
# Wait a bit then stop the old environment
sleep 10
info "Stopping old $current_env environment"
systemctl stop "app-$current_env"
info "Deployment completed successfully"
else
error "Failed to switch upstream, rolling back"
systemctl stop "app-$inactive_env"
exit 1
fi
else
error "Health check failed for $inactive_env environment"
systemctl stop "app-$inactive_env"
exit 1
fi
}
Rollback function
rollback() {
local current_env
local previous_env
current_env=$(get_current_env)
previous_env=$(get_inactive_env)
info "Rolling back from $current_env to $previous_env"
# Start previous environment
systemctl start "app-$previous_env"
sleep 5
# Health check
if health_check "$previous_env"; then
if switch_upstream "$previous_env"; then
info "Rollback successful! Traffic switched back to $previous_env"
systemctl stop "app-$current_env"
else
error "Failed to switch upstream during rollback"
exit 1
fi
else
error "Health check failed for $previous_env during rollback"
exit 1
fi
}
Status function
status() {
local current_env
current_env=$(get_current_env)
echo -e "${BLUE}=== Deployment Status ===${NC}"
echo -e "Current active environment: ${GREEN}$current_env${NC}"
echo
echo -e "${BLUE}=== Service Status ===${NC}"
echo -e "Blue environment: $(systemctl is-active app-blue)"
echo -e "Green environment: $(systemctl is-active app-green)"
echo
echo -e "${BLUE}=== Health Checks ===${NC}"
if curl -sf http://127.0.0.1:$BLUE_PORT/health >/dev/null 2>&1; then
echo -e "Blue health: ${GREEN}OK${NC}"
else
echo -e "Blue health: ${RED}FAIL${NC}"
fi
if curl -sf http://127.0.0.1:$GREEN_PORT/health >/dev/null 2>&1; then
echo -e "Green health: ${GREEN}OK${NC}"
else
echo -e "Green health: ${RED}FAIL${NC}"
fi
}
Main script logic
case "${1:-}" in
deploy)
deploy
;;
rollback)
rollback
;;
status)
status
;;
*)
echo "Usage: $0 {deploy|rollback|status}"
echo " deploy - Deploy to inactive environment and switch traffic"
echo " rollback - Switch back to previous environment"
echo " status - Show current deployment status"
exit 1
;;
esacMake deployment script executable and set permissions
Set proper permissions for the deployment script and create required log directories.
sudo chmod +x /opt/app/deploy.sh
sudo chown app:app /opt/app/deploy.sh
sudo touch /opt/app/shared/logs/deployment.log
sudo chown app:app /opt/app/shared/logs/deployment.logConfigure log rotation
Set up log rotation for application and deployment logs to prevent disk space issues.
/opt/app/shared/logs/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
copytruncate
su app app
}Create monitoring script
Build a monitoring script that continuously checks the health of your blue-green deployment and can send alerts.
#!/bin/bash
set -euo pipefail
Configuration
BLUE_PORT=8000
GREEN_PORT=8001
MONITOR_LOG="/opt/app/shared/logs/monitor.log"
ALERT_EMAIL="admin@example.com"
Logging function
log() {
echo "$(date +'%Y-%m-%d %H:%M:%S') $1" | tee -a "$MONITOR_LOG"
}
Check if environment is responding
check_environment() {
local env=$1
local port=$2
if curl -sf --max-time 5 "http://127.0.0.1:$port/health" >/dev/null; then
return 0
else
return 1
fi
}
Get active environment from NGINX config
get_active_env() {
if grep -A1 "upstream app_active" /etc/nginx/conf.d/upstream.conf | grep -q "127.0.0.1:$BLUE_PORT"; then
echo "blue"
else
echo "green"
fi
}
Send alert (placeholder - integrate with your alerting system)
send_alert() {
local message=$1
log "ALERT: $message"
# echo "$message" | mail -s "App Deployment Alert" "$ALERT_EMAIL"
}
Main monitoring loop
monitor() {
local active_env
active_env=$(get_active_env)
# Check active environment
if [ "$active_env" = "blue" ]; then
if ! check_environment "blue" $BLUE_PORT; then
send_alert "Active blue environment is not responding"
return 1
fi
else
if ! check_environment "green" $GREEN_PORT; then
send_alert "Active green environment is not responding"
return 1
fi
fi
log "Active environment ($active_env) is healthy"
return 0
}
Run monitoring check
monitorsudo chmod +x /opt/app/monitor.sh
sudo chown app:app /opt/app/monitor.shSet up monitoring cron job
Configure a cron job to run the monitoring script every minute and log the results.
sudo -u app crontab -l 2>/dev/null | { cat; echo " * /opt/app/monitor.sh"; } | sudo -u app crontab -Verify your setup
Test the blue-green deployment system to ensure everything works correctly.
# Check initial status
sudo -u app /opt/app/deploy.sh status
Test the application
curl http://localhost/
curl http://localhost/health/blue
Verify NGINX is serving traffic
curl -I http://localhost/
Check service status
sudo systemctl status app-blue
sudo systemctl status nginxPerforming deployments
Execute your first deployment
Test the blue-green deployment process by deploying to the green environment.
# Perform deployment
sudo -u app /opt/app/deploy.sh deploy
Check new status
sudo -u app /opt/app/deploy.sh status
Test the updated application
curl http://localhost/Test rollback functionality
Verify that you can quickly rollback to the previous environment if needed.
# Perform rollback
sudo -u app /opt/app/deploy.sh rollback
Verify rollback worked
sudo -u app /opt/app/deploy.sh status
curl http://localhost/Monitoring and logging
Your blue-green deployment generates several log files for monitoring and troubleshooting. Here's how to monitor your deployment effectively.
# View deployment logs
sudo tail -f /opt/app/shared/logs/deployment.log
Monitor application logs
sudo tail -f /opt/app/shared/logs/blue-error.log
sudo tail -f /opt/app/shared/logs/green-error.log
Check NGINX access logs
sudo tail -f /var/log/nginx/access.log
View monitoring logs
sudo tail -f /opt/app/shared/logs/monitor.logFor production environments, consider integrating with monitoring solutions like Prometheus and Grafana for Gunicorn monitoring or centralized logging with the ELK stack.
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Deployment script fails with permission denied | Script lacks execute permissions or wrong ownership | sudo chmod +x /opt/app/deploy.sh && sudo chown app:app /opt/app/deploy.sh |
| Health checks always fail | Application not binding to correct interface | Check gunicorn.conf.py bind setting is 127.0.0.1, not localhost |
| NGINX returns 502 Bad Gateway | Backend services not running or wrong ports | sudo systemctl status app-blue app-green and verify port configuration |
| Deployment hangs during switch | NGINX configuration test failing | Run sudo nginx -t to check configuration syntax |
| Applications can't write to log files | Log directory permissions incorrect | sudo chown -R app:app /opt/app/shared/logs && sudo chmod 775 /opt/app/shared/logs |
| Rollback fails to start previous environment | Previous environment service stopped or corrupted | Manually start service: sudo systemctl start app-blue or sudo systemctl start app-green |
Next steps
- Configure NGINX rate limiting and DDoS protection
- Set up monitoring and alerting with Prometheus and Grafana
- Configure automated backup and recovery testing
- Add SSL certificate management with Let's Encrypt
- Scale to Kubernetes blue-green deployments with ArgoCD
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Blue-Green Deployment Setup Script for Python Applications
# Supports Ubuntu, Debian, AlmaLinux, Rocky Linux, CentOS, RHEL, Fedora
# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Global variables
DOMAIN=${1:-""}
APP_USER="app"
APP_DIR="/opt/app"
NGINX_CONFIG_DIR=""
TOTAL_STEPS=12
# Cleanup function
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Cleaning up...${NC}"
systemctl stop gunicorn-blue gunicorn-green 2>/dev/null || true
systemctl disable gunicorn-blue gunicorn-green 2>/dev/null || true
rm -f /etc/systemd/system/gunicorn-{blue,green}.service
userdel -r $APP_USER 2>/dev/null || true
rm -rf $APP_DIR
}
trap cleanup ERR
# Usage message
usage() {
echo "Usage: $0 [domain_name]"
echo "Example: $0 myapp.example.com"
echo "If no domain provided, will use server IP"
exit 1
}
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
# Detect distribution
echo -e "${YELLOW}[1/$TOTAL_STEPS] Detecting distribution...${NC}"
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
NGINX_CONFIG_DIR="/etc/nginx/sites-available"
NGINX_ENABLED_DIR="/etc/nginx/sites-enabled"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
NGINX_CONFIG_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR="/etc/nginx/conf.d"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
NGINX_CONFIG_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR="/etc/nginx/conf.d"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
echo -e "${GREEN}Detected: $PRETTY_NAME${NC}"
else
echo -e "${RED}Cannot detect distribution${NC}"
exit 1
fi
# Update system packages
echo -e "${YELLOW}[2/$TOTAL_STEPS] Updating system packages...${NC}"
$PKG_UPDATE
# Install required packages
echo -e "${YELLOW}[3/$TOTAL_STEPS] Installing required packages...${NC}"
$PKG_INSTALL python3 python3-pip nginx curl jq
# Create application user and directory structure
echo -e "${YELLOW}[4/$TOTAL_STEPS] Creating application user and directories...${NC}"
useradd --system --shell /bin/bash --home-dir $APP_DIR $APP_USER || true
mkdir -p $APP_DIR/{blue,green,shared/{logs,uploads}}
chown -R $APP_USER:$APP_USER $APP_DIR
chmod 755 $APP_DIR
chmod 755 $APP_DIR/shared/{logs,uploads}
# Create sample Flask application
echo -e "${YELLOW}[5/$TOTAL_STEPS] Creating sample application...${NC}"
cat > $APP_DIR/blue/app.py << 'EOF'
from flask import Flask, jsonify
import os
import socket
app = Flask(__name__)
@app.route('/health')
def health():
return jsonify({
'status': 'healthy',
'version': os.environ.get('APP_VERSION', 'blue'),
'hostname': socket.gethostname()
})
@app.route('/')
def home():
return jsonify({
'message': 'Hello from Blue Environment',
'version': os.environ.get('APP_VERSION', 'blue')
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
EOF
cp $APP_DIR/blue/app.py $APP_DIR/green/
sed -i 's/Hello from Blue Environment/Hello from Green Environment/' $APP_DIR/green/app.py
chown $APP_USER:$APP_USER $APP_DIR/{blue,green}/app.py
# Set up Python virtual environments
echo -e "${YELLOW}[6/$TOTAL_STEPS] Setting up Python virtual environments...${NC}"
sudo -u $APP_USER python3 -m venv $APP_DIR/blue/venv
sudo -u $APP_USER python3 -m venv $APP_DIR/green/venv
sudo -u $APP_USER $APP_DIR/blue/venv/bin/pip install flask gunicorn
sudo -u $APP_USER $APP_DIR/green/venv/bin/pip install flask gunicorn
# Create Gunicorn configuration files
echo -e "${YELLOW}[7/$TOTAL_STEPS] Creating Gunicorn configurations...${NC}"
cat > $APP_DIR/blue/gunicorn.conf.py << EOF
bind = "127.0.0.1:8000"
workers = 2
worker_class = "sync"
worker_connections = 1000
timeout = 30
keepalive = 2
preload_app = True
max_requests = 1000
max_requests_jitter = 100
pidfile = "$APP_DIR/blue/gunicorn.pid"
accesslog = "$APP_DIR/shared/logs/blue-access.log"
errorlog = "$APP_DIR/shared/logs/blue-error.log"
loglevel = "info"
user = "$APP_USER"
group = "$APP_USER"
EOF
cat > $APP_DIR/green/gunicorn.conf.py << EOF
bind = "127.0.0.1:8001"
workers = 2
worker_class = "sync"
worker_connections = 1000
timeout = 30
keepalive = 2
preload_app = True
max_requests = 1000
max_requests_jitter = 100
pidfile = "$APP_DIR/green/gunicorn.pid"
accesslog = "$APP_DIR/shared/logs/green-access.log"
errorlog = "$APP_DIR/shared/logs/green-error.log"
loglevel = "info"
user = "$APP_USER"
group = "$APP_USER"
EOF
chown $APP_USER:$APP_USER $APP_DIR/{blue,green}/gunicorn.conf.py
# Create systemd service files
echo -e "${YELLOW}[8/$TOTAL_STEPS] Creating systemd services...${NC}"
cat > /etc/systemd/system/gunicorn-blue.service << EOF
[Unit]
Description=Gunicorn Blue Environment
After=network.target
[Service]
Type=forking
User=$APP_USER
Group=$APP_USER
WorkingDirectory=$APP_DIR/blue
Environment=APP_VERSION=blue
ExecStart=$APP_DIR/blue/venv/bin/gunicorn --config gunicorn.conf.py app:app
ExecReload=/bin/kill -s HUP \$MAINPID
PIDFile=$APP_DIR/blue/gunicorn.pid
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
cat > /etc/systemd/system/gunicorn-green.service << EOF
[Unit]
Description=Gunicorn Green Environment
After=network.target
[Service]
Type=forking
User=$APP_USER
Group=$APP_USER
WorkingDirectory=$APP_DIR/green
Environment=APP_VERSION=green
ExecStart=$APP_DIR/green/venv/bin/gunicorn --config gunicorn.conf.py app:app
ExecReload=/bin/kill -s HUP \$MAINPID
PIDFile=$APP_DIR/green/gunicorn.pid
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
# Configure NGINX
echo -e "${YELLOW}[9/$TOTAL_STEPS] Configuring NGINX...${NC}"
SERVER_NAME=${DOMAIN:-$(curl -s ifconfig.me || echo "localhost")}
if [[ "$PKG_MGR" == "apt" ]]; then
CONFIG_FILE="$NGINX_CONFIG_DIR/bluegreen"
else
CONFIG_FILE="$NGINX_CONFIG_DIR/bluegreen.conf"
fi
cat > $CONFIG_FILE << EOF
upstream backend {
server 127.0.0.1:8000;
}
server {
listen 80;
server_name $SERVER_NAME;
location /health {
proxy_pass http://backend;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
location / {
proxy_pass http://backend;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
}
EOF
if [[ "$PKG_MGR" == "apt" ]]; then
ln -sf $CONFIG_FILE $NGINX_ENABLED_DIR/bluegreen
rm -f /etc/nginx/sites-enabled/default
fi
# Create deployment script
echo -e "${YELLOW}[10/$TOTAL_STEPS] Creating deployment script...${NC}"
cat > $APP_DIR/deploy.sh << 'EOF'
#!/bin/bash
set -e
ACTIVE_ENV=$(nginx -T 2>/dev/null | grep -oP "server 127\.0\.0\.1:\K(8000|8001)" | head -1)
if [[ "$ACTIVE_ENV" == "8000" ]]; then
CURRENT="blue"
NEW="green"
NEW_PORT="8001"
else
CURRENT="green"
NEW="blue"
NEW_PORT="8000"
fi
echo "Current active: $CURRENT, deploying to: $NEW"
# Start new environment
systemctl start gunicorn-$NEW
sleep 5
# Health check
if curl -f http://127.0.0.1:$NEW_PORT/health > /dev/null 2>&1; then
echo "Health check passed, switching traffic..."
sed -i "s/server 127.0.0.1:[0-9]*/server 127.0.0.1:$NEW_PORT/" /etc/nginx/conf.d/bluegreen.conf 2>/dev/null || \
sed -i "s/server 127.0.0.1:[0-9]*/server 127.0.0.1:$NEW_PORT/" /etc/nginx/sites-available/bluegreen
nginx -t && systemctl reload nginx
sleep 2
systemctl stop gunicorn-$CURRENT
echo "Deployment complete! Active environment: $NEW"
else
echo "Health check failed, rolling back..."
systemctl stop gunicorn-$NEW
exit 1
fi
EOF
chmod 755 $APP_DIR/deploy.sh
chown $APP_USER:$APP_USER $APP_DIR/deploy.sh
# Start and enable services
echo -e "${YELLOW}[11/$TOTAL_STEPS] Starting services...${NC}"
systemctl daemon-reload
systemctl enable nginx gunicorn-blue
systemctl start nginx gunicorn-blue
# Configure firewall
if command -v ufw &> /dev/null; then
ufw allow 'Nginx Full' 2>/dev/null || true
elif command -v firewall-cmd &> /dev/null; then
firewall-cmd --permanent --add-service=http 2>/dev/null || true
firewall-cmd --reload 2>/dev/null || true
fi
# Verification
echo -e "${YELLOW}[12/$TOTAL_STEPS] Verifying installation...${NC}"
sleep 3
if systemctl is-active --quiet nginx && systemctl is-active --quiet gunicorn-blue; then
if curl -f http://localhost/health > /dev/null 2>&1; then
echo -e "${GREEN}✓ Installation successful!${NC}"
echo -e "${GREEN}✓ Blue environment is active${NC}"
echo -e "${GREEN}✓ Access your application at: http://$SERVER_NAME${NC}"
echo -e "${GREEN}✓ Health check: http://$SERVER_NAME/health${NC}"
echo -e "${GREEN}✓ Deploy script: $APP_DIR/deploy.sh${NC}"
else
echo -e "${RED}✗ Application not responding${NC}"
exit 1
fi
else
echo -e "${RED}✗ Services not running properly${NC}"
exit 1
fi
Review the script before running. Execute with: bash install.sh