Why Bash Still Rules DevOps?
Infrastructure as code, containers, and Kubernetes are great, but when you log into a box to fix something right now, Bash is still the fastest tool you have. ​
This post focuses on practical Bash scripting patterns that automate real DevOps tasks: deployments, health checks, log cleanups, and safety‑first operations you can trust in production. ​
1. Bash Scripting Foundations Done Right
Even experienced engineers skip basics that later cause flaky scripts.
Always start with a shebang and safe defaults:
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
-
et -e → exit on error
-
set -u → fail on undefined variables
-
set -o pipefail → fail if any part of a pipe fails ​
Use functions instead of long procedural blobs:
log() { echo "[$(date +'%F %T')] $*"; }
die() {
log "ERROR: $*"
exit 1
}
These patterns make scripts safer and easier to reuse across services and environments.
2. Parameters, Flags, and Environments
A good DevOps script is configurable without editing the file.
Using Arguments and Defaults
#!/usr/bin/env bash
set -euo pipefail
ENVIRONMENT="${1:-staging}" # default to staging
log() { echo "[$(date +'%F %T')] [$ENVIRONMENT] $*"; }
log "Deploying to $ENVIRONMENT"
-
${1:-staging} gives you a default while still allowing overrides.
-
This pattern works great for scripts you’ll run from CI/CD pipelines. ​
Robust Flag Parsing
For more complex tools, use getopts:
while getopts "e:v:h" opt; do
case "$opt" in
e) ENVIRONMENT="$OPTARG" ;;
v) VERSION="$OPTARG" ;;
h) echo "Usage: deploy.sh -e <env> -v <version>"; exit 0 ;;
*) exit 1 ;;
esac
done
This turns a script into a proper CLI your team can rely on.​
3. Real‑World Deployment Script Pattern
Here’s a simplified multi‑step deployment flow you can adapt for web apps or microservices. ​
#!/usr/bin/env bash
set -euo pipefail
ENVIRONMENT="${1:-staging}"
APP_DIR="/srv/myapp"
REPO_URL="git@github.com:org/myapp.git"
log() { echo "[$(date +'%F %T')] [$ENVIRONMENT] $*"; }
deploy() {
log "Updating code..."
if [[ ! -d "$APP_DIR/.git" ]]; then
git clone "$REPO_URL" "$APP_DIR"
fi
cd "$APP_DIR"
git fetch --all
git checkout main
git pull --ff-only
log "Installing dependencies..."
npm ci
log "Running tests..."
npm test
log "Building..."
npm run build
log "Restarting service..."
sudo systemctl restart myapp
log "Deployment complete."
}
deploy
Why this works for DevOps:
- Idempotent: safe to run multiple times. ​
- Ties into systemd for consistent service management.
4. Automated Health Checks and Rollbacks
Production automation needs more than “deploy and hope.”
Health Check Example
health_check() {
local url="${1:-http://localhost/health}"
if curl -fsS "$url" > /dev/null; then
log "Health check passed for $url"
else
die "Health check FAILED for $url"
fi
}
Combine this with deployment:
previous_version() {
git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo ""
}
rollback() {
local prev
prev="$(previous_version)"
[[ -z "$prev" ]] && die "No previous version found for rollback"
log "Rolling back to $prev"
git checkout "$prev"
npm run build
sudo systemctl restart myapp
}
After deployment:
deploy
if ! health_check "https://myapp.example.com/health"; then
log "Health check failed; rolling back"
rollback
fi
This pattern mirrors real‑world blue/green or canary flows on a smaller scale. ​
5. Log Rotation and Cleanup Jobs
Bash + cron is still a perfectly valid way to manage logs on smaller setups. ​
Rotate and Compress Logs
#!/usr/bin/env bash
set -euo pipefail
LOG_DIR="/var/log/myapp"
DAYS_TO_KEEP=7
find "$LOG_DIR" -type f -name "*.log" -mtime +$DAYS_TO_KEEP -print0 \
| while IFS= read -r -d '' file; do
gzip "$file"
done
Remove Old Archives
find "$LOG_DIR" -type f -name "*.gz" -mtime +30 -delete
Schedule with cron:
crontab -e
# Run cleanup daily at 01:30
30 1 * * * /usr/local/bin/log_cleanup.sh
This keeps disks healthy without needing a full log‑management stack. ​
6. Monitoring Scripts with Alerts
You can wrap common Linux monitoring commands into Bash scripts that push alerts to Slack, email, or webhooks. ​ Example: CPU and Memory Watchdog
#!/usr/bin/env bash
set -euo pipefail
CPU_THRESHOLD=80
MEM_THRESHOLD=80
cpu_usage() {
mpstat 1 1 | awk '/Average/ && $12 ~ /[0-9.]+/ {print 100-$12}'
}
mem_usage() {
free | awk '/Mem:/ {printf(\"%.0f\", $3/$2 * 100)}'
}
CPU=$(cpu_usage)
MEM=$(mem_usage)
if (( CPU > CPU_THRESHOLD || MEM > MEM_THRESHOLD )); then
echo "High usage detected: CPU=${CPU}% MEM=${MEM}%"
# Hook: send to Slack / email / alerting system
fi
This complements full monitoring stacks by giving you lightweight, scriptable checks.​
7. Safer File and Config Changes
Use Bash to modify configuration files predictably instead of manual edits.
Backups + Atomic Changes
CONFIG="/etc/myapp/config.yaml"
BACKUP="/etc/myapp/config.yaml.$(date +'%F-%H%M%S').bak"
cp "$CONFIG" "$BACKUP"
# Example: toggle a feature flag
sed -i 's/feature_x: false/feature_x: true/' "$CONFIG"
systemctl restart myapp
Pattern:
-
Always create timestamped backups.
-
Make changes with sed, awk, or yq/jq for structured data. ​