Stage 2 · Tools
DevOps Scripts in Practice
Deployment Scripts
Zero-downtime deploy, health checks, rollback, and notification on failure.
Deployment Strategies
Deployment scripts automate releasing new versions safely. Different strategies trade off complexity, downtime, and risk. Choose the right strategy for your reliability requirements.
| Strategy | Downtime | Complexity | Rollback Speed |
|---|---|---|---|
| Blue-Green | Zero | Medium | Instant |
| Rolling | Zero | Low | Minutes |
| Canary | Zero | High | Minutes |
| Recreate | Yes | Low | Minutes |
Blue-Green Deployment
Blue-green maintains two identical environments. The blue environment serves production. Deploy to green, verify, then switch traffic. Rollback is instant — just switch back.
#!/usr/bin/env bash
set -euo pipefail
readonly BLUE_PORT=8001
readonly GREEN_PORT=8002
readonly HEALTH_URL="http://localhost:PORT/health"
readonly DEPLOY_DIR="/opt/app"
# Determine current active environment
get_active() {
local active
active=$(cat "$DEPLOY_DIR/active-env" 2>/dev/null || echo "blue")
echo "$active"
}
get_inactive() {
local active
active=$(get_active)
[[ "$active" == "blue" ]] && echo "green" || echo "blue"
}
get_port() {
[[ "$1" == "blue" ]] && echo "$BLUE_PORT" || echo "$GREEN_PORT"
}
# Deploy to inactive environment
deploy() {
local target
target=$(get_inactive)
local port
port=$(get_port "$target")
echo "Deploying to $target (port $port)..."
# Copy new version
rsync -av "$DEPLOY_DIR/current/" "$DEPLOY_DIR/$target/"
# Start the new environment
sudo systemctl start "app-$target"
# Health check
if ! wait_for_health "$port"; then
echo "Health check failed on $target"
sudo systemctl stop "app-$target"
return 1
fi
echo "$target" > "$DEPLOY_DIR/active-env"
echo "Deploy complete. Now serving from $target"
}
# Switch traffic (nginx reload)
switch_traffic() {
local target="$1"
local port
port=$(get_port "$target")
sudo sed -i "s/server 127.0.0.1:[0-9]*/server 127.0.0.1:$port/" /etc/nginx/conf.d/app.conf
sudo nginx -t && sudo nginx -s reload
}Blue-green keeps two copies of the app. Deploy to the inactive one, verify it works, then switch the load balancer. Rollback is instant — switch back to the previous environment.
Rolling Deployment
#!/usr/bin/env bash
set -euo pipefail
SERVERS=("web1" "web2" "web3" "web4")
HEALTH_TIMEOUT=30
HEALTH_INTERVAL=2
rolling_deploy() {
local total=${#SERVERS[@]}
local current=0
for server in "${SERVERS[@]}"; do
((current++))
echo "[$current/$total] Deploying to $server..."
# Deploy
rsync -avz ./app/ "$server:/opt/app/"
# Restart service
ssh "$server" "sudo systemctl restart app"
# Wait for health
if ! wait_for_health_remote "$server" 8080; then
echo "FAILED: $server unhealthy after deploy"
echo "Rolling back..."
rollback "$server"
return 1
fi
echo "[$current/$total] $server deployed successfully"
done
echo "Rolling deploy complete"
}
wait_for_health_remote() {
local host="$1"
local port="$2"
local elapsed=0
while (( elapsed < HEALTH_TIMEOUT )); do
if nc -zv -w 2 "$host" "$port" 2>/dev/null; then
return 0
fi
sleep "$HEALTH_INTERVAL"
elapsed=$((elapsed + HEALTH_INTERVAL))
done
return 1
}Rolling deployment updates servers one at a time. Each server is deployed and verified before moving to the next. If a server fails health check, the deployment stops and rolls back.
Post-Deploy Health Checks
wait_for_health() {
local port="$1"
local timeout="{2:-30}"
local elapsed=0
local url="http://localhost:$port/health"
echo "Waiting for health check at $url..."
while (( elapsed < timeout )); do
local status
status=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 2 "$url" 2>/dev/null || echo "000")
if [[ "$status" == "200" ]]; then
echo "Health check passed (HTTP $status)"
return 0
fi
echo " Attempt ($elapsed/{timeout}s): HTTP $status"
sleep 2
elapsed=$((elapsed + 2))
done
echo "Health check failed after {timeout}s"
return 1
}
# Check multiple endpoints
verify_deploy() {
local base_url="$1"
local endpoints=("/health" "/api/status" "/ready")
for endpoint in "${endpoints[@]}"; do
local status
status=$(curl -sS -o /dev/null -w '%{http_code}' "$base_url$endpoint")
if [[ "$status" != "200" ]]; then
echo "FAIL: $endpoint returned $status"
return 1
fi
echo "OK: $endpoint (HTTP $status)"
done
return 0
}Health checks should verify multiple endpoints. The health endpoint confirms the app is running. The status endpoint confirms dependencies are connected. The ready endpoint confirms it can serve traffic.
Automatic Rollback
#!/usr/bin/env bash
set -euo pipefail
DEPLOY_DIR="/opt/app"
BACKUP_DIR="/opt/app-backups"
rollback() {
local current
current=$(cat "$DEPLOY_DIR/active-env")
local previous
if [[ -f "$BACKUP_DIR/previous-env" ]]; then
previous=$(cat "$BACKUP_DIR/previous-env")
else
echo "No previous version available"
return 1
fi
echo "Rolling back from $current to $previous..."
# Restore previous version
rsync -av "$BACKUP_DIR/$previous/" "$DEPLOY_DIR/$previous/"
# Switch traffic
sudo systemctl restart "app-$previous"
echo "$previous" > "$DEPLOY_DIR/active-env"
# Verify
local port
port=$(get_port "$previous")
if wait_for_health "$port"; then
echo "Rollback complete. Now serving from $previous"
else
echo "CRITICAL: Rollback health check failed"
return 1
fi
}
# Save backup before deploy
save_backup() {
local current
current=$(get_active)
mkdir -p "$BACKUP_DIR"
rsync -av "$DEPLOY_DIR/$current/" "$BACKUP_DIR/$current/"
echo "$current" > "$BACKUP_DIR/previous-env"
}Always backup before deploying. If the health check fails, the rollback script restores the previous version and switches traffic. This should be automatic — no human intervention required.
Deploy Notifications
#!/usr/bin/env bash
notify_deploy() {
local status="$1"
local version="{2:-unknown}"
local env="{3:-production}"
local color
[[ "$status" == "success" ]] && color="#36a64f" || color="#ff0000"
# Slack notification
curl -sS -X POST "$SLACK_WEBHOOK" \
-H "Content-Type: application/json" \
-d "{
"attachments": [{
"color": "$color",
"title": "Deploy $status",
"fields": [
{"title": "Environment", "value": "$env", "short": true},
{"title": "Version", "value": "$version", "short": true},
{"title": "Author", "value": "$(whoami)", "short": true},
{"title": "Time", "value": "$(date)", "short": true}
]
}]
}" > /dev/null
}
# Usage in deploy script
if perform_deploy; then
notify_deploy "success" "$VERSION" "production"
else
notify_deploy "failure" "$VERSION" "production"
exit 1
fiDeploy notifications keep the team informed. Success notifications provide visibility. Failure notifications trigger immediate response. Include version, environment, author, and timestamp.
1) Save backup. 2) Deploy new version. 3) Health check. 4) Switch traffic. 5) Verify. 6) Notify on success/failure. 7) Automatic rollback on failure. This is the complete deployment flow.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.